initial commitl
This commit is contained in:
commit
0b3dabddde
16 changed files with 1918 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
out/
|
||||
node_modules/
|
505
package-lock.json
generated
Normal file
505
package-lock.json
generated
Normal file
|
@ -0,0 +1,505 @@
|
|||
{
|
||||
"name": "serde",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "serde",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/text-encoding": "^0.0.40",
|
||||
"esbuild": "^0.25.4",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz",
|
||||
"integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz",
|
||||
"integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz",
|
||||
"integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz",
|
||||
"integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz",
|
||||
"integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz",
|
||||
"integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz",
|
||||
"integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz",
|
||||
"integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz",
|
||||
"integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz",
|
||||
"integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz",
|
||||
"integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz",
|
||||
"integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz",
|
||||
"integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz",
|
||||
"integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz",
|
||||
"integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz",
|
||||
"integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz",
|
||||
"integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz",
|
||||
"integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz",
|
||||
"integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz",
|
||||
"integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz",
|
||||
"integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz",
|
||||
"integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz",
|
||||
"integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz",
|
||||
"integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz",
|
||||
"integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/text-encoding": {
|
||||
"version": "0.0.40",
|
||||
"resolved": "https://registry.npmjs.org/@types/text-encoding/-/text-encoding-0.0.40.tgz",
|
||||
"integrity": "sha512-dHzoIdwBfY7jcSTTt6XBkaeiuFQAQD7r/7aJySKDdHkYBCDOvs9jPVt4NYXuwBMn89PP6gSd29WubIS19wTiXg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz",
|
||||
"integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.4",
|
||||
"@esbuild/android-arm": "0.25.4",
|
||||
"@esbuild/android-arm64": "0.25.4",
|
||||
"@esbuild/android-x64": "0.25.4",
|
||||
"@esbuild/darwin-arm64": "0.25.4",
|
||||
"@esbuild/darwin-x64": "0.25.4",
|
||||
"@esbuild/freebsd-arm64": "0.25.4",
|
||||
"@esbuild/freebsd-x64": "0.25.4",
|
||||
"@esbuild/linux-arm": "0.25.4",
|
||||
"@esbuild/linux-arm64": "0.25.4",
|
||||
"@esbuild/linux-ia32": "0.25.4",
|
||||
"@esbuild/linux-loong64": "0.25.4",
|
||||
"@esbuild/linux-mips64el": "0.25.4",
|
||||
"@esbuild/linux-ppc64": "0.25.4",
|
||||
"@esbuild/linux-riscv64": "0.25.4",
|
||||
"@esbuild/linux-s390x": "0.25.4",
|
||||
"@esbuild/linux-x64": "0.25.4",
|
||||
"@esbuild/netbsd-arm64": "0.25.4",
|
||||
"@esbuild/netbsd-x64": "0.25.4",
|
||||
"@esbuild/openbsd-arm64": "0.25.4",
|
||||
"@esbuild/openbsd-x64": "0.25.4",
|
||||
"@esbuild/sunos-x64": "0.25.4",
|
||||
"@esbuild/win32-arm64": "0.25.4",
|
||||
"@esbuild/win32-ia32": "0.25.4",
|
||||
"@esbuild/win32-x64": "0.25.4"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
package.json
Normal file
18
package.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "serde",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.4",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
81
src/de/generic.ts
Normal file
81
src/de/generic.ts
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { staticImplements } from '../utils'
|
||||
import { Deserialize, Deserializer, IterableAccess, MapAccess, Visitor } from './interface'
|
||||
|
||||
@staticImplements<Deserialize<any>>()
|
||||
export class GenericSeed<T> implements Deserialize<T> {
|
||||
static deserialize<T, D extends Deserializer>(deserializer: D): T {
|
||||
return deserializer.deserializeAny(new GenericVisitor<T>())
|
||||
}
|
||||
|
||||
deserialize<D extends Deserializer>(deserializer: D): T {
|
||||
return GenericSeed.deserialize(deserializer)
|
||||
}
|
||||
}
|
||||
|
||||
export class GenericVisitor<T> implements Visitor<T> {
|
||||
visitString(value: string): T {
|
||||
return value as T
|
||||
}
|
||||
|
||||
visitNumber(value: number): T {
|
||||
return value as T
|
||||
}
|
||||
|
||||
visitBigInt(value: bigint): T {
|
||||
return value as T
|
||||
}
|
||||
|
||||
visitBoolean(value: boolean): T {
|
||||
return value as T
|
||||
}
|
||||
|
||||
visitSymbol(value: symbol): T {
|
||||
return value as T
|
||||
}
|
||||
|
||||
visitNull(): T {
|
||||
return null as T
|
||||
}
|
||||
|
||||
visitObject(access: MapAccess): T {
|
||||
const result: Record<PropertyKey, any> = {}
|
||||
let entry
|
||||
|
||||
while ((entry = access.nextEntry<string, any>())) {
|
||||
result[entry[0]] = entry[1]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
visitFunction?(value: Function): T {
|
||||
return value as T
|
||||
}
|
||||
|
||||
visitMap?(access: MapAccess): T {
|
||||
const result = new Map()
|
||||
let entry
|
||||
|
||||
while ((entry = access.nextEntry<string, any>())) {
|
||||
result.set(entry[0], entry[1])
|
||||
}
|
||||
|
||||
return result as T
|
||||
}
|
||||
|
||||
visitIterable?(access: IterableAccess): T {
|
||||
const result = new Array(access.sizeHint())
|
||||
let element
|
||||
|
||||
while ((element = access.nextElement())) {
|
||||
result.push(element)
|
||||
}
|
||||
|
||||
return result as T
|
||||
}
|
||||
|
||||
visitClass?(_name: string, _fields: string[], _value: any): T {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
}
|
||||
|
4
src/de/index.ts
Normal file
4
src/de/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export * from './interface'
|
||||
export * from './generic'
|
||||
export * from './mixin'
|
||||
|
88
src/de/interface.ts
Normal file
88
src/de/interface.ts
Normal file
|
@ -0,0 +1,88 @@
|
|||
import { Nullable } from '../utils'
|
||||
import { GenericSeed } from './generic'
|
||||
|
||||
export interface MapAccess {
|
||||
nextKeySeed<T, K extends Deserialize<T>>(seed: K): Nullable<T>
|
||||
nextValueSeed<T, V extends Deserialize<T>>(seed: V): Nullable<T>
|
||||
nextEntrySeed<TK, TV, K extends Deserialize<TK>, V extends Deserialize<TV>>(kseed: K, vseed: V): Nullable<[TK, TV]>
|
||||
nextKey<T>(): Nullable<T>
|
||||
nextValue<T>(): Nullable<T>
|
||||
nextEntry<K, V>(): Nullable<[K, V]>
|
||||
}
|
||||
|
||||
export abstract class DefaultMapAccessImpl implements MapAccess {
|
||||
abstract nextKeySeed<T, K extends Deserialize<T>>(seed: K): Nullable<T>
|
||||
abstract nextValueSeed<T, V extends Deserialize<T>>(seed: V): Nullable<T>
|
||||
|
||||
nextEntrySeed<TK, TV, K extends Deserialize<TK>, V extends Deserialize<TV>>(kseed: K, vseed: V): Nullable<[TK, TV]> {
|
||||
const key = this.nextKeySeed(kseed) as Nullable<TK>
|
||||
if (key) {
|
||||
const value = this.nextValueSeed(vseed) as Nullable<TV>
|
||||
|
||||
if (value) {
|
||||
return [key, value]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextKey<T>(): Nullable<T> {
|
||||
return this.nextValueSeed(GenericSeed<T>)
|
||||
}
|
||||
|
||||
nextValue<T>(): Nullable<T> {
|
||||
return this.nextValueSeed(GenericSeed<T>)
|
||||
}
|
||||
|
||||
nextEntry<K, V>(): Nullable<[K, V]> {
|
||||
return this.nextEntrySeed(GenericSeed<K>, GenericSeed<V>)
|
||||
}
|
||||
}
|
||||
|
||||
export interface IterableAccess {
|
||||
nextElementSeed<T, I extends Deserialize<T>>(seed: I): Nullable<T>
|
||||
nextElement<T>(): Nullable<T>
|
||||
sizeHint(): number
|
||||
}
|
||||
|
||||
export abstract class DefaultIterableAccessImpl implements IterableAccess {
|
||||
abstract nextElementSeed<T, I extends Deserialize<T>>(seed: I): Nullable<T>
|
||||
|
||||
nextElement<T>(): Nullable<T> {
|
||||
return this.nextElementSeed(GenericSeed<T>)
|
||||
}
|
||||
|
||||
sizeHint(): number { return 0 }
|
||||
}
|
||||
|
||||
export interface Visitor<T> {
|
||||
visitString(value: string): T
|
||||
visitNumber(value: number): T
|
||||
visitBigInt(value: bigint): T
|
||||
visitBoolean(value: boolean): T
|
||||
visitSymbol(value: symbol): T
|
||||
visitNull(): T
|
||||
visitObject(value: MapAccess): T
|
||||
visitFunction?(value: Function): T
|
||||
visitMap?(value: MapAccess): T
|
||||
visitIterable?(value: IterableAccess): T
|
||||
visitClass?(name: string, fields: string[], value: any): T
|
||||
}
|
||||
|
||||
export interface Deserializer {
|
||||
deserializeAny<T, V extends Visitor<T>>(visitor: V): T
|
||||
deserializeString<T, V extends Visitor<T>>(visitor: V): T
|
||||
deserializeNumber<T, V extends Visitor<T>>(visitor: V): T
|
||||
deserializeBigInt<T, V extends Visitor<T>>(visitor: V): T
|
||||
deserializeBoolean<T, V extends Visitor<T>>(visitor: V): T
|
||||
deserializeSymbol<T, V extends Visitor<T>>(visitor: V): T
|
||||
deserializeNull<T, V extends Visitor<T>>(visitor: V): T
|
||||
deserializeObject<T, V extends Visitor<T>>(visitor: V): T
|
||||
deserializeFunction?<T, V extends Visitor<T>>(visitor: V): T
|
||||
deserializeMap?<T, V extends Visitor<T>>(visitor: V): T
|
||||
deserializeIterable?<T, V extends Visitor<T>>(visitor: V): T
|
||||
deserializeClass?<T, V extends Visitor<T>>(name: string, fields: string[], visitor: V): T
|
||||
}
|
||||
|
||||
export interface Deserialize<T> {
|
||||
deserialize<D extends Deserializer>(deserializer: D): T
|
||||
}
|
30
src/de/mixin.ts
Normal file
30
src/de/mixin.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { CaseConvention, Constructor, staticImplements } from '../utils'
|
||||
import { GenericVisitor } from './generic'
|
||||
import { Deserialize, Deserializer } from './interface'
|
||||
|
||||
export interface DeserializationOptions {
|
||||
rename?: string
|
||||
renameAll?: CaseConvention
|
||||
}
|
||||
|
||||
const DefaultDeserializationOptions = {}
|
||||
|
||||
export function deserialize(options?: DeserializationOptions) {
|
||||
options = {
|
||||
...DefaultDeserializationOptions,
|
||||
...options
|
||||
}
|
||||
|
||||
return function <T, C extends Constructor>(constructor: C) {
|
||||
@staticImplements<Deserialize<T>>()
|
||||
class Deserializable extends constructor {
|
||||
static deserialize<D extends Deserializer>(deserializer: D): T {
|
||||
const visitor = new GenericVisitor<T>()
|
||||
return deserializer.deserializeAny(visitor)
|
||||
}
|
||||
}
|
||||
|
||||
return Deserializable
|
||||
}
|
||||
}
|
||||
|
400
src/index.ts
Normal file
400
src/index.ts
Normal file
|
@ -0,0 +1,400 @@
|
|||
//export type Nullable<T> = T | undefined
|
||||
//
|
||||
//function staticImplements<T>() {
|
||||
// return <U extends T>(constructor: U) => { constructor }
|
||||
//}
|
||||
//
|
||||
//@staticImplements<Deserialize<any>>()
|
||||
//class GenericSeed<T> implements Deserialize<T> {
|
||||
// static deserialize<T, D extends Deserializer>(deserializer: D): T {
|
||||
// return deserializer.deserializeAny(new GenericVisitor<T>())
|
||||
// }
|
||||
//
|
||||
// deserialize<D extends Deserializer>(deserializer: D): T {
|
||||
// return GenericSeed.deserialize(deserializer)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//class GenericVisitor<T> implements Visitor<T> {
|
||||
// visitString(value: string): T {
|
||||
// return value as T
|
||||
// }
|
||||
//
|
||||
// visitNumber(value: number): T {
|
||||
// return value as T
|
||||
// }
|
||||
//
|
||||
// visitBigInt(value: bigint): T {
|
||||
// return value as T
|
||||
// }
|
||||
//
|
||||
// visitBoolean(value: boolean): T {
|
||||
// return value as T
|
||||
// }
|
||||
//
|
||||
// visitSymbol(value: symbol): T {
|
||||
// return value as T
|
||||
// }
|
||||
//
|
||||
// visitNull(): T {
|
||||
// return null as T
|
||||
// }
|
||||
//
|
||||
// visitObject(access: MapAccess): T {
|
||||
// const result: Record<PropertyKey, any> = {}
|
||||
// let entry
|
||||
//
|
||||
// while ((entry = access.nextEntry<string, any>())) {
|
||||
// result[entry[0]] = entry[1]
|
||||
// }
|
||||
//
|
||||
// return result
|
||||
// }
|
||||
//
|
||||
// visitFunction?(value: Function): T {
|
||||
// return value as T
|
||||
// }
|
||||
//
|
||||
// visitMap?(access: MapAccess): T {
|
||||
// const result = new Map()
|
||||
// let entry
|
||||
//
|
||||
// while ((entry = access.nextEntry<string, any>())) {
|
||||
// result.set(entry[0], entry[1])
|
||||
// }
|
||||
//
|
||||
// return result as T
|
||||
// }
|
||||
//
|
||||
// visitIterable?(access: IterableAccess): T {
|
||||
// const result = new Array(access.sizeHint())
|
||||
// let element
|
||||
//
|
||||
// while ((element = access.nextElement())) {
|
||||
// result.push(element)
|
||||
// }
|
||||
//
|
||||
// return result as T
|
||||
// }
|
||||
//
|
||||
// visitClass?(_name: string, _fields: string[], _value: any): T {
|
||||
// throw new Error("Method not implemented.")
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//export abstract class MapAccess {
|
||||
// abstract nextKeySeed<T, K extends Deserialize<T>>(seed: K): Nullable<T>
|
||||
// abstract nextValueSeed<T, V extends Deserialize<T>>(seed: V): Nullable<T>
|
||||
//
|
||||
// nextEntrySeed<TK, TV, K extends Deserialize<TK>, V extends Deserialize<TV>>(kseed: K, vseed: V): Nullable<[TK, TV]> {
|
||||
// const key = this.nextKeySeed(kseed) as Nullable<TK>
|
||||
// if (key) {
|
||||
// const value = this.nextValueSeed(vseed) as Nullable<TV>
|
||||
//
|
||||
// if (value) {
|
||||
// return [key, value]
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// nextKey<T>(): Nullable<T> {
|
||||
// return this.nextValueSeed(GenericSeed<T>)
|
||||
// }
|
||||
//
|
||||
// nextValue<T>(): Nullable<T> {
|
||||
// return this.nextValueSeed(GenericSeed<T>)
|
||||
// }
|
||||
//
|
||||
// nextEntry<K, V>(): Nullable<[K, V]> {
|
||||
// return this.nextEntrySeed(GenericSeed<K>, GenericSeed<V>)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//export abstract class IterableAccess {
|
||||
// abstract nextElementSeed<T, I extends Deserialize<T>>(seed: I): Nullable<T>
|
||||
//
|
||||
// nextElement<T>(): Nullable<T> {
|
||||
// return this.nextElementSeed(GenericSeed<T>)
|
||||
// }
|
||||
//
|
||||
// sizeHint(): number { return 0 }
|
||||
//}
|
||||
//
|
||||
//export interface ObjectSerializer<T = void> {
|
||||
// serializeKey<U extends Serializable>(key: U): T
|
||||
// serializeValue<U extends Serializable>(value: U): T
|
||||
// end(): T
|
||||
//}
|
||||
//
|
||||
//export interface IterableSerializer<T = void> {
|
||||
// serializeElement<U extends Serializable>(element: U): T
|
||||
// end(): T
|
||||
//}
|
||||
//
|
||||
//export interface ClassSerializer<T = void> {
|
||||
// serializeField<U extends Serializable>(name: PropertyKey, value: U): T
|
||||
// end(): T
|
||||
//}
|
||||
//
|
||||
//const TypeSerializerMethods = [
|
||||
// 'serializeString',
|
||||
// 'serializeNumber',
|
||||
// 'serializeBigInt',
|
||||
// 'serializeBoolean',
|
||||
// 'serializeSymbol',
|
||||
// 'serializeMap',
|
||||
// 'serializeIterable',
|
||||
// 'serializeNull',
|
||||
// 'serializeObject',
|
||||
// 'serializeInstance',
|
||||
// 'serializeFunction'
|
||||
//] as const
|
||||
//
|
||||
//interface TypeSerializer<T> {
|
||||
// serializeString(value: string): T
|
||||
// serializeNumber(value: number): T
|
||||
// serializeBigInt(value: bigint): T
|
||||
// serializeBoolean(value: boolean): T
|
||||
// serializeSymbol(value: Symbol): T
|
||||
// serializeNull(): T
|
||||
// serializeObject(): ObjectSerializer<T>
|
||||
// serializeFunction?(value: Function): T
|
||||
// serializeMap?(): ObjectSerializer<T>
|
||||
// serializeIterable?(): IterableSerializer<T>
|
||||
// serializeClass?(name: PropertyKey): ClassSerializer<T>
|
||||
//}
|
||||
//
|
||||
//const AnySerializerMethods = ['serializeAny']
|
||||
//
|
||||
//interface AnySerializer<T> {
|
||||
// serializeAny?(value?: any): T
|
||||
//}
|
||||
//
|
||||
//function isGenericSerializer(value: any): boolean {
|
||||
// return AnySerializerMethods.every(k => isFunction(value[k])) &&
|
||||
// TypeSerializerMethods.every(k => !isFunction(value[k]))
|
||||
//}
|
||||
//
|
||||
//function isPlainObject(value: any): boolean {
|
||||
// return Object.getPrototypeOf(value) === Object.prototype
|
||||
//}
|
||||
//
|
||||
//function isFunction(value: any): value is Function {
|
||||
// return value != null && typeof value === 'function'
|
||||
//}
|
||||
//
|
||||
//function isIterable(value: any): value is Iterable<any> {
|
||||
// return isFunction(value[Symbol.iterator])
|
||||
//}
|
||||
//
|
||||
//export type Serializer<T> = Partial<TypeSerializer<T>> & Partial<AnySerializer<T>>
|
||||
//
|
||||
//export interface Visitor<T> {
|
||||
// visitString(value: string): T
|
||||
// visitNumber(value: number): T
|
||||
// visitBigInt(value: bigint): T
|
||||
// visitBoolean(value: boolean): T
|
||||
// visitSymbol(value: symbol): T
|
||||
// visitNull(): T
|
||||
// visitObject(value: MapAccess): T
|
||||
// visitFunction?(value: Function): T
|
||||
// visitMap?(value: MapAccess): T
|
||||
// visitIterable?(value: IterableAccess): T
|
||||
// visitClass?(name: string, fields: string[], value: any): T
|
||||
//}
|
||||
//
|
||||
//export interface Deserializer {
|
||||
// deserializeAny<T, V extends Visitor<T>>(visitor: V): T
|
||||
// deserializeString<T, V extends Visitor<T>>(visitor: V): T
|
||||
// deserializeNumber<T, V extends Visitor<T>>(visitor: V): T
|
||||
// deserializeBigInt<T, V extends Visitor<T>>(visitor: V): T
|
||||
// deserializeBoolean<T, V extends Visitor<T>>(visitor: V): T
|
||||
// deserializeSymbol<T, V extends Visitor<T>>(visitor: V): T
|
||||
// deserializeNull<T, V extends Visitor<T>>(visitor: V): T
|
||||
// deserializeObject<T, V extends Visitor<T>>(visitor: V): T
|
||||
// deserializeFunction?<T, V extends Visitor<T>>(visitor: V): T
|
||||
// deserializeMap?<T, V extends Visitor<T>>(visitor: V): T
|
||||
// deserializeIterable?<T, V extends Visitor<T>>(visitor: V): T
|
||||
// deserializeClass?<T, V extends Visitor<T>>(name: string, fields: string[], visitor: V): T
|
||||
//}
|
||||
//
|
||||
//export type Primitive = string | number | boolean | symbol | bigint | null | undefined
|
||||
//export interface ToString {
|
||||
// toString(): string
|
||||
//}
|
||||
//
|
||||
//export interface Serialize {
|
||||
// serialize<T, S extends Serializer<T>>(serializer: S): T
|
||||
//}
|
||||
//
|
||||
//export type Serializable = Primitive | ToString | Serialize
|
||||
//
|
||||
//export interface Deserialize<T> {
|
||||
// deserialize<D extends Deserializer>(deserializer: D): T
|
||||
//}
|
||||
//
|
||||
//type Constructor = new (...args: any[]) => object
|
||||
//
|
||||
//export const CaseConvention = Object.freeze({
|
||||
// Lowercase: 0,
|
||||
// Uppercase: 1,
|
||||
// PascalCase: 2,
|
||||
// CamelCase: 3,
|
||||
// SnakeCase: 4,
|
||||
// ScreamingSnakeCase: 5,
|
||||
// KebabCase: 6,
|
||||
// ScreamingKebabCase: 7
|
||||
//} as const)
|
||||
//
|
||||
//export type CaseConvention = typeof CaseConvention[keyof typeof CaseConvention]
|
||||
//
|
||||
//function orElse(thisArg: any, a: Nullable<Function>, b: Function) {
|
||||
// return function(...args: any) {
|
||||
// const fn = a != null ? a : b
|
||||
// return fn.apply(thisArg, args)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// helper for better ergonomics
|
||||
//// allows us to capture this, the fallback method, and the args in a closure
|
||||
//function ifNull(thisArg: any, b: Function, ...args: any) {
|
||||
// return function(a: Nullable<Function>) {
|
||||
// return orElse(thisArg, a, b).call(thisArg, args)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//const unhandledType = (serializer: any, value: any) => new TypeError(`"${serializer.constructor.name}" has no method for value type "${typeof value}"`)
|
||||
//
|
||||
//function serializeEntries<T, K extends Serializable, V extends Serializable, E extends Iterable<[K, V]>>(serializer: ObjectSerializer<T>, value: E) {
|
||||
// let state
|
||||
//
|
||||
// for (const [key, val] of value) {
|
||||
// state = serializer.serializeKey(key)
|
||||
// state = serializer.serializeValue(val)
|
||||
// }
|
||||
//
|
||||
// return serializer.end()
|
||||
//}
|
||||
//
|
||||
//function serializeObject<T, K extends PropertyKey, V extends Serializable, R extends Record<K, V>>(serializer: ObjectSerializer<T>, value: R) {
|
||||
// return serializeEntries(serializer, Object.entries(value) as Iterable<[K, V]>)
|
||||
//}
|
||||
//
|
||||
//function getClassName(value: any): Nullable<string> {
|
||||
// return value?.constructor.name
|
||||
//}
|
||||
//
|
||||
//function serializeClass<T, K extends PropertyKey, V extends Serializable, R extends Record<K, V>>(serializer: ClassSerializer<T>, value: R) {
|
||||
// for (const prop in value) {
|
||||
// serializer.serializeField(prop, value[prop])
|
||||
// }
|
||||
//
|
||||
// return serializer.end()
|
||||
//}
|
||||
//
|
||||
//function serializeIter<T, V extends Iterable<any>>(serializer: IterableSerializer<T>, value: V) {
|
||||
// let state
|
||||
//
|
||||
// for (const val of value) {
|
||||
// state = serializer.serializeElement(val)
|
||||
// }
|
||||
//
|
||||
// return serializer.end()
|
||||
//}
|
||||
//
|
||||
//// dispatches in the order of serialize<type> -> serializeAny -> throw TypeError
|
||||
//export function serializeWith<T>(serializer: Serializer<T>, value: Serializable): Nullable<T> {
|
||||
// // prepare fallback methods
|
||||
// const serializeAny = orElse(
|
||||
// serializer,
|
||||
// serializer.serializeAny,
|
||||
// (value: Serializable) => unhandledType(serializer, value)
|
||||
// )
|
||||
//
|
||||
// const serialize = ifNull(serializer, serializeAny, value)
|
||||
//
|
||||
// switch (typeof value) {
|
||||
// case 'string': return serialize(serializer.serializeString)
|
||||
// case 'number': return serialize(serializer.serializeNumber)
|
||||
// case 'bigint': return serialize(serializer.serializeBigInt)
|
||||
// case 'boolean': return serialize(serializer.serializeBoolean)
|
||||
// case 'symbol': return serialize(serializer.serializeSymbol)
|
||||
// case 'undefined': return serialize(serializer.serializeNull)
|
||||
// case 'function': return serialize(serializer.serializeFunction)
|
||||
//
|
||||
// case 'object':
|
||||
// if (value instanceof Map && isFunction(serializer.serializeMap)) {
|
||||
// return serializeEntries(serializer.serializeMap(), value)
|
||||
// } else if (isIterable(value) && isFunction(serializer.serializeIterable)) {
|
||||
// return serializeIter(serializer.serializeIterable(), value)
|
||||
// } else if (isFunction(serializer.serializeClass) && !isPlainObject(value)) {
|
||||
// const name = getClassName(value)
|
||||
// return serializeClass(serializer.serializeClass(name!), value as any)
|
||||
// } else if (isFunction(serializer.serializeObject)) {
|
||||
// return serializeObject(serializer.serializeObject!(), value as Record<PropertyKey, any>)
|
||||
// } // deliberate fallthrough when the above fail
|
||||
//
|
||||
// default: return serializeAny(value)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//export interface SerializationOptions {
|
||||
// default?: <T>() => T
|
||||
// rename?: string
|
||||
// renameAll?: CaseConvention
|
||||
// tag?: string
|
||||
// untagged?: boolean
|
||||
// withInherited?: boolean
|
||||
//}
|
||||
//
|
||||
//const DefaultSerializationOptions: SerializationOptions = {
|
||||
// withInherited: true
|
||||
//}
|
||||
//
|
||||
//export function serialize(options?: SerializationOptions) {
|
||||
// options = {
|
||||
// ...DefaultSerializationOptions,
|
||||
// ...options
|
||||
// }
|
||||
//
|
||||
// return function <T extends Constructor>(constructor: T) {
|
||||
// return class Serializable extends constructor implements Serializable {
|
||||
// static name = constructor.name
|
||||
// serialize<U>(serializer: Serializer<U>): U {
|
||||
// // shortcut for serializers with only the serializeAny method
|
||||
// if (isGenericSerializer(serializer)) {
|
||||
// return serializer.serializeAny!(this) as U
|
||||
// } else {
|
||||
// return serializeWith(serializer, this) as U
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//export interface DeserializationOptions {
|
||||
// rename?: string
|
||||
// renameAll?: CaseConvention
|
||||
//}
|
||||
//
|
||||
//const DefaultDeserializationOptions = {
|
||||
//}
|
||||
//
|
||||
//export function deserialize(options?: DeserializationOptions) {
|
||||
// options = {
|
||||
// ...DefaultDeserializationOptions,
|
||||
// ...options
|
||||
// }
|
||||
//
|
||||
// return function <T, C extends Constructor>(constructor: C) {
|
||||
// @staticImplements<Deserialize<T>>()
|
||||
// class Deserializable extends constructor {
|
||||
// static deserialize<D extends Deserializer>(deserializer: D): T {
|
||||
// const visitor = new GenericVisitor<T>()
|
||||
// return deserializer.deserializeAny(visitor)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return Deserializable
|
||||
// }
|
||||
//}
|
388
src/json.ts
Normal file
388
src/json.ts
Normal file
|
@ -0,0 +1,388 @@
|
|||
import { DefaultIterableAccessImpl, DefaultMapAccessImpl, Deserialize, Deserializer, IterableAccess, MapAccess, Visitor } from './de'
|
||||
import { Serializer, serializeWith } from './ser'
|
||||
import { mixin, Nullable } from './utils'
|
||||
|
||||
export function toString(value: any): string {
|
||||
return serializeWith(new JSONSerializer(), value)!
|
||||
}
|
||||
|
||||
export function fromString<T, D extends Deserialize<T>>(value: string, into: D): T {
|
||||
const deserializer = JSONDeserializer.fromString(value)
|
||||
return into.deserialize(deserializer)
|
||||
}
|
||||
|
||||
type Byte = number
|
||||
|
||||
const clamp = (value: number, min: number, max: number): number => {
|
||||
return Math.min(Math.max(value, min), max)
|
||||
}
|
||||
|
||||
const isNumeric = (value: any): value is number => {
|
||||
return !isNaN(value)
|
||||
}
|
||||
|
||||
const isNumericToken = (value: Byte) => {
|
||||
return value === Token.Period || value === Token.Hyphen || Token.Digit.includes(value)
|
||||
}
|
||||
|
||||
interface Predicate<T> {
|
||||
(value: T): boolean
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
const b = (strings: TemplateStringsArray) => encoder.encode(strings[0])
|
||||
const char = (strings: TemplateStringsArray) => b(strings)[0]
|
||||
|
||||
const Literal = Object.freeze({
|
||||
True: b`true`,
|
||||
False: b`false`
|
||||
} as const)
|
||||
|
||||
const Token = Object.freeze({
|
||||
Space: char` `,
|
||||
LeftCurly: char`{`,
|
||||
RightCurly: char`}`,
|
||||
LeftSquare: char`[`,
|
||||
RightSquare: char`]`,
|
||||
Quote: char`"`,
|
||||
ForwardSlash: char`\\`,
|
||||
Digit: b`0123456789`,
|
||||
Hyphen: char`-`,
|
||||
Period: char`.`,
|
||||
Comma: char`,`,
|
||||
Colon: char`:`
|
||||
} as const)
|
||||
|
||||
|
||||
export interface CommaSeparated extends MapAccess, IterableAccess { }
|
||||
@mixin<MapAccess>(DefaultMapAccessImpl)
|
||||
@mixin<IterableAccess>(DefaultIterableAccessImpl)
|
||||
export class CommaSeparated implements MapAccess, IterableAccess {
|
||||
private readonly de: JSONDeserializer
|
||||
private first: boolean = true
|
||||
|
||||
constructor(deserializer: JSONDeserializer) {
|
||||
this.de = deserializer
|
||||
}
|
||||
|
||||
nextKeySeed<T, K extends Deserialize<T>>(seed: K): Nullable<T> {
|
||||
if (this.de.buffer.peek().next() === Token.RightCurly) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.first) {
|
||||
const take = this.de.buffer.take()
|
||||
if (take.next() !== Token.Comma) {
|
||||
throw unexpected(',', take.toString(), this.de.buffer.position)
|
||||
}
|
||||
}
|
||||
|
||||
this.first = false
|
||||
return seed.deserialize(this.de)
|
||||
}
|
||||
|
||||
nextValueSeed<T, V extends Deserialize<T>>(seed: V): Nullable<T> {
|
||||
const next = this.de.buffer.next()
|
||||
if (next !== Token.Colon) {
|
||||
throw unexpected(':', next.toString(), this.de.buffer.position)
|
||||
}
|
||||
|
||||
return seed.deserialize(this.de)
|
||||
}
|
||||
|
||||
nextElementSeed<T, I extends Deserialize<T>>(seed: I): Nullable<T> {
|
||||
if (this.de.buffer.peek().next() === Token.RightSquare) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.first) {
|
||||
const take = this.de.buffer.take()
|
||||
if (take.next() !== Token.Comma) {
|
||||
throw unexpected(',', take.toString(), this.de.buffer.position)
|
||||
}
|
||||
}
|
||||
|
||||
this.first = false
|
||||
return seed.deserialize(this.de)
|
||||
}
|
||||
}
|
||||
|
||||
class StringBuffer {
|
||||
private readonly view: Uint8Array
|
||||
private index: number = 0
|
||||
private readonly encoder: TextEncoder
|
||||
private readonly decoder: TextDecoder
|
||||
|
||||
get position() {
|
||||
return this.index
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.view.byteLength
|
||||
}
|
||||
|
||||
constructor(view: Uint8Array, encoder: TextEncoder = new TextEncoder(), decoder: TextDecoder = new TextDecoder()) {
|
||||
this.view = view
|
||||
this.encoder = encoder
|
||||
this.decoder = decoder
|
||||
}
|
||||
|
||||
static fromArrayBuffer(value: ArrayBuffer, encoder?: TextEncoder, decoder?: TextDecoder): StringBuffer {
|
||||
return new this(new Uint8Array(value), encoder, decoder)
|
||||
}
|
||||
|
||||
static fromString(value: string, encoder: TextEncoder = new TextEncoder(), decoder?: TextDecoder): StringBuffer {
|
||||
return this.fromArrayBuffer(
|
||||
encoder.encode(value),
|
||||
encoder,
|
||||
decoder
|
||||
)
|
||||
}
|
||||
|
||||
next() {
|
||||
const value = this.view[this.index]
|
||||
this.index += 1
|
||||
return value
|
||||
}
|
||||
|
||||
nextChar() {
|
||||
return this.take().toString()
|
||||
}
|
||||
|
||||
done(): boolean {
|
||||
return this.index >= this.view.byteLength
|
||||
}
|
||||
|
||||
toBytes() {
|
||||
return this.view.slice(this.index)
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.decoder.decode(this.toBytes())
|
||||
}
|
||||
|
||||
take(limit: number = 1): StringBuffer {
|
||||
const bytes = this.peek(limit)
|
||||
this.index += limit
|
||||
return bytes
|
||||
}
|
||||
|
||||
at(index: number) {
|
||||
return this.view[this.index + index]
|
||||
}
|
||||
|
||||
takeWhile(fn: Predicate<number>): StringBuffer {
|
||||
let index = 0
|
||||
|
||||
while (!this.done() && fn(this.at(index))) {
|
||||
index += 1
|
||||
}
|
||||
|
||||
return this.take(index)
|
||||
}
|
||||
|
||||
drop(limit: number) {
|
||||
this.index += limit
|
||||
return this
|
||||
}
|
||||
|
||||
peek(limit: number = 1): StringBuffer {
|
||||
const index = this.index
|
||||
return this.slice(index, index + limit)
|
||||
}
|
||||
|
||||
startsWith(value: string | ArrayBufferLike): boolean {
|
||||
if (typeof value === 'string') {
|
||||
return this.startsWith(this.encoder.encode(value))
|
||||
}
|
||||
|
||||
const length = value.byteLength
|
||||
const bytes = new Uint8Array(value)
|
||||
|
||||
return this.peek(length).toBytes().every((v, i) => v === bytes[i])
|
||||
}
|
||||
|
||||
slice(start?: number, end?: number) {
|
||||
return new StringBuffer(
|
||||
this.view.subarray(start, end),
|
||||
this.encoder,
|
||||
this.decoder
|
||||
)
|
||||
}
|
||||
|
||||
indexOf(value: number | ArrayBufferLike, start: number = 0) {
|
||||
const search = new Uint8Array(isNumeric(value) ? [value] : value)
|
||||
start = clamp(start, this.index, this.length)
|
||||
const bytes = this.slice(start)
|
||||
|
||||
for (let i = 0, len = bytes.length; i < len; i++) {
|
||||
if (bytes.at(i) === search[0] && bytes.slice(i).startsWith(search)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
export class JSONSerializer implements Serializer<string> {
|
||||
serializeAny(value?: any): string {
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
}
|
||||
|
||||
const unexpected = (expected: string, actual: string, position: number) => new SyntaxError(`Expected ${expected} at position ${position} (got '${actual}')`)
|
||||
|
||||
export class JSONDeserializer implements Deserializer {
|
||||
readonly buffer: StringBuffer
|
||||
|
||||
constructor(buffer: StringBuffer) {
|
||||
this.buffer = buffer
|
||||
}
|
||||
|
||||
static fromString(value: string): JSONDeserializer {
|
||||
return new this(StringBuffer.fromString(value))
|
||||
}
|
||||
|
||||
deserializeAny<T, V extends Visitor<T>>(visitor: V): T {
|
||||
const peek = this.buffer.peek()
|
||||
const nextByte = peek.take()
|
||||
const byte = nextByte.next()
|
||||
|
||||
switch (true) {
|
||||
case b`n`.includes(byte):
|
||||
return this.deserializeNull(visitor)
|
||||
case b`tf`.includes(byte):
|
||||
return this.deserializeBoolean(visitor)
|
||||
case b`-0123456789`.includes(byte):
|
||||
return this.deserializeNumber(visitor)
|
||||
case Token.Quote === byte:
|
||||
return this.deserializeString(visitor)
|
||||
case Token.LeftSquare === byte:
|
||||
return this.deserializeIterable!(visitor)
|
||||
case Token.LeftCurly === byte:
|
||||
return this.deserializeObject(visitor)
|
||||
default:
|
||||
throw new SyntaxError(`Invalid syntax at position ${this.buffer.position}: "${nextByte.toString()}"`)
|
||||
}
|
||||
}
|
||||
|
||||
deserializeNull<T, V extends Visitor<T>>(visitor: V): T {
|
||||
if (this.buffer.startsWith('null')) {
|
||||
this.buffer.take(4)
|
||||
}
|
||||
|
||||
return visitor.visitNull()
|
||||
}
|
||||
|
||||
deserializeObject<T, V extends Visitor<T>>(visitor: V): T {
|
||||
let next = this.buffer.take()
|
||||
if (next.next() === Token.LeftCurly) {
|
||||
|
||||
const value = visitor.visitObject(new CommaSeparated(this))
|
||||
|
||||
next = this.buffer.take()
|
||||
if (next.next() === Token.RightCurly) {
|
||||
return value
|
||||
} else {
|
||||
throw unexpected('}', next.toString(), this.buffer.position)
|
||||
}
|
||||
} else {
|
||||
throw unexpected('{', next.toString(), this.buffer.position)
|
||||
}
|
||||
}
|
||||
|
||||
deserializeString<T, V extends Visitor<T>>(visitor: V): T {
|
||||
const next = this.buffer.take()
|
||||
if (next.next() === Token.Quote) {
|
||||
let index = -1
|
||||
|
||||
do {
|
||||
index = this.buffer.indexOf(Token.Quote, index)
|
||||
} while (index > -1 && this.buffer.at(index - 1) === Token.ForwardSlash)
|
||||
|
||||
if (index === -1) {
|
||||
throw new SyntaxError('Unterminated string literal')
|
||||
}
|
||||
|
||||
const bytes = this.buffer.take(index)
|
||||
this.buffer.take()
|
||||
return visitor.visitString(bytes.toString())
|
||||
} else {
|
||||
throw unexpected('"', next.toString(), this.buffer.position)
|
||||
}
|
||||
}
|
||||
|
||||
deserializeNumber<T, V extends Visitor<T>>(visitor: V): T {
|
||||
const next = this.buffer.peek().next()
|
||||
|
||||
if (isNumericToken(next)) {
|
||||
const digits = this.buffer.takeWhile(isNumericToken).toString()
|
||||
if (digits.length >= 16) {
|
||||
const number = BigInt(digits)
|
||||
return visitor.visitBigInt(number)
|
||||
} else if (digits.length > 0) {
|
||||
let number = parseInt(digits.toString(), 10)
|
||||
return visitor.visitNumber(number)
|
||||
}
|
||||
}
|
||||
|
||||
throw unexpected('"-", ".", or 0..=9', next.toString(), this.buffer.position)
|
||||
}
|
||||
|
||||
deserializeBigInt<T, V extends Visitor<T>>(visitor: V): T {
|
||||
return this.deserializeNumber(visitor)
|
||||
}
|
||||
|
||||
deserializeBoolean<T, V extends Visitor<T>>(visitor: V): T {
|
||||
const next = this.buffer.next()
|
||||
let length = 3
|
||||
|
||||
switch (next) {
|
||||
case Literal.False[0]:
|
||||
length = 4
|
||||
case Literal.True[0]:
|
||||
break
|
||||
default:
|
||||
throw unexpected('"true" or "false"', this.buffer.next().toString(), this.buffer.position)
|
||||
}
|
||||
|
||||
this.buffer.take(length)
|
||||
return visitor.visitBoolean(length === 3)
|
||||
}
|
||||
|
||||
deserializeSymbol<T, V extends Visitor<T>>(_visitor: V): T {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
deserializeFunction?<T, V extends Visitor<T>>(_visitor: V): T {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
deserializeMap?<T, V extends Visitor<T>>(_visitor: V): T {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
deserializeIterable?<T, V extends Visitor<T>>(visitor: V): T {
|
||||
let next = this.buffer.take()
|
||||
if (next.next() === Token.LeftSquare) {
|
||||
|
||||
const value = visitor.visitIterable!(new CommaSeparated(this))
|
||||
|
||||
next = this.buffer.take()
|
||||
if (next.next() === Token.RightSquare) {
|
||||
return value
|
||||
} else {
|
||||
throw unexpected(']', next.toString(), this.buffer.position)
|
||||
}
|
||||
} else {
|
||||
throw unexpected('[', next.toString(), this.buffer.position)
|
||||
}
|
||||
}
|
||||
|
||||
deserializeClass?<T, V extends Visitor<T>>(name: string, fields: string[], visitor: V): T {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
}
|
||||
|
||||
|
78
src/ser/impl.ts
Normal file
78
src/ser/impl.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { ifNull, isFunction, isIterable, isPlainObject, Nullable, orElse } from '../utils'
|
||||
import { ClassSerializer, IterableSerializer, ObjectSerializer, Serializable, Serializer } from './interface'
|
||||
|
||||
const unhandledType = (serializer: any, value: any) => new TypeError(`'${serializer.constructor.name}' has no method for value type '${typeof value}'`)
|
||||
|
||||
function serializeEntries<T, K extends Serializable, V extends Serializable, E extends Iterable<[K, V]>>(serializer: ObjectSerializer<T>, value: E) {
|
||||
let state
|
||||
|
||||
for (const [key, val] of value) {
|
||||
state = serializer.serializeKey(key)
|
||||
state = serializer.serializeValue(val)
|
||||
}
|
||||
|
||||
return serializer.end()
|
||||
}
|
||||
|
||||
function serializeObject<T, K extends PropertyKey, V extends Serializable, R extends Record<K, V>>(serializer: ObjectSerializer<T>, value: R) {
|
||||
return serializeEntries(serializer, Object.entries(value) as Iterable<[K, V]>)
|
||||
}
|
||||
|
||||
function getClassName(value: any): Nullable<string> {
|
||||
return value?.constructor.name
|
||||
}
|
||||
|
||||
function serializeClass<T, K extends PropertyKey, V extends Serializable, R extends Record<K, V>>(serializer: ClassSerializer<T>, value: R) {
|
||||
for (const prop in value) {
|
||||
serializer.serializeField(prop, value[prop])
|
||||
}
|
||||
|
||||
return serializer.end()
|
||||
}
|
||||
|
||||
function serializeIter<T, V extends Iterable<any>>(serializer: IterableSerializer<T>, value: V) {
|
||||
let state
|
||||
|
||||
for (const val of value) {
|
||||
state = serializer.serializeElement(val)
|
||||
}
|
||||
|
||||
return serializer.end()
|
||||
}
|
||||
|
||||
// dispatches in the order of serialize<type> -> serializeAny -> throw TypeError
|
||||
export function serializeWith<T>(serializer: Serializer<T>, value: Serializable): Nullable<T> {
|
||||
// prepare fallback methods
|
||||
const serializeAny = orElse(
|
||||
serializer,
|
||||
serializer.serializeAny,
|
||||
(value: Serializable) => unhandledType(serializer, value)
|
||||
)
|
||||
|
||||
const serialize = ifNull(serializer, serializeAny, value)
|
||||
|
||||
switch (typeof value) {
|
||||
case 'string': return serialize(serializer.serializeString)
|
||||
case 'number': return serialize(serializer.serializeNumber)
|
||||
case 'bigint': return serialize(serializer.serializeBigInt)
|
||||
case 'boolean': return serialize(serializer.serializeBoolean)
|
||||
case 'symbol': return serialize(serializer.serializeSymbol)
|
||||
case 'undefined': return serialize(serializer.serializeNull)
|
||||
case 'function': return serialize(serializer.serializeFunction)
|
||||
|
||||
case 'object':
|
||||
if (value instanceof Map && isFunction(serializer.serializeMap)) {
|
||||
return serializeEntries(serializer.serializeMap(), value)
|
||||
} else if (isIterable(value) && isFunction(serializer.serializeIterable)) {
|
||||
return serializeIter(serializer.serializeIterable(), value)
|
||||
} else if (isFunction(serializer.serializeClass) && !isPlainObject(value)) {
|
||||
const name = getClassName(value)
|
||||
return serializeClass(serializer.serializeClass(name!), value as any)
|
||||
} else if (isFunction(serializer.serializeObject)) {
|
||||
return serializeObject(serializer.serializeObject!(), value as Record<PropertyKey, any>)
|
||||
} // deliberate fallthrough when the above fail
|
||||
|
||||
default: return serializeAny(value)
|
||||
}
|
||||
}
|
||||
|
4
src/ser/index.ts
Normal file
4
src/ser/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export * from './interface'
|
||||
export * from './mixin'
|
||||
export * from './impl'
|
||||
|
65
src/ser/interface.ts
Normal file
65
src/ser/interface.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { isFunction, Primitive, ToString } from "../utils"
|
||||
|
||||
export interface ObjectSerializer<T = void> {
|
||||
serializeKey<U extends Serializable>(key: U): T
|
||||
serializeValue<U extends Serializable>(value: U): T
|
||||
end(): T
|
||||
}
|
||||
|
||||
export interface IterableSerializer<T = void> {
|
||||
serializeElement<U extends Serializable>(element: U): T
|
||||
end(): T
|
||||
}
|
||||
|
||||
export interface ClassSerializer<T = void> {
|
||||
serializeField<U extends Serializable>(name: PropertyKey, value: U): T
|
||||
end(): T
|
||||
}
|
||||
|
||||
const TypeSerializerMethods = [
|
||||
'serializeString',
|
||||
'serializeNumber',
|
||||
'serializeBigInt',
|
||||
'serializeBoolean',
|
||||
'serializeSymbol',
|
||||
'serializeMap',
|
||||
'serializeIterable',
|
||||
'serializeNull',
|
||||
'serializeObject',
|
||||
'serializeInstance',
|
||||
'serializeFunction'
|
||||
] as const
|
||||
|
||||
interface TypeSerializer<T> {
|
||||
serializeString(value: string): T
|
||||
serializeNumber(value: number): T
|
||||
serializeBigInt(value: bigint): T
|
||||
serializeBoolean(value: boolean): T
|
||||
serializeSymbol(value: Symbol): T
|
||||
serializeNull(): T
|
||||
serializeObject(): ObjectSerializer<T>
|
||||
serializeFunction?(value: Function): T
|
||||
serializeMap?(): ObjectSerializer<T>
|
||||
serializeIterable?(): IterableSerializer<T>
|
||||
serializeClass?(name: PropertyKey): ClassSerializer<T>
|
||||
}
|
||||
|
||||
const AnySerializerMethods = ['serializeAny']
|
||||
|
||||
interface AnySerializer<T> {
|
||||
serializeAny?(value?: any): T
|
||||
}
|
||||
|
||||
export function isGenericSerializer(value: any): boolean {
|
||||
return AnySerializerMethods.every(k => isFunction(value[k])) &&
|
||||
TypeSerializerMethods.every(k => !isFunction(value[k]))
|
||||
}
|
||||
|
||||
export type Serializer<T> = Partial<TypeSerializer<T>> & Partial<AnySerializer<T>>
|
||||
|
||||
export type Serializable = Primitive | ToString | Serialize
|
||||
|
||||
export interface Serialize {
|
||||
serialize<T, S extends Serializer<T>>(serializer: S): T
|
||||
}
|
||||
|
37
src/ser/mixin.ts
Normal file
37
src/ser/mixin.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { CaseConvention, Constructor } from '../utils'
|
||||
import { serializeWith } from './impl'
|
||||
import { isGenericSerializer, Serializer } from './interface'
|
||||
|
||||
export interface SerializationOptions {
|
||||
default?: <T>() => T
|
||||
rename?: string
|
||||
renameAll?: CaseConvention
|
||||
tag?: string
|
||||
untagged?: boolean
|
||||
withInherited?: boolean
|
||||
}
|
||||
|
||||
const DefaultSerializationOptions: SerializationOptions = {
|
||||
withInherited: true
|
||||
}
|
||||
|
||||
export function serialize(options?: SerializationOptions) {
|
||||
options = {
|
||||
...DefaultSerializationOptions,
|
||||
...options
|
||||
}
|
||||
|
||||
return function <T extends Constructor>(constructor: T) {
|
||||
return class Serializable extends constructor implements Serializable {
|
||||
static name = constructor.name
|
||||
serialize<U>(serializer: Serializer<U>): U {
|
||||
// shortcut for serializers with only the serializeAny method
|
||||
if (isGenericSerializer(serializer)) {
|
||||
return serializer.serializeAny!(this) as U
|
||||
} else {
|
||||
return serializeWith(serializer, this) as U
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
33
src/test.ts
Normal file
33
src/test.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { deserialize } from './de'
|
||||
import { fromString, toString } from './json'
|
||||
import { serialize } from './ser'
|
||||
|
||||
const InnerStruct = deserialize()(
|
||||
@serialize()
|
||||
class {
|
||||
c = 'awawa'
|
||||
})
|
||||
|
||||
const TestStruct = deserialize()(
|
||||
@serialize()
|
||||
class {
|
||||
a = 1
|
||||
b
|
||||
inner = new InnerStruct()
|
||||
d = true
|
||||
e = Math.pow(2, 53)
|
||||
f = Symbol('test')
|
||||
g = [1, 'a', [3]]
|
||||
|
||||
constructor() {
|
||||
this.b = new Map()
|
||||
this.b.set('test key', 2)
|
||||
}
|
||||
})
|
||||
|
||||
const test = new TestStruct()
|
||||
const value = toString(test)
|
||||
console.log(value)
|
||||
const test2 = fromString(value, TestStruct)
|
||||
console.log(test2)
|
||||
|
72
src/utils.ts
Normal file
72
src/utils.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
export type Nullable<T> = T | undefined
|
||||
|
||||
export type Primitive = string | number | boolean | symbol | bigint | null | undefined
|
||||
|
||||
export interface ToString {
|
||||
toString(): string
|
||||
}
|
||||
|
||||
export function staticImplements<T>() {
|
||||
return <U extends T>(constructor: U) => { constructor }
|
||||
}
|
||||
|
||||
export function isPlainObject(value: any): boolean {
|
||||
return Object.getPrototypeOf(value) === Object.prototype
|
||||
}
|
||||
|
||||
export function isFunction(value: any): value is Function {
|
||||
return value != null && typeof value === 'function'
|
||||
}
|
||||
|
||||
export function isIterable(value: any): value is Iterable<any> {
|
||||
return isFunction(value[Symbol.iterator])
|
||||
}
|
||||
|
||||
export type Constructor<T = any> = new (...args: any[]) => T
|
||||
|
||||
export const CaseConvention = Object.freeze({
|
||||
Lowercase: 0,
|
||||
Uppercase: 1,
|
||||
PascalCase: 2,
|
||||
CamelCase: 3,
|
||||
SnakeCase: 4,
|
||||
ScreamingSnakeCase: 5,
|
||||
KebabCase: 6,
|
||||
ScreamingKebabCase: 7
|
||||
} as const)
|
||||
|
||||
export type CaseConvention = typeof CaseConvention[keyof typeof CaseConvention]
|
||||
|
||||
export function orElse(thisArg: any, a: Nullable<Function>, b: Function) {
|
||||
return function(...args: any) {
|
||||
const fn = a != null ? a : b
|
||||
return fn.apply(thisArg, args)
|
||||
}
|
||||
}
|
||||
|
||||
export function ifNull(thisArg: any, b: Function, ...args: any) {
|
||||
return function(a: Nullable<Function>) {
|
||||
return orElse(thisArg, a, b).call(thisArg, args)
|
||||
}
|
||||
}
|
||||
|
||||
function applyMixins(derivedCtor: any, constructors: any[]) {
|
||||
constructors.forEach((baseCtor) => {
|
||||
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
|
||||
Object.defineProperty(
|
||||
derivedCtor.prototype,
|
||||
name,
|
||||
Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
|
||||
Object.create(null)
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function mixin<U = any>(impl: Function) {
|
||||
return function <TBase extends Constructor<U>>(constructor: TBase) {
|
||||
applyMixins(constructor, [impl])
|
||||
return constructor
|
||||
}
|
||||
}
|
||||
|
113
tsconfig.json
Normal file
113
tsconfig.json
Normal file
|
@ -0,0 +1,113 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
"lib": ["es2020", "dom"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "libReplacement": true, /* Enable lib replacement. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
"useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./out", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
|
||||
// "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue