refactor(struct): tweak type plugin system

This commit is contained in:
Simon Chan 2021-01-07 14:16:30 +08:00
parent b601f7a7f2
commit 9f3d480523
22 changed files with 862 additions and 530 deletions

View file

@ -24,6 +24,7 @@
"hhmm", "hhmm",
"hisi", "hisi",
"jmuxer", "jmuxer",
"keyof",
"killforward", "killforward",
"laggy", "laggy",
"lapo", "lapo",
@ -46,8 +47,10 @@
"tcpip", "tcpip",
"tinyh", "tinyh",
"tsbuildinfo", "tsbuildinfo",
"typeof",
"uifabric", "uifabric",
"webadb", "webadb",
"webpackbar",
"websockify", "websockify",
"webusb", "webusb",
"wifi", "wifi",

View file

@ -1,5 +1,6 @@
{ {
"name": "@yume-chan/adb-backend-web", "name": "@yume-chan/adb-backend-web",
"private": true,
"version": "0.0.1", "version": "0.0.1",
"description": "Backend for `@yume-chan/adb` using Web technologies.", "description": "Backend for `@yume-chan/adb` using Web technologies.",
"keywords": [ "keywords": [

View file

@ -1,5 +1,6 @@
{ {
"name": "@yume-chan/adb", "name": "@yume-chan/adb",
"private": true,
"version": "0.0.1", "version": "0.0.1",
"description": "TypeScript implementation of Android Debug Bridge (ADB) protocol.", "description": "TypeScript implementation of Android Debug Bridge (ADB) protocol.",
"keywords": [ "keywords": [
@ -34,7 +35,7 @@
"dependencies": { "dependencies": {
"@yume-chan/async-operation-manager": "2.1.3", "@yume-chan/async-operation-manager": "2.1.3",
"@yume-chan/event": "^0.0.1", "@yume-chan/event": "^0.0.1",
"@yume-chan/struct": "^0.0.1", "@yume-chan/struct": "^0.0.2",
"tslib": "2.0.3" "tslib": "2.0.3"
}, },
"devDependencies": { "devDependencies": {

View file

@ -861,6 +861,15 @@
"integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==",
"dev": true "dev": true
}, },
"ansi-escapes": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz",
"integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==",
"dev": true,
"requires": {
"type-fest": "^0.11.0"
}
},
"ansi-html": { "ansi-html": {
"version": "0.0.7", "version": "0.0.7",
"resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",
@ -1232,6 +1241,12 @@
} }
} }
}, },
"ci-info": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
"integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==",
"dev": true
},
"class-utils": { "class-utils": {
"version": "0.3.6", "version": "0.3.6",
"resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
@ -1407,6 +1422,12 @@
"integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==",
"dev": true "dev": true
}, },
"consola": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz",
"integrity": "sha512-vlcSGgdYS26mPf7qNi+dCisbhiyDnrN1zaRbw3CSuc2wGOMEGGPsp46PdRG5gqXwgtJfjxDkxRNAgRPr1B77vQ==",
"dev": true
},
"content-disposition": { "content-disposition": {
"version": "0.5.3", "version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
@ -2249,6 +2270,15 @@
"websocket-driver": ">=0.5.1" "websocket-driver": ">=0.5.1"
} }
}, },
"figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
"integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5"
}
},
"file-loader": { "file-loader": {
"version": "6.2.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
@ -4348,6 +4378,12 @@
} }
} }
}, },
"pretty-time": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz",
"integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==",
"dev": true
},
"process-nextick-args": { "process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@ -5232,6 +5268,15 @@
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
"dev": true "dev": true
}, },
"std-env": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-2.2.1.tgz",
"integrity": "sha512-IjYQUinA3lg5re/YMlwlfhqNRTzMZMqE+pezevdcTaHceqx8ngEi1alX9nNCk9Sc81fy1fLDeQoaCzeiW1yBOQ==",
"dev": true,
"requires": {
"ci-info": "^1.6.0"
}
},
"streamsaver": { "streamsaver": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/streamsaver/-/streamsaver-2.0.5.tgz", "resolved": "https://registry.npmjs.org/streamsaver/-/streamsaver-2.0.5.tgz",
@ -5402,6 +5447,12 @@
} }
} }
}, },
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"thunky": { "thunky": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
@ -5516,6 +5567,12 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
}, },
"type-fest": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz",
"integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==",
"dev": true
},
"type-is": { "type-is": {
"version": "1.6.18", "version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@ -6454,6 +6511,122 @@
"source-map": "~0.6.1" "source-map": "~0.6.1"
} }
}, },
"webpackbar": {
"version": "5.0.0-3",
"resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.0-3.tgz",
"integrity": "sha512-viW6KCYjMb0NPoDrw2jAmLXU2dEOhRrtku28KmOfeE1vxbfwCYuTbTaMhnkrCZLFAFyY9Q49Z/jzYO80Dw5b8g==",
"dev": true,
"requires": {
"ansi-escapes": "^4.3.1",
"chalk": "^4.1.0",
"consola": "^2.15.0",
"figures": "^3.2.0",
"pretty-time": "^1.1.0",
"std-env": "^2.2.1",
"text-table": "^0.2.0",
"wrap-ansi": "^7.0.0"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"dev": true
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
}
}
}
},
"websocket-driver": { "websocket-driver": {
"version": "0.6.5", "version": "0.6.5",
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz",

View file

@ -1,7 +1,7 @@
{ {
"name": "demo", "name": "demo",
"version": "0.0.1",
"private": true, "private": true,
"version": "0.0.1",
"description": "Demo of `@yume-chan/adb` and `@yume-chan/adb-backend-web`.", "description": "Demo of `@yume-chan/adb` and `@yume-chan/adb-backend-web`.",
"author": "Simon Chan <cnsimonchan@live.com>", "author": "Simon Chan <cnsimonchan@live.com>",
"homepage": "https://github.com/yume-chan/ya-webadb#readme", "homepage": "https://github.com/yume-chan/ya-webadb#readme",
@ -37,6 +37,7 @@
"webpack-bundle-analyzer": "4.3.0", "webpack-bundle-analyzer": "4.3.0",
"webpack-cli": "4.2.0", "webpack-cli": "4.2.0",
"webpack-dev-server": "3.11.0", "webpack-dev-server": "3.11.0",
"webpackbar": "5.0.0-3",
"worker-loader": "3.0.7" "worker-loader": "3.0.7"
}, },
"dependencies": { "dependencies": {
@ -47,7 +48,7 @@
"@yume-chan/adb-backend-web": "^0.0.1", "@yume-chan/adb-backend-web": "^0.0.1",
"@yume-chan/async-operation-manager": "2.1.3", "@yume-chan/async-operation-manager": "2.1.3",
"@yume-chan/event": "^0.0.1", "@yume-chan/event": "^0.0.1",
"@yume-chan/struct": "^0.0.1", "@yume-chan/struct": "^0.0.2",
"jmuxer": "2.0.0", "jmuxer": "2.0.0",
"path-browserify": "1.0.1", "path-browserify": "1.0.1",
"react": "17.0.1", "react": "17.0.1",

View file

@ -12,6 +12,18 @@ import { createTinyH264Decoder, TinyH264Decoder } from './tinyh264';
const DeviceServerPath = '/data/local/tmp/scrcpy-server.jar'; const DeviceServerPath = '/data/local/tmp/scrcpy-server.jar';
function clamp(value: number, min: number, max: number): number {
if (value < min) {
return min;
}
if (value > max) {
return max;
}
return value;
}
export const Scrcpy = withDisplayName('Scrcpy')(({ export const Scrcpy = withDisplayName('Scrcpy')(({
device device
}: RouteProps): JSX.Element | null => { }: RouteProps): JSX.Element | null => {
@ -283,14 +295,11 @@ export const Scrcpy = withDisplayName('Scrcpy')(({
action: AndroidMotionEventAction, action: AndroidMotionEventAction,
e: React.PointerEvent<HTMLCanvasElement> e: React.PointerEvent<HTMLCanvasElement>
) => { ) => {
e.preventDefault(); const view = canvasRef.current!.getBoundingClientRect();
e.stopPropagation();
const view = e.currentTarget.getBoundingClientRect();
const pointerViewX = e.clientX - view.x; const pointerViewX = e.clientX - view.x;
const pointerViewY = e.clientY - view.y; const pointerViewY = e.clientY - view.y;
const pointerScreenX = pointerViewX / view.width * width; const pointerScreenX = clamp(pointerViewX / view.width, 0, 1) * width;
const pointerScreenY = pointerViewY / view.height * height; const pointerScreenY = clamp(pointerViewY / view.height, 0, 1) * height;
scrcpyClientRef.current?.injectTouch({ scrcpyClientRef.current?.injectTouch({
action, action,
@ -307,6 +316,7 @@ export const Scrcpy = withDisplayName('Scrcpy')(({
return; return;
} }
canvasRef.current!.focus(); canvasRef.current!.focus();
e.currentTarget.setPointerCapture(e.pointerId);
injectTouch(AndroidMotionEventAction.Down, e); injectTouch(AndroidMotionEventAction.Down, e);
}, [injectTouch]); }, [injectTouch]);
@ -321,6 +331,7 @@ export const Scrcpy = withDisplayName('Scrcpy')(({
if (e.button !== 0) { if (e.button !== 0) {
return; return;
} }
e.currentTarget.releasePointerCapture(e.pointerId);
injectTouch(AndroidMotionEventAction.Up, e); injectTouch(AndroidMotionEventAction.Up, e);
}, [injectTouch]); }, [injectTouch]);
@ -406,6 +417,7 @@ export const Scrcpy = withDisplayName('Scrcpy')(({
onPointerDown={handlePointerDown} onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove} onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp} onPointerUp={handlePointerUp}
onPointerCancel={handlePointerUp}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
/> />
</DeviceView> </DeviceView>

View file

@ -6,9 +6,10 @@ var html_webpack_plugin_1 = tslib_1.__importDefault(require("html-webpack-plugin
var mini_css_extract_plugin_1 = tslib_1.__importDefault(require("mini-css-extract-plugin")); var mini_css_extract_plugin_1 = tslib_1.__importDefault(require("mini-css-extract-plugin"));
var path_1 = tslib_1.__importDefault(require("path")); var path_1 = tslib_1.__importDefault(require("path"));
var webpack_bundle_analyzer_1 = require("webpack-bundle-analyzer"); var webpack_bundle_analyzer_1 = require("webpack-bundle-analyzer");
var webpackbar_1 = tslib_1.__importDefault(require("webpackbar"));
var context = path_1.default.resolve(process.cwd()); var context = path_1.default.resolve(process.cwd());
var plugins = [ var plugins = [
new clean_webpack_plugin_1.CleanWebpackPlugin(), new webpackbar_1.default({}),
new mini_css_extract_plugin_1.default({ new mini_css_extract_plugin_1.default({
filename: '[name].[contenthash].css', filename: '[name].[contenthash].css',
}), }),
@ -29,34 +30,40 @@ var plugins = [
if (process.env.ANALYZE) { if (process.env.ANALYZE) {
plugins.push(new webpack_bundle_analyzer_1.BundleAnalyzerPlugin()); plugins.push(new webpack_bundle_analyzer_1.BundleAnalyzerPlugin());
} }
var config = function (env, argv) { return ({ var config = function (env, argv) {
mode: 'development', if (argv.mode !== 'production') {
devtool: argv.mode === 'production' ? 'source-map' : 'eval-source-map', plugins.unshift(new clean_webpack_plugin_1.CleanWebpackPlugin());
context: context, }
target: 'web', return {
entry: { mode: 'development',
index: './src/index.tsx', devtool: argv.mode === 'production' ? 'source-map' : 'eval-source-map',
}, context: context,
output: { target: 'web',
path: path_1.default.resolve(context, 'lib'), entry: {
filename: '[name].[contenthash].js', index: './src/index.tsx',
}, },
resolve: { output: {
extensions: ['.ts', '.tsx', '.js'], path: path_1.default.resolve(context, 'lib'),
fallback: { "path": require.resolve("path-browserify") }, filename: '[name].[contenthash].js',
}, },
plugins: plugins, resolve: {
module: { extensions: ['.ts', '.tsx', '.js'],
rules: [ // @ts-expect-error typing is not up to date
{ test: /\.js$/, enforce: 'pre', use: ['source-map-loader'], }, fallback: { "path": require.resolve("path-browserify") },
{ test: /\.css$/i, use: [mini_css_extract_plugin_1.default.loader, 'css-loader'] }, },
{ test: /\.asset$/, use: { loader: "file-loader" } }, plugins: plugins,
{ test: /\.tsx?$/i, loader: 'ts-loader', options: { projectReferences: true } }, module: {
], rules: [
}, { test: /\.js$/, enforce: 'pre', use: ['source-map-loader'], },
devServer: { { test: /\.css$/i, use: [mini_css_extract_plugin_1.default.loader, 'css-loader'] },
contentBase: path_1.default.resolve(context, 'lib'), { test: /\.asset$/, use: { loader: "file-loader" } },
port: 9000, { test: /\.tsx?$/i, loader: 'ts-loader', options: { projectReferences: true } },
}, ],
}); }; },
devServer: {
contentBase: path_1.default.resolve(context, 'lib'),
port: 9000,
},
};
};
module.exports = config; module.exports = config;

View file

@ -1,5 +1,6 @@
{ {
"name": "@yume-chan/event", "name": "@yume-chan/event",
"private": true,
"version": "0.0.1", "version": "0.0.1",
"description": "Event/EventEmitter", "description": "Event/EventEmitter",
"keywords": [ "keywords": [

View file

@ -4,30 +4,42 @@ C-style structure serializer and deserializer.
Fully compatible with TypeScript. Fully compatible with TypeScript.
- [Compatibility](#compatibility)
- [Quick Start](#quick-start) - [Quick Start](#quick-start)
- [Compatibility](#compatibility)
- [API](#api) - [API](#api)
- [`placeholder` method](#placeholder-method) - [`placeholder` method](#placeholder-method)
- [`Struct` constructor](#struct-constructor) - [`Struct` constructor](#struct-constructor)
- [`Struct#uint8`/`uint16`/`uint32`/`int32` methods](#structuint8uint16uint32int32-methods) - [`Struct#fields` method](#structfields-method)
- [`Struct#uint64`/`int64` method](#structuint64int64-method) - [`Struct#uint8`/`uint16`/`int32`/`uint32` methods](#structuint8uint16int32uint32-methods)
- [`Struct#int64`/`uint64` methods](#structint64uint64-methods)
- [`extra` function](#extra-function) - [`extra` function](#extra-function)
- [`afterParsed` method](#afterparsed-method) - [`postDeserialize` method](#postdeserialize-method)
- [`deserialize` method](#deserialize-method) - [`deserialize` method](#deserialize-method)
- [`serialize` method](#serialize-method) - [`serialize` method](#serialize-method)
- [Extend types](#extend-types) - [Custom types](#custom-types)
- [Backing Field](#backing-field) - [`Struct#field` method](#structfield-method)
- [`FieldDescriptorBase` interface](#fielddescriptorbase-interface) - [FieldDefinition](#fielddefinition)
- [`field` method](#field-method) - [`FieldDefinition#getSize` method](#fielddefinitiongetsize-method)
- [`FieldTypeDefinition` interface](#fieldtypedefinition-interface) - [`FieldDefinition#deserialize` method](#fielddefinitiondeserialize-method)
- [`deserialize` method](#deserialize-method-1) - [`FieldDefinition#createValue` method](#fielddefinitioncreatevalue-method)
- [`getSize` method](#getsize-method) - [FieldRuntimeValue](#fieldruntimevalue)
- [`getDynamicSize` method](#getdynamicsize-method)
- [`initialize` method](#initialize-method) ## Quick Start
- [`serialize` method](#serialize-method-1)
- [Array type](#array-type) ```ts
- [`registerFieldTypeDefinition` method](#registerfieldtypedefinition-method) import Struct from '@yume-chan/struct';
- [Data flow](#data-flow)
const MyStruct =
new Struct({ littleEndian: true })
.int32('foo')
.int32('bar');
const value = await MyStruct.deserialize(someStream);
// TypeScript can infer type of the result object.
const { foo, bar } = value;
const buffer = MyStruct.serialize({ foo, bar });
```
## Compatibility ## Compatibility
@ -47,7 +59,7 @@ Basic usage requires [`Promise`][MDN_Promise], [`ArrayBuffer`][MDN_ArrayBuffer],
| **Safari** | 8 | | | **Safari** | 8 | |
| **Node.js** | 0.12 | | | **Node.js** | 0.12 | |
Usage of `uint64` requires [`BigInt`][MDN_BigInt] (**can't** be polyfilled), [`DataView#getBigUint64`][MDN_DataView_getBigUint64] and [`DataView#setBigUint64`][MDN_DataView_setBigUint64] (can be polyfilled). Usage of `int64`/`uint64` requires [`BigInt`][MDN_BigInt] (**can't** be polyfilled), [`DataView#getBigUint64`][MDN_DataView_getBigUint64] and [`DataView#setBigUint64`][MDN_DataView_setBigUint64] (can be polyfilled).
[MDN_BigInt]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt [MDN_BigInt]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
[MDN_DataView_getBigUint64]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigUint64 [MDN_DataView_getBigUint64]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigUint64
@ -62,21 +74,6 @@ Usage of `uint64` requires [`BigInt`][MDN_BigInt] (**can't** be polyfilled), [`D
| **Safari** | 14 | Requires polyfills for `DataView#getBigUint64`/`DataView#setBigUint64` | | **Safari** | 14 | Requires polyfills for `DataView#getBigUint64`/`DataView#setBigUint64` |
| **Node.js** | 10.4.0 | | | **Node.js** | 10.4.0 | |
## Quick Start
```ts
import Struct from '@yume-chan/struct';
const MyStruct =
new Struct({ littleEndian: true })
.int32('foo')
.int32('bar');
const value = MyStruct.deserialize(someStream);
// TypeScript can infer type of the result object.
const { foo, bar } = value;
```
## API ## API
### `placeholder` method ### `placeholder` method
@ -89,20 +86,18 @@ export function placeholder<T>(): T {
Return a (fake) value of the given type. Return a (fake) value of the given type.
Because TypeScript only supports supply all or none type arguments, this library allows all type parameters to be inferred from arguments. Because TypeScript only supports supply all or none type arguments, this method allows all type parameters to be inferred from arguments.
This method can be used where an argument is only used to infer a type parameter.
**While all following APIs heavily rely on generic, DO NOT PASS ANY GENERIC ARGUMENTS MANUALLY!** **While all following APIs heavily rely on generic, DO NOT PASS ANY GENERIC ARGUMENTS MANUALLY!**
### `Struct` constructor ### `Struct` constructor
```ts ```ts
export default class Struct< class Struct<
TResult extends object = {}, TValue extends object = {},
TInit extends object = {}, TInit extends object = {},
TExtra extends object = {}, TExtra extends object = {},
TAfterParsed = undefined, TPostDeserialized = undefined
> { > {
public constructor(options: Partial<StructOptions> = StructDefaultOptions); public constructor(options: Partial<StructOptions> = StructDefaultOptions);
} }
@ -112,10 +107,10 @@ Creates a new structure definition.
**Generic Parameters** **Generic Parameters**
1. `TResult`: Type of the result object. 1. `TValue`: Type of the Struct instance.
2. `TInit`: Type requirement to create such a structure. (Because some fields may implies other fields) 2. `TInit`: Type requirement to create such a structure. (May not be same as `TValue` because some fields can implies others)
3. `TExtra`: Type of extra fields. 3. `TExtra`: Type of extra fields.
4. `TAfterParsed`: State of the `afterParsed` function. 4. `TPostDeserialized`: State of the `postDeserialize` function.
**DO NOT PASS ANY GENERIC ARGUMENTS MANUALLY!** **DO NOT PASS ANY GENERIC ARGUMENTS MANUALLY!**
@ -124,52 +119,90 @@ These are considered "internal state" of the `Struct` and will be taken care of
**Parameters** **Parameters**
1. `options`: 1. `options`:
* `littleEndian:boolean = false`: Whether all multi-byte fields are [little-endian encoded][Wikipeida_Endianess]. * `littleEndian:boolean = false`: Whether all multi-byte fields in this struct are [little-endian encoded][Wikipeida_Endianess].
[Wikipeida_Endianess]: https://en.wikipedia.org/wiki/Endianness [Wikipeida_Endianess]: https://en.wikipedia.org/wiki/Endianness
### `Struct#uint8`/`uint16`/`uint32`/`int32` methods ### `Struct#fields` method
```ts ```ts
public int32< fields<
TName extends string, TOther extends Struct<any, any, any, any>
TTypeScriptType = number
>( >(
name: TName, struct: TOther
options: FieldDescriptorBaseOptions = {},
_typescriptType?: TTypeScriptType,
): Struct< ): Struct<
TResult & Record<TName, TTypeScriptType>, TValue & TOther['valueType'],
TInit & Record<TName, TTypeScriptType>, TInit & TOther['initType'],
TExtra, TExtra & TOther['extraType'],
TAfterParsed, TPostDeserialized
>;
public uint32<
TName extends string,
TTypeScriptType = number
>(
name: TName,
options: {} = {},
_typescriptType?: TTypeScriptType,
): Struct<
TResult & Record<TName, TTypeScriptType>,
TInit & Record<TName, TTypeScriptType>,
TExtra,
TAfterParsed,
>; >;
``` ```
Return a new `Struct` instance with an `int32`/`uint32` field appended to the end. Merges (flats) another `Struct`'s fields and extra fields into this one.
The original `Struct` instance will not be changed. **Examples**
TypeScript will also append a `name: TTypeScriptType` field into the result object and the init object. 1. Extending another `Struct`
```ts
const MyStructV1 =
new Struct()
.int32('field1');
const MyStructV2 =
new Struct()
.fields(MyStructV1)
.int32('field2');
const structV2 = await MyStructV2.deserialize(context);
structV2.field1; // number
structV2.field2; // number
// Same result, but serialize/deserialize order is reversed
```
2. Also possible in any order
```ts
const MyStructV1 =
new Struct()
.int32('field1');
const MyStructV2 =
new Struct()
.int32('field2')
.fields(MyStructV1);
const structV2 = await MyStructV2.deserialize(context);
structV2.field1; // number
structV2.field2; // number
// Fields are flatten
```
### `Struct#uint8`/`uint16`/`int32`/`uint32` methods
```ts
int32<
TName extends PropertyKey,
TTypeScriptType = number
>(
name: TName,
_typescriptType?: TTypeScriptType
): Struct<
Evaluate<TValue & Record<TName, TTypeScriptType>>,
Evaluate<TInit & Record<TName, TTypeScriptType>>,
TExtra,
TPostDeserialized
>;
```
(All method signatures are same)
Appends an `uint8`/`uint16`/`int32`/`uint32` field to the `Struct`
**Generic Parameters** **Generic Parameters**
1. `TName`: Literal type of the field's name. 1. `TName`: Literal type of the field's name.
2. `TTypeScriptType = number`: Type of the field in the result object. 2. `TTypeScriptType = number`: Type of the field in the result object. For example you can declare it as a number literal type, or some enum type.
**DO NOT PASS ANY GENERIC ARGUMENTS MANUALLY!** **DO NOT PASS ANY GENERIC ARGUMENTS MANUALLY!**
@ -178,7 +211,6 @@ TypeScript will infer them from arguments. See examples below.
**Parameters** **Parameters**
1. `name`: (Required) Field name. Should be a string literal to make types work. 1. `name`: (Required) Field name. Should be a string literal to make types work.
1. `options`: currently unused.
2. `_typescriptType`: Set field's type. See examples below. 2. `_typescriptType`: Set field's type. See examples below.
**Note** **Note**
@ -226,67 +258,62 @@ But obviously, it's a bad idea.
struct.create({ foo: MyEnum.a, bar: MyEnum.b }); // ok struct.create({ foo: MyEnum.a, bar: MyEnum.b }); // ok
``` ```
3. Create a new struct by extending existing one ### `Struct#int64`/`uint64` methods
```ts
const struct1 = new Struct()
.int32('foo');
const struct2 = struct1
.int32('bar');
assert(struct2 !== struct1);
// `struct1` will not be changed
```
### `Struct#uint64`/`int64` method
```ts ```ts
public uint64< int64<
TName extends string, TName extends PropertyKey,
TTypeScriptType = bigint TTypeScriptType = bigint
>( >(
name: TName, name: TName,
options: FieldDescriptorBaseOptions = {}, _typescriptType?: TTypeScriptType
_typescriptType?: TTypeScriptType,
): Struct< ): Struct<
TResult & Record<TName, TTypeScriptType>, Evaluate<TValue & Record<TName, TTypeScriptType>>,
TInit & Record<TName, TTypeScriptType>, Evaluate<TInit & Record<TName, TTypeScriptType>>,
TExtra, TExtra,
TAfterParsed, TPostDeserialized
>; >;
``` ```
Return a new `Struct` instance with an `uint64` field appended to the end. Appends an `int64`/`uint64` field to the `Struct`.
The original `Struct` instance will not be changed. Requires native `BigInt` support of runtime. See [compatibility](#compatibility).
TypeScript will also append a `name: TTypeScriptType` field into the result object and the init object.
Require native `BigInt` support in runtime. See [compatibility](#compatibility).
### `extra` function ### `extra` function
```ts ```ts
public extra<TValue extends object>( extra<
value: TValue & ThisType<WithBackingField<Overwrite<Overwrite<TExtra, TValue>, TResult>>> T extends Record<
Exclude<
keyof T,
Exclude<
keyof T,
keyof TValue
>
>,
never
>
>(
value: T & ThisType<Overwrite<Overwrite<TExtra, T>, TValue>>
): Struct< ): Struct<
TResult, TValue,
TInit, TInit,
Overwrite<TExtra, TValue>, Overwrite<TExtra, T>,
TAfterParsed TPostDeserialized
>; >;
``` ```
Return a new `Struct` instance adding some extra fields. Adds some extra fields into every Struct instance.
The original `Struct` instance will not be changed. Extra fields will not affect serialize or deserialize process.
TypeScript will also append all extra fields into the result object (if not already exited). Multiple calls to `extra` will merge all values together.
See examples below.
**Generic Parameters** **Generic Parameters**
1. `TValue`: Type of the extra fields. 1. `T`: Type of the extra fields. The scary looking generic constraint is used to forbid overwriting any already existed fields.
**DO NOT PASS ANY GENERIC ARGUMENTS MANUALLY!** **DO NOT PASS ANY GENERIC ARGUMENTS MANUALLY!**
@ -299,8 +326,6 @@ TypeScript will infer them from arguments. See examples below.
**Note** **Note**
1. If the current `Struct` already has some extra fields, it will be merged with `value`, with `value` taking precedence. 1. If the current `Struct` already has some extra fields, it will be merged with `value`, with `value` taking precedence.
2. Extra fields will not be serialized.
3. Extra fields will be ignored if it has the same name with some defined fields.
**Examples** **Examples**
@ -343,31 +368,30 @@ TypeScript will infer them from arguments. See examples below.
value.logBar(); value.logBar();
``` ```
### `afterParsed` method ### `postDeserialize` method
```ts ```ts
public afterParsed( postDeserialize(callback: StructPostDeserialized<TValue, never>): Struct<TValue, TInit, TExtra, never>;
callback?: StructAfterParsed<TResult, void> postDeserialize(callback?: StructPostDeserialized<TValue, void>): Struct<TValue, TInit, TExtra, undefined>;
): Struct<TResult, TInit, TExtra, undefined>;
``` ```
Return a new `Struct` instance, registering (or replacing) a custom callback to be run after deserialized. Registers (or replaces) a custom callback to be run after deserialized.
The original `Struct` instance will not be changed. A callback returning `never` (always throw an error) will also change the return type of `deserialize` to `never`.
A callback returning `void` means it modify the result object in-place (or doesn't modify it at all), so `deserialize` will still return the result object.
```ts ```ts
public afterParsed<TAfterParsed>( postDeserialize<TPostSerialize>(callback?: StructPostDeserialized<TValue, TPostSerialize>): Struct<TValue, TInit, TExtra, TPostSerialize>;
callback?: StructAfterParsed<TResult, TAfterParsed>
): Struct<TResult, TInit, TExtra, TAfterParsed>;
``` ```
Return a new `Struct` instance, registering (or replacing) a custom callback to be run after deserialized, and replacing the result object with the returned value. Registers (or replaces) a custom callback to be run after deserialized.
The original `Struct` instance will not be changed. A callback returning anything other than `undefined` will `deserialize` to return that object instead.
**Generic Parameters** **Generic Parameters**
1. `TAfterParsed`: Type of the new result object. 1. `TPostSerialize`: Type of the new result object.
**DO NOT PASS ANY GENERIC ARGUMENTS MANUALLY!** **DO NOT PASS ANY GENERIC ARGUMENTS MANUALLY!**
@ -389,7 +413,7 @@ TypeScript will infer them from arguments. See examples below.
const struct = new Struct() const struct = new Struct()
.int32('messageLength') .int32('messageLength')
.string('message', { lengthField: 'messageLength' }) .string('message', { lengthField: 'messageLength' })
.afterParsed(value => { .postDeserialize(value => {
throw new Error(value.message); throw new Error(value.message);
}); });
``` ```
@ -400,28 +424,12 @@ TypeScript will infer them from arguments. See examples below.
// I think this one doesn't need any code example // I think this one doesn't need any code example
``` ```
3. Clear a previously set `afterParsed` callback 3. Replace result object
```ts
// Most used with extending structures
const struct1 = new Struct()
.int32('foo')
.afterParsed(value => {
// do something
});
const struct2 = struct1
.afterParsed() // don't inherit `struct1`'s `afterParsed`
.int32('bar');
```
4. Replace result object
```ts ```ts
const struct1 = new Struct() const struct1 = new Struct()
.int32('foo') .int32('foo')
.afterParsed(value => { .postDeserialize(value => {
return { return {
bar: value.foo, bar: value.foo,
}; };
@ -434,14 +442,12 @@ TypeScript will infer them from arguments. See examples below.
### `deserialize` method ### `deserialize` method
```ts ```ts
public async deserialize( deserialize(context: StructDeserializationContext): Promise<TPostDeserialized extends undefined ? Overwrite<TExtra, TValue> : TPostDeserialized>;
context: StructDeserializationContext
): Promise<TAfterParsed extends undefined ? Overwrite<TExtra, TResult> : TAfterParsed>;
``` ```
Deserialize one structure from the `context`. Deserialize a Struct instance from `context`.
As you can see, if your `afterParsed` callback returns a value, that value will be returned by `deserialize`. Or the result object will be returned. As you can see, if your `postDeserialize` callback returns something, that value will be returned by `deserialize`.
### `serialize` method ### `serialize` method
@ -449,214 +455,97 @@ As you can see, if your `afterParsed` callback returns a value, that value will
public serialize(init: TInit, context: StructSerializationContext): ArrayBuffer; public serialize(init: TInit, context: StructSerializationContext): ArrayBuffer;
``` ```
Serialize a value as the structure. Serialize a Struct instance into an `ArrayBuffer`.
## Extend types ## Custom types
The library also supports adding custom types. It also supports adding custom types.
There are two concepts around the type plugin system. ### `Struct#field` method
### Backing Field
The result object has a hidden backing field, containing implementation details of each field.
```ts ```ts
import { getBackingField, setBackingField } from '@yume-chan/struct'; field<
TName extends PropertyKey,
const value = getBackingField<number>(resultObject, 'foo'); TDefinition extends FieldDefinition<any, any, any>
setBackingField(resultObject, 'foo', value); >(
name: TName,
definition: TDefinition
): Struct<
Evaluate<TValue & Record<TFieldName, TDefinition['valueType']>>,
Evaluate<Omit<TInit, TDefinition['removeFields']> & Record<TFieldName, TDefinition['valueType']>>,
TExtra,
TPostDeserialized
>;
``` ```
It's possible to access other fields' data if you know the type. But it's not recommended to modify them. Appends a `FieldDefinition` to the `Struct.
### `FieldDescriptorBase` interface All above built-in methods are all alias to this method.
This interface describes one field, and will be stored in `Struct` class. ### FieldDefinition
**Generic Parameters**
* `TName extends string = string`: Name of the field. Although `FieldDescriptorBase` doesn't need it to be generic, derived types will need it. So marking this way helps TypeScript infer the type.
* `TResultObject = {}`: Type that will be merged into the result object (`TResult`). Any key that has `never` type will be removed.
* `TInitObject = {}`: Type that will be merged into the init object (`TInit`). Any key that has `never` type will be removed. Normally you only need to add the current field into `TInit`, but sometimes one field will imply other fields, so you may want to also remove those implied fields from `TInit`.
* `TOptions extends FieldDescriptorBaseOptions = FieldDescriptorBaseOptions`: Type of the `options`. currently `FieldDescriptorBaseOptions` is empty but maybe something will happen later.
When declaring your own field descriptor, you need to extend `FieldDescriptorBase`, and correctly pass all generic arguments.
**Fields**
* `type: string`: The unique identifier of the type.
* `name: TName`: Field name in the result object.
* `options: TOptions`: You can store your options here.
* `resultObject?: TResultObject`: Make it possible for TypeScript to infer `TResultObject`. DO NOT TOUCH.
* `initObject?: TInitObject`: Make it possible for TypeScript to infer `TInitObject`. DO NOT TOUCH.
When declaring your own field descriptor, you can also add more fields to hold your data.
### `field` method
`Struct` class also has a `field` method to add a custom field descriptor.
Due to the limitation of TypeScript, you can't extend `Struct` class while keeping the fluent style API working.
So for type safety you should provide a function to generate your own field descriptor, then let the user call the `field` method.
### `FieldTypeDefinition` interface
This interface defines how to serialize and deserialize a type. You need to implement this interface for your type and register it.
**Generic Parameters**
* `TDescriptor extends FieldDescriptorBase = FieldDescriptorBase`: Type of the field descriptor. Just pass in your own field descriptor type.
**Fields**
* `type: string`: The unique identifier of the type. Make sure it's same as in `FieldDescriptor`.
#### `deserialize` method
```ts ```ts
deserialize(options: { abstract class FieldDefinition<TOptions = void, TValueType = unknown, TRemoveFields = never> {
context: StructDeserializationContext; readonly options: TOptions;
field: TDescriptor;
object: any;
options: StructOptions;
}): Promise<void>;
```
Defines how to deserialize the field. constructor(options: TOptions);
You should `read` data from the `context` according to your `field` descriptor, and set appropriate values onto `object` ("appropriate" means "same as `TDescriptor`'s `TResultObject`").
If you also defined `initialize` method, the result data shape of `object` should be same as the result of `initialize`.
#### `getSize` method
```ts
getSize(options: {
field: TDescriptor;
options: StructOptions;
}): number;
```
Get the size (in bytes) of the field.
If the size is (partially or fully) dynamic, returns the minimal size.
It's just a hint for how much data should be ready before parsing, not that important.
#### `getDynamicSize` method
```ts
getDynamicSize?(options: {
context: StructSerializationContext,
field: TDescriptor,
object: any,
options: StructOptions,
}): number;
```
Similar to `getSize`, but also has access to `object` and `context` so the actual size can be calculated.
This method will be called just before `serialize`, so you can also prepare your field to be serialized in it.
You can also modify `object` to store your lazily evaluated values so next serialization can reuse them. But make sure you have also defined a setter in `initialize` method to invalidate the cache.
#### `initialize` method
```ts
initialize?(options: {
context: StructSerializationContext;
field: TDescriptor;
init: any;
object: any;
options: StructOptions;
}): void;
```
When creating or serializing an object, you can fine tune how to map fields from `init` object onto the result `object`.
You can modify the `object` as your wish, but a common practice is storing actual data on the backing field and define getter/setter on `object` to access them. Because fields may be overwritten by `extra` fields, where data on the backing field is still useful.
```ts
initialize({ field, init, object }) {
object[BackingField][field.name] = {
value: init[field.name],
...extraData,
};
Object.defineProperty(object, field.name, {
configurable: true,
enumerable: true,
get() { return object[BackingField][field.name].value; }
set(value) {
object[BackingField][field.name].value = value;
// set some other data
}
});
} }
``` ```
If omitted, value from `init` will be set into the backing field and a pair of simple getter/setter will be defined on `object`. A `FieldDefinition` defines its type, size, etc.
Some possible usages: It's an `abstract` class, means it lacks some method implementations, so it shouldn't be constructed.
1. Do some calculations and then set it onto `object`. To create a custom type, one should create its own derived classes of `FieldDefinition` and `FieldRuntimeValue`.
2. Define getter/setter onto `object` to intercept read/write.
3. Maybe one field implies others, so you can define multiple fields onto `object` for a single `field`.
#### `serialize` method The custom `FieldDefinition` then can be passed to `Struct#field` method to append such a custom type field.
### `FieldDefinition#getSize` method
```ts ```ts
serialize(options: { abstract getSize(): number;
context: StructSerializationContext;
dataView: DataView;
field: TDescriptor;
object: any;
offset: number;
options: StructOptions;
}): void;
``` ```
Defines how to serialize the field. Returns the size (or minimal size if it's dynamic) of this field.
You should serialize your `field`'s value on `object`, and write it to `dataView` at `offset`. Actual size should been returned from `FieldRuntimeValue#getSize`
You must not write more data than `getSize`/`getDynamicSize` returned. Or an Error will be thrown. ### `FieldDefinition#deserialize` method
### Array type ```ts
abstract deserialize(
options: Readonly<StructOptions>,
context: StructDeserializationContext,
object: any,
): ValueOrPromise<FieldRuntimeValue<FieldDefinition<TOptions, TValueType, TRemoveInitFields>>>;
```
Instead of true "Array", the current array types (`arraybuffer` and `string`) are more like buffers. Defines how to deserialize a value from `context`. Can also return a `Promise`.
### `registerFieldTypeDefinition` method Usually implementations should be:
This library exports the `registerFieldTypeDefinition` method to register your custom type definitions. 1. Some how parse the value from `context`
2. Pass the value into `FieldDefinition#createValue`
Pass the `undefined as unknown as YourTypeDescriptor` as the first argument to make TypeScript infers the type. Sometimes, some metadata is present when deserializing, but need to be calculated when serializing, for example a UTF-8 encoded string may have different length between itself (character count) and serialized form (byte length). So `deserialize` and save those metadata on the `FieldRuntimeValue` instance.
### Data flow ### `FieldDefinition#createValue` method
Here are lists of methods calling order to help you understand how this library works. ```ts
abstract createValue(
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any,
value: TValueType,
): FieldRuntimeValue<FieldDefinition<TOptions, TValueType, TRemoveInitFields>>;
```
| Method | Description | Similar to `deserialize`, creates a `FieldRuntimeValue` for this instance.
| ----------------------------- | -------------------------- |
| `Struct#field` | Add a field descriptor |
| `FieldTypeDefinition#getSize` | Add up struct's total size |
| Method | Description | The difference is `createValue` will be called when a init value was provided to create a Struct instance.
| --------------------------------- | ------------------------------------ |
| `Struct#deserialize` | Start deserializing from a `context` |
| `FieldTypeDefinition#deserialize` | Deserialize each field |
| Method | Description | ### FieldRuntimeValue
| -------------------------------- | ---------------------------------------------------- |
| `Struct#create` | Validate and create a value of the current structure |
| `FieldTypeDefinition#initialize` | Initialize each field |
| Method | Description | One `FieldDefinition` instance represents one field declaration, and one `FieldRuntimeValue` instance represents one value.
| ------------------------------------ | ---------------------------------------------------- |
| `Struct#serialize` | Serialize a value into a buffer | It defines how to get, set, and serialize a value.
| `Struct#create` | Validate and create a value of the current structure |
| `FieldTypeDefinition#initialize` | Initialize each field |
| `FieldTypeDefinition#getDynamicSize` | Get actual sizes of each field |
| `FieldTypeDefinition#serialize` | Write each field into the allocated buffer |

View file

@ -28,7 +28,7 @@ export interface StructDeserializationContext extends StructSerializationContext
export interface StructOptions { export interface StructOptions {
/** /**
* Whether multi-byte fields in this struct are in little endian * Whether all multi-byte fields in this struct are little-endian encoded.
* *
* Default to `false` * Default to `false`
*/ */

View file

@ -1,6 +1,8 @@
import type { StructOptions, StructSerializationContext } from './context'; import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
import type { FieldRuntimeValue } from './runtime-value'; import type { FieldRuntimeValue } from './runtime-value';
type ValueOrPromise<T> = T | Promise<T>;
/** /**
* A field definition is a bridge between its type and its runtime value. * A field definition is a bridge between its type and its runtime value.
* *
@ -14,44 +16,54 @@ import type { FieldRuntimeValue } from './runtime-value';
* *
* @template TOptions TypeScript type of this definition's `options`. * @template TOptions TypeScript type of this definition's `options`.
* @template TValueType TypeScript type of this field. * @template TValueType TypeScript type of this field.
* @template TRemoveFields Optional remove keys from current `Struct`. Should be a union of string literal types. * @template TRemoveInitFields Optional remove some field from the `TInit` type. Should be a union of string literal types.
*/ */
export abstract class FieldDefinition< export abstract class FieldDefinition<
TOptions = void, TOptions = void,
TValueType = unknown, TValueType = unknown,
TRemoveFields = never, TRemoveInitFields = never,
> { > {
public readonly options: TOptions; public readonly options: TOptions;
/** /**
* When `T` is a type initiated `FieldDefinition`, * When `T` is a type initiated `FieldDefinition`,
* use `T['valueType']` to retrieve its `TValueType` type parameter * use `T['valueType']` to retrieve its `TValueType` type parameter.
*/ */
public readonly valueType!: TValueType; public readonly valueType!: TValueType;
/** /**
* When `T` is a type initiated `FieldDefinition`, * When `T` is a type initiated `FieldDefinition`,
* use `T['removeFields']` to retrieve its `TRemoveFields` type parameter . * use `T['removeInitFields']` to retrieve its `TRemoveInitFields` type parameter.
*/ */
public readonly removeFields!: TRemoveFields; public readonly removeInitFields!: TRemoveInitFields;
public constructor(options: TOptions) { public constructor(options: TOptions) {
this.options = options; this.options = options;
} }
/** /**
* When implemented in derived classes, returns the static size (or smallest size) of this field. * When implemented in derived classes, returns the size (or minimal size if it's dynamic) of this field.
* *
* Actual size can be retrieved from `FieldRuntimeValue#getSize` * Actual size can be retrieved from `FieldRuntimeValue#getSize`
*/ */
public abstract getSize(): number; public abstract getSize(): number;
/** /**
* When implemented in derived classes, creates a `FieldRuntimeValue` for the current field definition. * When implemented in derived classes, creates a `FieldRuntimeValue` by parsing the `context`.
*/
public abstract deserialize(
options: Readonly<StructOptions>,
context: StructDeserializationContext,
object: any,
): ValueOrPromise<FieldRuntimeValue<FieldDefinition<TOptions, TValueType, TRemoveInitFields>>>;
/**
* When implemented in derived classes, creates a `FieldRuntimeValue` from a given `value`.
*/ */
public abstract createValue( public abstract createValue(
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructSerializationContext, context: StructSerializationContext,
object: any object: any,
): FieldRuntimeValue; value: TValueType,
): FieldRuntimeValue<FieldDefinition<TOptions, TValueType, TRemoveInitFields>>;
} }

View file

@ -1,4 +1,4 @@
import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context'; import type { StructOptions, StructSerializationContext } from './context';
import type { FieldDefinition } from './definition'; import type { FieldDefinition } from './definition';
/** /**
@ -22,23 +22,22 @@ export abstract class FieldRuntimeValue<
/** Gets the associated `Struct` instance */ /** Gets the associated `Struct` instance */
public readonly object: any; public readonly object: any;
protected value: TDefinition['valueType'];
public constructor( public constructor(
definition: TDefinition, definition: TDefinition,
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructSerializationContext, context: StructSerializationContext,
object: any, object: any,
value: TDefinition['valueType'],
) { ) {
this.definition = definition; this.definition = definition;
this.options = options; this.options = options;
this.context = context; this.context = context;
this.object = object; this.object = object;
this.value = value;
} }
/** When implemented in derived classes, deserialize this field from the specified `context` */
public abstract deserialize(
context: StructDeserializationContext
): void | Promise<void>;
/** /**
* Gets the actual size of this field. By default, the return value of its `definition.getSize()` * Gets the actual size of this field. By default, the return value of its `definition.getSize()`
* *
@ -51,12 +50,16 @@ export abstract class FieldRuntimeValue<
/** /**
* When implemented in derived classes, returns the current value of this field * When implemented in derived classes, returns the current value of this field
*/ */
public abstract get(): unknown; public get(): TDefinition['valueType'] {
return this.value;
}
/** /**
* When implemented in derived classes, update the current value of this field * When implemented in derived classes, update the current value of this field
*/ */
public abstract set(value: unknown): void; public set(value: TDefinition['valueType']): void {
this.value = value;
}
/** /**
* When implemented in derived classes, serializes this field into `dataView` at `offset` * When implemented in derived classes, serializes this field into `dataView` at `offset`

View file

@ -31,7 +31,7 @@ type AddFieldDescriptor<
Evaluate<TValue & Record<TFieldName, TDefinition['valueType']>>, Evaluate<TValue & Record<TFieldName, TDefinition['valueType']>>,
// There is no `Evaluate` here, because otherwise the type of a `Struct` with many fields // There is no `Evaluate` here, because otherwise the type of a `Struct` with many fields
// can become too complex for TypeScript to compute // can become too complex for TypeScript to compute
Evaluate<Omit<TInit, TDefinition['removeFields']> & Record<TFieldName, TDefinition['valueType']>>, Evaluate<Omit<TInit, TDefinition['removeInitFields']> & Record<TFieldName, TDefinition['valueType']>>,
TExtra, TExtra,
TPostDeserialized TPostDeserialized
>>; >>;
@ -171,7 +171,7 @@ export default class Struct<
private _size = 0; private _size = 0;
/** /**
* Get the static size (exclude fields that can change size at runtime) * Gets the static size (exclude fields that can change size at runtime)
*/ */
public get size() { return this._size; } public get size() { return this._size; }
@ -185,6 +185,9 @@ export default class Struct<
this.options = { ...StructDefaultOptions, ...options }; this.options = { ...StructDefaultOptions, ...options };
} }
/**
* Appends a `FieldDefinition` to the `Struct
*/
public field< public field<
TName extends PropertyKey, TName extends PropertyKey,
TDefinition extends FieldDefinition<any, any, any> TDefinition extends FieldDefinition<any, any, any>
@ -208,6 +211,9 @@ export default class Struct<
return this as any; return this as any;
} }
/**
* Merges (flats) another `Struct`'s fields and extra fields into this one.
*/
public fields<TOther extends Struct<any, any, any, any>>( public fields<TOther extends Struct<any, any, any, any>>(
struct: TOther struct: TOther
): Struct< ): Struct<
@ -239,6 +245,9 @@ export default class Struct<
); );
} }
/**
* Appends an `uint8` field to the `Struct`
*/
public uint8< public uint8<
TName extends PropertyKey, TName extends PropertyKey,
TTypeScriptType = (typeof NumberFieldType)['Uint8']['valueType'] TTypeScriptType = (typeof NumberFieldType)['Uint8']['valueType']
@ -253,6 +262,9 @@ export default class Struct<
); );
} }
/**
* Appends an `uint16` field to the `Struct`
*/
public uint16< public uint16<
TName extends PropertyKey, TName extends PropertyKey,
TTypeScriptType = (typeof NumberFieldType)['Uint16']['valueType'] TTypeScriptType = (typeof NumberFieldType)['Uint16']['valueType']
@ -267,6 +279,9 @@ export default class Struct<
); );
} }
/**
* Appends an `int32` field to the `Struct`
*/
public int32< public int32<
TName extends PropertyKey, TName extends PropertyKey,
TTypeScriptType = (typeof NumberFieldType)['Int32']['valueType'] TTypeScriptType = (typeof NumberFieldType)['Int32']['valueType']
@ -281,6 +296,9 @@ export default class Struct<
); );
} }
/**
* Appends an `uint32` field to the `Struct`
*/
public uint32< public uint32<
TName extends PropertyKey, TName extends PropertyKey,
TTypeScriptType = (typeof NumberFieldType)['Uint32']['valueType'] TTypeScriptType = (typeof NumberFieldType)['Uint32']['valueType']
@ -295,6 +313,11 @@ export default class Struct<
); );
} }
/**
* Appends an `int64` field to the `Struct`
*
* Requires native `BigInt` support
*/
public int64< public int64<
TName extends PropertyKey, TName extends PropertyKey,
TTypeScriptType = (typeof NumberFieldType)['Int64']['valueType'] TTypeScriptType = (typeof NumberFieldType)['Int64']['valueType']
@ -309,6 +332,11 @@ export default class Struct<
); );
} }
/**
* Appends an `uint64` field to the `Struct`
*
* Requires native `BigInt` support
*/
public uint64< public uint64<
TName extends PropertyKey, TName extends PropertyKey,
TTypeScriptType = (typeof NumberFieldType)['Uint64']['valueType'] TTypeScriptType = (typeof NumberFieldType)['Uint64']['valueType']
@ -380,6 +408,13 @@ export default class Struct<
return this.arrayBufferLike(name, StringFieldType.instance, options); return this.arrayBufferLike(name, StringFieldType.instance, options);
}; };
/**
* Adds some extra fields into every Struct instance.
*
* Extra fields will not affect serialize or deserialize process.
*
* Multiple calls to `extra` will merge all values together.
*/
public extra<T extends Record< public extra<T extends Record<
// This trick disallows any keys that are already in `TValue` // This trick disallows any keys that are already in `TValue`
Exclude< Exclude<
@ -400,14 +435,29 @@ export default class Struct<
} }
/** /**
* Registers (or replaces) a custom callback to be run after deserialized.
* *
* A callback returning `never` (always throw an error)
* will also change the return type of `deserialize` to `never`.
*/ */
public postDeserialize( public postDeserialize(
callback: StructPostDeserialized<TValue, never> callback: StructPostDeserialized<TValue, never>
): Struct<TValue, TInit, TExtra, never>; ): Struct<TValue, TInit, TExtra, never>;
/**
* Registers (or replaces) a custom callback to be run after deserialized.
*
* A callback returning `void` means it modify the result object in-place
* (or doesn't modify it at all), so `deserialize` will still return the result object.
*/
public postDeserialize( public postDeserialize(
callback?: StructPostDeserialized<TValue, void> callback?: StructPostDeserialized<TValue, void>
): Struct<TValue, TInit, TExtra, undefined>; ): Struct<TValue, TInit, TExtra, undefined>;
/**
* Registers (or replaces) a custom callback to be run after deserialized.
*
* A callback returning anything other than `undefined`
* will `deserialize` to return that object instead.
*/
public postDeserialize<TPostSerialize>( public postDeserialize<TPostSerialize>(
callback?: StructPostDeserialized<TValue, TPostSerialize> callback?: StructPostDeserialized<TValue, TPostSerialize>
): Struct<TValue, TInit, TExtra, TPostSerialize>; ): Struct<TValue, TInit, TExtra, TPostSerialize>;
@ -418,24 +468,18 @@ export default class Struct<
return this as any; return this as any;
} }
private initializeObject(context: StructSerializationContext) { private initializeObject() {
const object = createRuntimeObject(); const object = createRuntimeObject();
Object.defineProperties(object, this._extra); Object.defineProperties(object, this._extra);
for (const [name, definition] of this._fields) {
const runtimeValue = definition.createValue(this.options, context, object);
setRuntimeValue(object, name, runtimeValue);
}
return object; return object;
} }
public create(init: TInit, context: StructSerializationContext): Overwrite<TExtra, TValue> { public create(init: TInit, context: StructSerializationContext): Overwrite<TExtra, TValue> {
const object = this.initializeObject(context); const object = this.initializeObject();
for (const [name] of this._fields) { for (const [name, definition] of this._fields) {
const runtimeValue = getRuntimeValue(object, name); const runtimeValue = definition.createValue(this.options, context, object, (init as any)[name]);
runtimeValue.set((init as any)[name]); setRuntimeValue(object, name, runtimeValue);
} }
return object as any; return object as any;
@ -444,11 +488,11 @@ export default class Struct<
public async deserialize( public async deserialize(
context: StructDeserializationContext context: StructDeserializationContext
): Promise<TPostDeserialized extends undefined ? Overwrite<TExtra, TValue> : TPostDeserialized> { ): Promise<TPostDeserialized extends undefined ? Overwrite<TExtra, TValue> : TPostDeserialized> {
const object = this.initializeObject(context); const object = this.initializeObject();
for (const [name] of this._fields) { for (const [name, definition] of this._fields) {
const runtimeValue = getRuntimeValue(object, name); const runtimeValue = await definition.deserialize(this.options, context, object);
await runtimeValue.deserialize(context); setRuntimeValue(object, name, runtimeValue);
} }
if (this._postDeserialized) { if (this._postDeserialized) {

View file

@ -1,4 +1,4 @@
import { FieldDefinition, FieldRuntimeValue, StructDeserializationContext, StructSerializationContext } from '../basic'; import { FieldDefinition, FieldRuntimeValue, StructDeserializationContext, StructOptions, StructSerializationContext } from '../basic';
/** /**
* Base class for all types that * Base class for all types that
@ -97,14 +97,16 @@ export class StringFieldType<TTypeScriptType = string>
} }
} }
const EmptyArrayBuffer = new ArrayBuffer(0);
export abstract class ArrayBufferLikeFieldDefinition< export abstract class ArrayBufferLikeFieldDefinition<
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType, TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
TOptions = void, TOptions = void,
TRemoveFields = never, TRemoveInitFields = never,
> extends FieldDefinition< > extends FieldDefinition<
TOptions, TOptions,
TType['valueType'], TType['valueType'],
TRemoveFields TRemoveInitFields
>{ >{
public readonly type: TType; public readonly type: TType;
@ -112,48 +114,55 @@ export abstract class ArrayBufferLikeFieldDefinition<
super(options); super(options);
this.type = type; this.type = type;
} }
}
const EmptyArrayBuffer = new ArrayBuffer(0); protected getDeserializeSize(object: any): number {
export abstract class ArrayBufferLikeFieldRuntimeValue<
TDefinition extends ArrayBufferLikeFieldDefinition<any, any, any>,
> extends FieldRuntimeValue<TDefinition> {
protected arrayBuffer: ArrayBuffer | undefined;
protected typedValue: unknown;
protected getDeserializeSize(): number {
return this.getSize(); return this.getSize();
} }
public async deserialize(context: StructDeserializationContext): Promise<void> { public async deserialize(
const size = this.getDeserializeSize(); options: Readonly<StructOptions>,
context: StructDeserializationContext,
this.arrayBuffer = undefined; object: any,
this.typedValue = undefined; ): Promise<ArrayBufferLikeFieldRuntimeValue<ArrayBufferLikeFieldDefinition<TType, TOptions, TRemoveInitFields>>> {
const size = this.getDeserializeSize(object);
let arrayBuffer: ArrayBuffer;
if (size === 0) { if (size === 0) {
this.arrayBuffer = EmptyArrayBuffer; arrayBuffer = EmptyArrayBuffer;
} else { } else {
this.arrayBuffer = await context.read(size); arrayBuffer = await context.read(size);
} }
this.typedValue = this.definition.type.fromArrayBuffer(this.arrayBuffer, context); const value = this.type.fromArrayBuffer(arrayBuffer, context);
const runtimeValue = this.createValue(options, context, object, value);
runtimeValue.arrayBuffer = arrayBuffer;
return runtimeValue;
} }
public get(): unknown { /**
return this.typedValue; * When implemented in derived classes, creates a `FieldRuntimeValue` for the current field definition.
} */
public abstract createValue(
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any,
value: TType['valueType'],
): ArrayBufferLikeFieldRuntimeValue<ArrayBufferLikeFieldDefinition<TType, TOptions, TRemoveInitFields>>;
}
public set(value: unknown): void { export class ArrayBufferLikeFieldRuntimeValue<
this.typedValue = value; TDefinition extends ArrayBufferLikeFieldDefinition<any, any, any>,
> extends FieldRuntimeValue<TDefinition> {
public arrayBuffer: ArrayBuffer | undefined;
public set(value: TDefinition['valueType']): void {
super.set(value);
this.arrayBuffer = undefined; this.arrayBuffer = undefined;
} }
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void { public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
if (!this.arrayBuffer) { if (!this.arrayBuffer) {
this.arrayBuffer = this.definition.type.toArrayBuffer(this.typedValue, context); this.arrayBuffer = this.definition.type.toArrayBuffer(this.value, context);
} }
new Uint8Array(dataView.buffer) new Uint8Array(dataView.buffer)

View file

@ -19,15 +19,9 @@ export class FixedLengthArrayBufferLikeFieldDefinition<
public createValue( public createValue(
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructSerializationContext, context: StructSerializationContext,
object: any object: any,
): FixedLengthArrayBufferFieldRuntimeValue { value: TType['valueType']
return new FixedLengthArrayBufferFieldRuntimeValue(this, options, context, object); ): ArrayBufferLikeFieldRuntimeValue<FixedLengthArrayBufferLikeFieldDefinition<TType, TOptions>> {
return new ArrayBufferLikeFieldRuntimeValue(this, options, context, object, value);
} }
}; };
class FixedLengthArrayBufferFieldRuntimeValue
extends ArrayBufferLikeFieldRuntimeValue<FixedLengthArrayBufferLikeFieldDefinition>{
public static getSize(descriptor: FixedLengthArrayBufferLikeFieldDefinition) {
return descriptor.options.length;
}
}

View file

@ -57,12 +57,27 @@ export class NumberFieldDefinition<
return this.type.size; return this.type.size;
} }
public async deserialize(
options: Readonly<StructOptions>,
context: StructDeserializationContext,
object: any,
): Promise<NumberFieldRuntimeValue<TType, TTypeScriptType>> {
const buffer = await context.read(this.getSize());
const view = new DataView(buffer);
const value = view[this.type.dataViewGetter](
0,
options.littleEndian
) as any;
return this.createValue(options, context, object, value);
}
public createValue( public createValue(
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructSerializationContext, context: StructSerializationContext,
object: any object: any,
value: TTypeScriptType,
): NumberFieldRuntimeValue<TType, TTypeScriptType> { ): NumberFieldRuntimeValue<TType, TTypeScriptType> {
return new NumberFieldRuntimeValue(this, options, context, object); return new NumberFieldRuntimeValue(this, options, context, object, value);
} }
} }
@ -70,25 +85,6 @@ export class NumberFieldRuntimeValue<
TType extends NumberFieldType = NumberFieldType, TType extends NumberFieldType = NumberFieldType,
TTypeScriptType = TType['valueType'], TTypeScriptType = TType['valueType'],
> extends FieldRuntimeValue<NumberFieldDefinition<TType, TTypeScriptType>> { > extends FieldRuntimeValue<NumberFieldDefinition<TType, TTypeScriptType>> {
protected value: number | bigint | undefined;
public async deserialize(context: StructDeserializationContext): Promise<void> {
const buffer = await context.read(this.getSize());
const view = new DataView(buffer);
this.value = view[this.definition.type.dataViewGetter](
0,
this.options.littleEndian
);
}
public get(): unknown {
return this.value;
}
public set(value: unknown): void {
this.value = value as number | bigint;
}
public serialize(dataView: DataView, offset: number): void { public serialize(dataView: DataView, offset: number): void {
// `setBigInt64` requires a `bigint` while others require `number` // `setBigInt64` requires a `bigint` while others require `number`
// So `dataView[DataViewSetters]` requires `bigint & number` // So `dataView[DataViewSetters]` requires `bigint & number`

View file

@ -1,6 +1,5 @@
import { getRuntimeValue, setRuntimeValue, StructOptions, StructSerializationContext } from '../basic'; import { FieldRuntimeValue, getRuntimeValue, setRuntimeValue, StructOptions, StructSerializationContext } from '../basic';
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldRuntimeValue, ArrayBufferLikeFieldType } from './array-buffer'; import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldRuntimeValue, ArrayBufferLikeFieldType } from './array-buffer';
import { NumberFieldDefinition, NumberFieldRuntimeValue } from './number';
import { KeysOfType } from './utils'; import { KeysOfType } from './utils';
export interface VariableLengthArrayBufferLikeFieldOptions< export interface VariableLengthArrayBufferLikeFieldOptions<
@ -22,47 +21,59 @@ export class VariableLengthArrayBufferLikeFieldDefinition<
return 0; return 0;
} }
protected getDeserializeSize(object: any) {
let value = object[this.options.lengthField] as number | string;
if (typeof value === 'string') {
value = Number.parseInt(value, 10);
}
return value;
}
public createValue( public createValue(
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructSerializationContext, context: StructSerializationContext,
object: any object: any,
): VariableLengthArrayBufferLikeFieldRuntimeValue { value: TType['valueType'],
return new VariableLengthArrayBufferLikeFieldRuntimeValue(this, options, context, object); ): VariableLengthArrayBufferLikeFieldRuntimeValue<TType, TOptions> {
return new VariableLengthArrayBufferLikeFieldRuntimeValue(this, options, context, object, value);
} }
} }
class VariableLengthArrayBufferLikeLengthFieldRuntimeValue extends NumberFieldRuntimeValue { class VariableLengthArrayBufferLikeLengthFieldRuntimeValue extends FieldRuntimeValue {
protected originalValue: FieldRuntimeValue;
protected arrayBufferValue: VariableLengthArrayBufferLikeFieldRuntimeValue; protected arrayBufferValue: VariableLengthArrayBufferLikeFieldRuntimeValue;
public constructor( public constructor(
definition: NumberFieldDefinition, originalValue: FieldRuntimeValue,
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any,
arrayBufferValue: VariableLengthArrayBufferLikeFieldRuntimeValue, arrayBufferValue: VariableLengthArrayBufferLikeFieldRuntimeValue,
) { ) {
super(definition, options, context, object); super(originalValue.definition, originalValue.options, originalValue.context, originalValue.object, 0);
this.originalValue = originalValue;
this.arrayBufferValue = arrayBufferValue; this.arrayBufferValue = arrayBufferValue;
} }
getDeserializeSize() { public getSize() {
return this.value; return this.originalValue.getSize();
} }
get() { get() {
// TODO: originalValue might be a `string` type, now it always returns `number`.
return this.arrayBufferValue.getSize(); return this.arrayBufferValue.getSize();
} }
set() { } set() { }
serialize(dataView: DataView, offset: number) { serialize(dataView: DataView, offset: number, context: StructSerializationContext) {
this.value = this.get(); this.originalValue.set(this.get());
super.serialize(dataView, offset); this.originalValue.serialize(dataView, offset, context);
} }
} }
class VariableLengthArrayBufferLikeFieldRuntimeValue class VariableLengthArrayBufferLikeFieldRuntimeValue<
extends ArrayBufferLikeFieldRuntimeValue<VariableLengthArrayBufferLikeFieldDefinition> { TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
TOptions extends VariableLengthArrayBufferLikeFieldOptions = VariableLengthArrayBufferLikeFieldOptions
> extends ArrayBufferLikeFieldRuntimeValue<VariableLengthArrayBufferLikeFieldDefinition<TType, TOptions>> {
public static getSize() { public static getSize() {
return 0; return 0;
} }
@ -72,38 +83,29 @@ class VariableLengthArrayBufferLikeFieldRuntimeValue
protected lengthFieldValue: VariableLengthArrayBufferLikeLengthFieldRuntimeValue; protected lengthFieldValue: VariableLengthArrayBufferLikeLengthFieldRuntimeValue;
public constructor( public constructor(
descriptor: VariableLengthArrayBufferLikeFieldDefinition, definition: VariableLengthArrayBufferLikeFieldDefinition<TType, TOptions>,
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructSerializationContext, context: StructSerializationContext,
object: any object: any,
value: TType['valueType'],
) { ) {
super(descriptor, options, context, object); super(definition, options, context, object, value);
// Patch the associated length field.
const lengthField = this.definition.options.lengthField; const lengthField = this.definition.options.lengthField;
const oldValue = getRuntimeValue(object, lengthField) as NumberFieldRuntimeValue; const originalValue = getRuntimeValue(object, lengthField);
this.lengthFieldValue = new VariableLengthArrayBufferLikeLengthFieldRuntimeValue( this.lengthFieldValue = new VariableLengthArrayBufferLikeLengthFieldRuntimeValue(originalValue, this);
oldValue.definition,
this.options,
this.context,
object,
this
);
setRuntimeValue(object, lengthField, this.lengthFieldValue); setRuntimeValue(object, lengthField, this.lengthFieldValue);
} }
protected getDeserializeSize() {
const value = this.lengthFieldValue.getDeserializeSize() as number;
return value;
}
public getSize() { public getSize() {
if (this.length === undefined) { if (this.length === undefined) {
if (this.arrayBuffer !== undefined) { if (this.arrayBuffer !== undefined) {
this.length = this.arrayBuffer.byteLength; this.length = this.arrayBuffer.byteLength;
} else { } else {
this.length = this.definition.type.getSize(this.typedValue); this.length = this.definition.type.getSize(this.value);
if (this.length === -1) { if (this.length === -1) {
this.arrayBuffer = this.definition.type.toArrayBuffer(this.typedValue, this.context); this.arrayBuffer = this.definition.type.toArrayBuffer(this.value, this.context);
this.length = this.arrayBuffer.byteLength; this.length = this.arrayBuffer.byteLength;
} }
} }

View file

@ -263,6 +263,30 @@
} }
} }
}, },
"ansi-escapes": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz",
"integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==",
"dev": true,
"requires": {
"type-fest": "^0.11.0"
}
},
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"dev": true
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"array-union": { "array-union": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
@ -294,6 +318,22 @@
"concat-map": "0.0.1" "concat-map": "0.0.1"
} }
}, },
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"ci-info": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
"integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==",
"dev": true
},
"clean-webpack-plugin": { "clean-webpack-plugin": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz", "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz",
@ -304,12 +344,33 @@
"del": "^4.1.1" "del": "^4.1.1"
} }
}, },
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true "dev": true
}, },
"consola": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz",
"integrity": "sha512-vlcSGgdYS26mPf7qNi+dCisbhiyDnrN1zaRbw3CSuc2wGOMEGGPsp46PdRG5gqXwgtJfjxDkxRNAgRPr1B77vQ==",
"dev": true
},
"del": { "del": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz",
@ -325,6 +386,27 @@
"rimraf": "^2.6.3" "rimraf": "^2.6.3"
} }
}, },
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
"integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5"
}
},
"fs.realpath": { "fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -366,6 +448,12 @@
} }
} }
}, },
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"inflight": { "inflight": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@ -382,6 +470,12 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true "dev": true
}, },
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true
},
"is-path-cwd": { "is-path-cwd": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
@ -469,6 +563,12 @@
"pinkie": "^2.0.0" "pinkie": "^2.0.0"
} }
}, },
"pretty-time": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz",
"integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==",
"dev": true
},
"rimraf": { "rimraf": {
"version": "2.7.1", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@ -484,12 +584,89 @@
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true "dev": true
}, },
"std-env": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-2.2.1.tgz",
"integrity": "sha512-IjYQUinA3lg5re/YMlwlfhqNRTzMZMqE+pezevdcTaHceqx8ngEi1alX9nNCk9Sc81fy1fLDeQoaCzeiW1yBOQ==",
"dev": true,
"requires": {
"ci-info": "^1.6.0"
}
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"type-fest": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz",
"integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==",
"dev": true
},
"typescript": { "typescript": {
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz",
"integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==", "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==",
"dev": true "dev": true
}, },
"webpackbar": {
"version": "5.0.0-3",
"resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.0-3.tgz",
"integrity": "sha512-viW6KCYjMb0NPoDrw2jAmLXU2dEOhRrtku28KmOfeE1vxbfwCYuTbTaMhnkrCZLFAFyY9Q49Z/jzYO80Dw5b8g==",
"dev": true,
"requires": {
"ansi-escapes": "^4.3.1",
"chalk": "^4.1.0",
"consola": "^2.15.0",
"figures": "^3.2.0",
"pretty-time": "^1.1.0",
"std-env": "^2.2.1",
"text-table": "^0.2.0",
"wrap-ansi": "^7.0.0"
}
},
"wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
}
},
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",

View file

@ -1,7 +1,7 @@
{ {
"name": "webpack-config", "name": "webpack-config",
"version": "0.0.1",
"private": true, "private": true,
"version": "0.0.1",
"description": "Webpack config for `demo` project in TypeScript", "description": "Webpack config for `demo` project in TypeScript",
"author": "Simon Chan <cnsimonchan@live.com>", "author": "Simon Chan <cnsimonchan@live.com>",
"homepage": "https://github.com/yume-chan/ya-webadb#readme", "homepage": "https://github.com/yume-chan/ya-webadb#readme",
@ -25,6 +25,7 @@
"@types/webpack-bundle-analyzer": "3.9.0", "@types/webpack-bundle-analyzer": "3.9.0",
"@types/webpack-dev-server": "3.11.1", "@types/webpack-dev-server": "3.11.1",
"clean-webpack-plugin": "3.0.0", "clean-webpack-plugin": "3.0.0",
"typescript": "4.1.3" "typescript": "4.1.3",
"webpackbar": "5.0.0-3"
} }
} }

View file

@ -5,11 +5,12 @@ import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import path from 'path'; import path from 'path';
import webpack from 'webpack'; import webpack from 'webpack';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import WebpackBar from 'webpackbar';
const context = path.resolve(process.cwd()); const context = path.resolve(process.cwd());
const plugins: webpack.Plugin[] = [ const plugins: webpack.Plugin[] = [
new CleanWebpackPlugin(), new WebpackBar({}),
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: '[name].[contenthash].css', filename: '[name].[contenthash].css',
}), }),
@ -35,36 +36,42 @@ if (process.env.ANALYZE) {
const config: webpack.ConfigurationFactory = ( const config: webpack.ConfigurationFactory = (
env: unknown, env: unknown,
argv: webpack.CliConfigOptions argv: webpack.CliConfigOptions
): webpack.Configuration => ({ ): webpack.Configuration => {
mode: 'development', if (argv.mode !== 'production') {
devtool: argv.mode === 'production' ? 'source-map' : 'eval-source-map', plugins.unshift(new CleanWebpackPlugin());
context, }
target: 'web',
entry: { return {
index: './src/index.tsx', mode: 'development',
}, devtool: argv.mode === 'production' ? 'source-map' : 'eval-source-map',
output: { context,
path: path.resolve(context, 'lib'), target: 'web',
filename: '[name].[contenthash].js', entry: {
}, index: './src/index.tsx',
resolve: { },
extensions: ['.ts', '.tsx', '.js'], output: {
// @ts-expect-error typing is not up to date path: path.resolve(context, 'lib'),
fallback: { "path": require.resolve("path-browserify") }, filename: '[name].[contenthash].js',
}, },
plugins, resolve: {
module: { extensions: ['.ts', '.tsx', '.js'],
rules: [ // @ts-expect-error typing is not up to date
{ test: /\.js$/, enforce: 'pre', use: ['source-map-loader'], }, fallback: { "path": require.resolve("path-browserify") },
{ test: /\.css$/i, use: [MiniCssExtractPlugin.loader, 'css-loader'] }, },
{ test: /\.asset$/, use: { loader: "file-loader" } }, plugins,
{ test: /\.tsx?$/i, loader: 'ts-loader', options: { projectReferences: true } }, module: {
], rules: [
}, { test: /\.js$/, enforce: 'pre', use: ['source-map-loader'], },
devServer: { { test: /\.css$/i, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
contentBase: path.resolve(context, 'lib'), { test: /\.asset$/, use: { loader: "file-loader" } },
port: 9000, { test: /\.tsx?$/i, loader: 'ts-loader', options: { projectReferences: true } },
}, ],
}); },
devServer: {
contentBase: path.resolve(context, 'lib'),
port: 9000,
},
};
};
export = config; export = config;

View file

@ -6,12 +6,12 @@
"ESNext" "ESNext"
], ],
"declaration": false, // /* Generates corresponding '.d.ts' file. */ "declaration": false, // /* Generates corresponding '.d.ts' file. */
"declarationDir": null,
"declarationMap": false, // /* Generates a sourcemap for each corresponding '.d.ts' file. */ "declarationMap": false, // /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": false, // /* Generates corresponding '.map' file. */ "sourceMap": false, // /* Generates corresponding '.map' file. */
"outDir": "../demo", // /* Redirect output structure to the directory. */ "outDir": "../demo", // /* Redirect output structure to the directory. */
"rootDir": "./src", // /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ "rootDir": "./src", // /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"composite": false, // /* Enable project compilation */ "composite": false, // /* Enable project compilation */
"tsBuildInfoFile": "./tsconfig.tsbuildinfo", /* Specify file to store incremental compilation information */
}, },
"include": [ "include": [
"src" "src"

View file

@ -10,7 +10,6 @@
"rootDir": "src", "rootDir": "src",
"outDir": "esm", "outDir": "esm",
"sourceMap": true, "sourceMap": true,
"removeComments": true,
"declaration": true, "declaration": true,
"declarationDir": "dts", "declarationDir": "dts",
"declarationMap": true, "declarationMap": true,