mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-03 09:49:24 +02:00
refactor: performance optimizations
also renamed some internal types, and adding more code comments. (not thoroughly tested yet)
This commit is contained in:
parent
65b8671b66
commit
8223d79886
49 changed files with 932 additions and 767 deletions
|
@ -43,7 +43,7 @@
|
||||||
"tslib": "^2.6.2"
|
"tslib": "^2.6.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.12.7",
|
"@types/node": "^20.12.8",
|
||||||
"@yume-chan/eslint-config": "workspace:^1.0.0",
|
"@yume-chan/eslint-config": "workspace:^1.0.0",
|
||||||
"@yume-chan/tsconfig": "workspace:^1.0.0",
|
"@yume-chan/tsconfig": "workspace:^1.0.0",
|
||||||
"jest": "^30.0.0-alpha.3",
|
"jest": "^30.0.0-alpha.3",
|
||||||
|
|
166
common/config/rush/pnpm-lock.yaml
generated
166
common/config/rush/pnpm-lock.yaml
generated
|
@ -33,8 +33,8 @@ importers:
|
||||||
version: 2.6.2
|
version: 2.6.2
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.12.7
|
specifier: ^20.12.8
|
||||||
version: 20.12.7
|
version: 20.12.8
|
||||||
'@yume-chan/eslint-config':
|
'@yume-chan/eslint-config':
|
||||||
specifier: workspace:^1.0.0
|
specifier: workspace:^1.0.0
|
||||||
version: link:../../toolchain/eslint-config
|
version: link:../../toolchain/eslint-config
|
||||||
|
@ -43,7 +43,7 @@ importers:
|
||||||
version: link:../../toolchain/tsconfig
|
version: link:../../toolchain/tsconfig
|
||||||
jest:
|
jest:
|
||||||
specifier: ^30.0.0-alpha.3
|
specifier: ^30.0.0-alpha.3
|
||||||
version: 30.0.0-alpha.3(@types/node@20.12.7)
|
version: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.2.5
|
version: 3.2.5
|
||||||
|
@ -73,8 +73,8 @@ importers:
|
||||||
specifier: ^30.0.0-alpha.3
|
specifier: ^30.0.0-alpha.3
|
||||||
version: 30.0.0-alpha.3
|
version: 30.0.0-alpha.3
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.12.7
|
specifier: ^20.12.8
|
||||||
version: 20.12.7
|
version: 20.12.8
|
||||||
'@yume-chan/eslint-config':
|
'@yume-chan/eslint-config':
|
||||||
specifier: workspace:^1.0.0
|
specifier: workspace:^1.0.0
|
||||||
version: link:../../toolchain/eslint-config
|
version: link:../../toolchain/eslint-config
|
||||||
|
@ -86,7 +86,7 @@ importers:
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
jest:
|
jest:
|
||||||
specifier: ^30.0.0-alpha.3
|
specifier: ^30.0.0-alpha.3
|
||||||
version: 30.0.0-alpha.3(@types/node@20.12.7)
|
version: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.2.5
|
version: 3.2.5
|
||||||
|
@ -179,7 +179,7 @@ importers:
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
jest:
|
jest:
|
||||||
specifier: ^30.0.0-alpha.3
|
specifier: ^30.0.0-alpha.3
|
||||||
version: 30.0.0-alpha.3(@types/node@20.12.7)
|
version: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.2.5
|
version: 3.2.5
|
||||||
|
@ -203,8 +203,8 @@ importers:
|
||||||
version: link:../struct
|
version: link:../struct
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.12.7
|
specifier: ^20.12.8
|
||||||
version: 20.12.7
|
version: 20.12.8
|
||||||
'@yume-chan/eslint-config':
|
'@yume-chan/eslint-config':
|
||||||
specifier: workspace:^1.0.0
|
specifier: workspace:^1.0.0
|
||||||
version: link:../../toolchain/eslint-config
|
version: link:../../toolchain/eslint-config
|
||||||
|
@ -213,7 +213,7 @@ importers:
|
||||||
version: link:../../toolchain/tsconfig
|
version: link:../../toolchain/tsconfig
|
||||||
jest:
|
jest:
|
||||||
specifier: ^30.0.0-alpha.3
|
specifier: ^30.0.0-alpha.3
|
||||||
version: 30.0.0-alpha.3(@types/node@20.12.7)
|
version: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.2.5
|
version: 3.2.5
|
||||||
|
@ -247,7 +247,7 @@ importers:
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
jest:
|
jest:
|
||||||
specifier: ^30.0.0-alpha.3
|
specifier: ^30.0.0-alpha.3
|
||||||
version: 30.0.0-alpha.3(@types/node@20.12.7)
|
version: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.2.5
|
version: 3.2.5
|
||||||
|
@ -294,7 +294,7 @@ importers:
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
jest:
|
jest:
|
||||||
specifier: ^30.0.0-alpha.3
|
specifier: ^30.0.0-alpha.3
|
||||||
version: 30.0.0-alpha.3(@types/node@20.12.7)
|
version: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.2.5
|
version: 3.2.5
|
||||||
|
@ -312,8 +312,8 @@ importers:
|
||||||
version: 4.0.3
|
version: 4.0.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.12.7
|
specifier: ^20.12.8
|
||||||
version: 20.12.7
|
version: 20.12.8
|
||||||
|
|
||||||
../../libraries/no-data-view:
|
../../libraries/no-data-view:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
@ -321,8 +321,8 @@ importers:
|
||||||
specifier: ^30.0.0-alpha.3
|
specifier: ^30.0.0-alpha.3
|
||||||
version: 30.0.0-alpha.3
|
version: 30.0.0-alpha.3
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.12.7
|
specifier: ^20.12.8
|
||||||
version: 20.12.7
|
version: 20.12.8
|
||||||
'@yume-chan/eslint-config':
|
'@yume-chan/eslint-config':
|
||||||
specifier: workspace:^1.0.0
|
specifier: workspace:^1.0.0
|
||||||
version: link:../../toolchain/eslint-config
|
version: link:../../toolchain/eslint-config
|
||||||
|
@ -334,7 +334,7 @@ importers:
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
jest:
|
jest:
|
||||||
specifier: ^30.0.0-alpha.3
|
specifier: ^30.0.0-alpha.3
|
||||||
version: 30.0.0-alpha.3(@types/node@20.12.7)
|
version: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.2.5
|
version: 3.2.5
|
||||||
|
@ -367,7 +367,7 @@ importers:
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
jest:
|
jest:
|
||||||
specifier: ^30.0.0-alpha.3
|
specifier: ^30.0.0-alpha.3
|
||||||
version: 30.0.0-alpha.3(@types/node@20.12.7)
|
version: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.2.5
|
version: 3.2.5
|
||||||
|
@ -410,7 +410,7 @@ importers:
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
jest:
|
jest:
|
||||||
specifier: ^30.0.0-alpha.3
|
specifier: ^30.0.0-alpha.3
|
||||||
version: 30.0.0-alpha.3(@types/node@20.12.7)
|
version: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.2.5
|
version: 3.2.5
|
||||||
|
@ -459,7 +459,7 @@ importers:
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
jest:
|
jest:
|
||||||
specifier: ^30.0.0-alpha.3
|
specifier: ^30.0.0-alpha.3
|
||||||
version: 30.0.0-alpha.3(@types/node@20.12.7)
|
version: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.2.5
|
version: 3.2.5
|
||||||
|
@ -502,7 +502,7 @@ importers:
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
jest:
|
jest:
|
||||||
specifier: ^30.0.0-alpha.3
|
specifier: ^30.0.0-alpha.3
|
||||||
version: 30.0.0-alpha.3(@types/node@20.12.7)
|
version: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.2.5
|
version: 3.2.5
|
||||||
|
@ -536,7 +536,7 @@ importers:
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
jest:
|
jest:
|
||||||
specifier: ^30.0.0-alpha.3
|
specifier: ^30.0.0-alpha.3
|
||||||
version: 30.0.0-alpha.3(@types/node@20.12.7)
|
version: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.2.5
|
version: 3.2.5
|
||||||
|
@ -567,7 +567,7 @@ importers:
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
jest:
|
jest:
|
||||||
specifier: ^30.0.0-alpha.3
|
specifier: ^30.0.0-alpha.3
|
||||||
version: 30.0.0-alpha.3(@types/node@20.12.7)
|
version: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.2.5
|
version: 3.2.5
|
||||||
|
@ -581,20 +581,20 @@ importers:
|
||||||
../../toolchain/eslint-config:
|
../../toolchain/eslint-config:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint/js':
|
'@eslint/js':
|
||||||
specifier: ^9.1.1
|
specifier: ^9.2.0
|
||||||
version: 9.1.1
|
version: 9.2.0
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.12.7
|
specifier: ^20.12.8
|
||||||
version: 20.12.7
|
version: 20.12.8
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.1.1
|
specifier: ^9.2.0
|
||||||
version: 9.1.1
|
version: 9.2.0
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.4.5
|
specifier: ^5.4.5
|
||||||
version: 5.4.5
|
version: 5.4.5
|
||||||
typescript-eslint:
|
typescript-eslint:
|
||||||
specifier: ^7.8.0
|
specifier: ^7.8.0
|
||||||
version: 7.8.0(eslint@9.1.1)(typescript@5.4.5)
|
version: 7.8.0(eslint@9.2.0)(typescript@5.4.5)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
|
@ -944,13 +944,13 @@ packages:
|
||||||
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@eslint-community/eslint-utils@4.4.0(eslint@9.1.1):
|
/@eslint-community/eslint-utils@4.4.0(eslint@9.2.0):
|
||||||
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
|
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
|
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.1.1
|
eslint: 9.2.0
|
||||||
eslint-visitor-keys: 3.4.3
|
eslint-visitor-keys: 3.4.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
@ -976,8 +976,8 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@eslint/js@9.1.1:
|
/@eslint/js@9.2.0:
|
||||||
resolution: {integrity: sha512-5WoDz3Y19Bg2BnErkZTp0en+c/i9PvgFS7MBe1+m60HjFr0hrphlAGp4yzI7pxpt4xShln4ZyYp4neJm8hmOkQ==}
|
resolution: {integrity: sha512-ESiIudvhoYni+MdsI8oD7skpprZ89qKocwRM2KEvhhBJ9nl5MRh7BXU5GTod7Mdygq+AUl+QzId6iWJKR/wABA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
@ -1039,7 +1039,7 @@ packages:
|
||||||
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
|
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/types': 30.0.0-alpha.3
|
'@jest/types': 30.0.0-alpha.3
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
jest-message-util: 30.0.0-alpha.3
|
jest-message-util: 30.0.0-alpha.3
|
||||||
jest-util: 30.0.0-alpha.3
|
jest-util: 30.0.0-alpha.3
|
||||||
|
@ -1060,14 +1060,14 @@ packages:
|
||||||
'@jest/test-result': 30.0.0-alpha.3
|
'@jest/test-result': 30.0.0-alpha.3
|
||||||
'@jest/transform': 30.0.0-alpha.3
|
'@jest/transform': 30.0.0-alpha.3
|
||||||
'@jest/types': 30.0.0-alpha.3
|
'@jest/types': 30.0.0-alpha.3
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
ansi-escapes: 4.3.2
|
ansi-escapes: 4.3.2
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
ci-info: 4.0.0
|
ci-info: 4.0.0
|
||||||
exit: 0.1.2
|
exit: 0.1.2
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
jest-changed-files: 30.0.0-alpha.3
|
jest-changed-files: 30.0.0-alpha.3
|
||||||
jest-config: 30.0.0-alpha.3(@types/node@20.12.7)
|
jest-config: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
jest-haste-map: 30.0.0-alpha.3
|
jest-haste-map: 30.0.0-alpha.3
|
||||||
jest-message-util: 30.0.0-alpha.3
|
jest-message-util: 30.0.0-alpha.3
|
||||||
jest-regex-util: 30.0.0-alpha.3
|
jest-regex-util: 30.0.0-alpha.3
|
||||||
|
@ -1095,7 +1095,7 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/fake-timers': 30.0.0-alpha.3
|
'@jest/fake-timers': 30.0.0-alpha.3
|
||||||
'@jest/types': 30.0.0-alpha.3
|
'@jest/types': 30.0.0-alpha.3
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
jest-mock: 30.0.0-alpha.3
|
jest-mock: 30.0.0-alpha.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
@ -1122,7 +1122,7 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/types': 30.0.0-alpha.3
|
'@jest/types': 30.0.0-alpha.3
|
||||||
'@sinonjs/fake-timers': 11.2.2
|
'@sinonjs/fake-timers': 11.2.2
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
jest-message-util: 30.0.0-alpha.3
|
jest-message-util: 30.0.0-alpha.3
|
||||||
jest-mock: 30.0.0-alpha.3
|
jest-mock: 30.0.0-alpha.3
|
||||||
jest-util: 30.0.0-alpha.3
|
jest-util: 30.0.0-alpha.3
|
||||||
|
@ -1155,7 +1155,7 @@ packages:
|
||||||
'@jest/transform': 30.0.0-alpha.3
|
'@jest/transform': 30.0.0-alpha.3
|
||||||
'@jest/types': 30.0.0-alpha.3
|
'@jest/types': 30.0.0-alpha.3
|
||||||
'@jridgewell/trace-mapping': 0.3.25
|
'@jridgewell/trace-mapping': 0.3.25
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
collect-v8-coverage: 1.0.2
|
collect-v8-coverage: 1.0.2
|
||||||
exit: 0.1.2
|
exit: 0.1.2
|
||||||
|
@ -1250,7 +1250,7 @@ packages:
|
||||||
'@jest/schemas': 29.6.3
|
'@jest/schemas': 29.6.3
|
||||||
'@types/istanbul-lib-coverage': 2.0.6
|
'@types/istanbul-lib-coverage': 2.0.6
|
||||||
'@types/istanbul-reports': 3.0.4
|
'@types/istanbul-reports': 3.0.4
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
'@types/yargs': 17.0.32
|
'@types/yargs': 17.0.32
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -1262,7 +1262,7 @@ packages:
|
||||||
'@jest/schemas': 30.0.0-alpha.3
|
'@jest/schemas': 30.0.0-alpha.3
|
||||||
'@types/istanbul-lib-coverage': 2.0.6
|
'@types/istanbul-lib-coverage': 2.0.6
|
||||||
'@types/istanbul-reports': 3.0.4
|
'@types/istanbul-reports': 3.0.4
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
'@types/yargs': 17.0.32
|
'@types/yargs': 17.0.32
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -1423,8 +1423,8 @@ packages:
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@types/node@20.12.7:
|
/@types/node@20.12.8:
|
||||||
resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==}
|
resolution: {integrity: sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 5.26.5
|
undici-types: 5.26.5
|
||||||
|
|
||||||
|
@ -1450,7 +1450,7 @@ packages:
|
||||||
'@types/yargs-parser': 21.0.3
|
'@types/yargs-parser': 21.0.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/eslint-plugin@7.8.0(@typescript-eslint/parser@7.8.0)(eslint@9.1.1)(typescript@5.4.5):
|
/@typescript-eslint/eslint-plugin@7.8.0(@typescript-eslint/parser@7.8.0)(eslint@9.2.0)(typescript@5.4.5):
|
||||||
resolution: {integrity: sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==}
|
resolution: {integrity: sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==}
|
||||||
engines: {node: ^18.18.0 || >=20.0.0}
|
engines: {node: ^18.18.0 || >=20.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1462,13 +1462,13 @@ packages:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.10.0
|
'@eslint-community/regexpp': 4.10.0
|
||||||
'@typescript-eslint/parser': 7.8.0(eslint@9.1.1)(typescript@5.4.5)
|
'@typescript-eslint/parser': 7.8.0(eslint@9.2.0)(typescript@5.4.5)
|
||||||
'@typescript-eslint/scope-manager': 7.8.0
|
'@typescript-eslint/scope-manager': 7.8.0
|
||||||
'@typescript-eslint/type-utils': 7.8.0(eslint@9.1.1)(typescript@5.4.5)
|
'@typescript-eslint/type-utils': 7.8.0(eslint@9.2.0)(typescript@5.4.5)
|
||||||
'@typescript-eslint/utils': 7.8.0(eslint@9.1.1)(typescript@5.4.5)
|
'@typescript-eslint/utils': 7.8.0(eslint@9.2.0)(typescript@5.4.5)
|
||||||
'@typescript-eslint/visitor-keys': 7.8.0
|
'@typescript-eslint/visitor-keys': 7.8.0
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
eslint: 9.1.1
|
eslint: 9.2.0
|
||||||
graphemer: 1.4.0
|
graphemer: 1.4.0
|
||||||
ignore: 5.3.1
|
ignore: 5.3.1
|
||||||
natural-compare: 1.4.0
|
natural-compare: 1.4.0
|
||||||
|
@ -1479,7 +1479,7 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@typescript-eslint/parser@7.8.0(eslint@9.1.1)(typescript@5.4.5):
|
/@typescript-eslint/parser@7.8.0(eslint@9.2.0)(typescript@5.4.5):
|
||||||
resolution: {integrity: sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==}
|
resolution: {integrity: sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==}
|
||||||
engines: {node: ^18.18.0 || >=20.0.0}
|
engines: {node: ^18.18.0 || >=20.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1494,7 +1494,7 @@ packages:
|
||||||
'@typescript-eslint/typescript-estree': 7.8.0(typescript@5.4.5)
|
'@typescript-eslint/typescript-estree': 7.8.0(typescript@5.4.5)
|
||||||
'@typescript-eslint/visitor-keys': 7.8.0
|
'@typescript-eslint/visitor-keys': 7.8.0
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
eslint: 9.1.1
|
eslint: 9.2.0
|
||||||
typescript: 5.4.5
|
typescript: 5.4.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
@ -1508,7 +1508,7 @@ packages:
|
||||||
'@typescript-eslint/visitor-keys': 7.8.0
|
'@typescript-eslint/visitor-keys': 7.8.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@typescript-eslint/type-utils@7.8.0(eslint@9.1.1)(typescript@5.4.5):
|
/@typescript-eslint/type-utils@7.8.0(eslint@9.2.0)(typescript@5.4.5):
|
||||||
resolution: {integrity: sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==}
|
resolution: {integrity: sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==}
|
||||||
engines: {node: ^18.18.0 || >=20.0.0}
|
engines: {node: ^18.18.0 || >=20.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1519,9 +1519,9 @@ packages:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/typescript-estree': 7.8.0(typescript@5.4.5)
|
'@typescript-eslint/typescript-estree': 7.8.0(typescript@5.4.5)
|
||||||
'@typescript-eslint/utils': 7.8.0(eslint@9.1.1)(typescript@5.4.5)
|
'@typescript-eslint/utils': 7.8.0(eslint@9.2.0)(typescript@5.4.5)
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
eslint: 9.1.1
|
eslint: 9.2.0
|
||||||
ts-api-utils: 1.3.0(typescript@5.4.5)
|
ts-api-utils: 1.3.0(typescript@5.4.5)
|
||||||
typescript: 5.4.5
|
typescript: 5.4.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
@ -1555,19 +1555,19 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@typescript-eslint/utils@7.8.0(eslint@9.1.1)(typescript@5.4.5):
|
/@typescript-eslint/utils@7.8.0(eslint@9.2.0)(typescript@5.4.5):
|
||||||
resolution: {integrity: sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==}
|
resolution: {integrity: sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==}
|
||||||
engines: {node: ^18.18.0 || >=20.0.0}
|
engines: {node: ^18.18.0 || >=20.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.56.0
|
eslint: ^8.56.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.4.0(eslint@9.1.1)
|
'@eslint-community/eslint-utils': 4.4.0(eslint@9.2.0)
|
||||||
'@types/json-schema': 7.0.15
|
'@types/json-schema': 7.0.15
|
||||||
'@types/semver': 7.5.8
|
'@types/semver': 7.5.8
|
||||||
'@typescript-eslint/scope-manager': 7.8.0
|
'@typescript-eslint/scope-manager': 7.8.0
|
||||||
'@typescript-eslint/types': 7.8.0
|
'@typescript-eslint/types': 7.8.0
|
||||||
'@typescript-eslint/typescript-estree': 7.8.0(typescript@5.4.5)
|
'@typescript-eslint/typescript-estree': 7.8.0(typescript@5.4.5)
|
||||||
eslint: 9.1.1
|
eslint: 9.2.0
|
||||||
semver: 7.6.0
|
semver: 7.6.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
@ -2171,15 +2171,15 @@ packages:
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/eslint@9.1.1:
|
/eslint@9.2.0:
|
||||||
resolution: {integrity: sha512-b4cRQ0BeZcSEzPpY2PjFY70VbO32K7BStTGtBsnIGdTSEEQzBi8hPBcGQmTG2zUvFr9uLe0TK42bw8YszuHEqg==}
|
resolution: {integrity: sha512-0n/I88vZpCOzO+PQpt0lbsqmn9AsnsJAQseIqhZFI8ibQT0U1AkEKRxA3EVMos0BoHSXDQvCXY25TUjB5tr8Og==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.4.0(eslint@9.1.1)
|
'@eslint-community/eslint-utils': 4.4.0(eslint@9.2.0)
|
||||||
'@eslint-community/regexpp': 4.10.0
|
'@eslint-community/regexpp': 4.10.0
|
||||||
'@eslint/eslintrc': 3.0.2
|
'@eslint/eslintrc': 3.0.2
|
||||||
'@eslint/js': 9.1.1
|
'@eslint/js': 9.2.0
|
||||||
'@humanwhocodes/config-array': 0.13.0
|
'@humanwhocodes/config-array': 0.13.0
|
||||||
'@humanwhocodes/module-importer': 1.0.1
|
'@humanwhocodes/module-importer': 1.0.1
|
||||||
'@humanwhocodes/retry': 0.2.3
|
'@humanwhocodes/retry': 0.2.3
|
||||||
|
@ -2794,7 +2794,7 @@ packages:
|
||||||
'@jest/expect': 30.0.0-alpha.3
|
'@jest/expect': 30.0.0-alpha.3
|
||||||
'@jest/test-result': 30.0.0-alpha.3
|
'@jest/test-result': 30.0.0-alpha.3
|
||||||
'@jest/types': 30.0.0-alpha.3
|
'@jest/types': 30.0.0-alpha.3
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
co: 4.6.0
|
co: 4.6.0
|
||||||
dedent: 1.5.3
|
dedent: 1.5.3
|
||||||
|
@ -2815,7 +2815,7 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/jest-cli@30.0.0-alpha.3(@types/node@20.12.7):
|
/jest-cli@30.0.0-alpha.3(@types/node@20.12.8):
|
||||||
resolution: {integrity: sha512-z1aQDxDe0VeDSEUeMr9MrfI5cc2SSCiKtG0Rt3XDfTgWrzyoakVds/9QMkkpNKHryCBzZZKOMe5W2uy7qM4WOA==}
|
resolution: {integrity: sha512-z1aQDxDe0VeDSEUeMr9MrfI5cc2SSCiKtG0Rt3XDfTgWrzyoakVds/9QMkkpNKHryCBzZZKOMe5W2uy7qM4WOA==}
|
||||||
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
|
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
@ -2831,7 +2831,7 @@ packages:
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
exit: 0.1.2
|
exit: 0.1.2
|
||||||
import-local: 3.1.0
|
import-local: 3.1.0
|
||||||
jest-config: 30.0.0-alpha.3(@types/node@20.12.7)
|
jest-config: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
jest-util: 30.0.0-alpha.3
|
jest-util: 30.0.0-alpha.3
|
||||||
jest-validate: 30.0.0-alpha.3
|
jest-validate: 30.0.0-alpha.3
|
||||||
yargs: 17.7.2
|
yargs: 17.7.2
|
||||||
|
@ -2842,7 +2842,7 @@ packages:
|
||||||
- ts-node
|
- ts-node
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/jest-config@30.0.0-alpha.3(@types/node@20.12.7):
|
/jest-config@30.0.0-alpha.3(@types/node@20.12.8):
|
||||||
resolution: {integrity: sha512-3eqS6gcsaPtcpU/VVlkLx1se1JiH18uh1Xg+oOf6FhlLDvAT5h6+dvWa2IpyucCN46dHHEw3E85qfjogq4XLtw==}
|
resolution: {integrity: sha512-3eqS6gcsaPtcpU/VVlkLx1se1JiH18uh1Xg+oOf6FhlLDvAT5h6+dvWa2IpyucCN46dHHEw3E85qfjogq4XLtw==}
|
||||||
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
|
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -2857,7 +2857,7 @@ packages:
|
||||||
'@babel/core': 7.24.5
|
'@babel/core': 7.24.5
|
||||||
'@jest/test-sequencer': 30.0.0-alpha.3
|
'@jest/test-sequencer': 30.0.0-alpha.3
|
||||||
'@jest/types': 30.0.0-alpha.3
|
'@jest/types': 30.0.0-alpha.3
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
babel-jest: 30.0.0-alpha.3(@babel/core@7.24.5)
|
babel-jest: 30.0.0-alpha.3(@babel/core@7.24.5)
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
ci-info: 4.0.0
|
ci-info: 4.0.0
|
||||||
|
@ -2917,7 +2917,7 @@ packages:
|
||||||
'@jest/environment': 30.0.0-alpha.3
|
'@jest/environment': 30.0.0-alpha.3
|
||||||
'@jest/fake-timers': 30.0.0-alpha.3
|
'@jest/fake-timers': 30.0.0-alpha.3
|
||||||
'@jest/types': 30.0.0-alpha.3
|
'@jest/types': 30.0.0-alpha.3
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
jest-mock: 30.0.0-alpha.3
|
jest-mock: 30.0.0-alpha.3
|
||||||
jest-util: 30.0.0-alpha.3
|
jest-util: 30.0.0-alpha.3
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -2932,7 +2932,7 @@ packages:
|
||||||
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
|
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/types': 30.0.0-alpha.3
|
'@jest/types': 30.0.0-alpha.3
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
anymatch: 3.1.3
|
anymatch: 3.1.3
|
||||||
fb-watchman: 2.0.2
|
fb-watchman: 2.0.2
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
|
@ -2983,7 +2983,7 @@ packages:
|
||||||
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
|
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/types': 30.0.0-alpha.3
|
'@jest/types': 30.0.0-alpha.3
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
jest-util: 30.0.0-alpha.3
|
jest-util: 30.0.0-alpha.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
@ -3038,7 +3038,7 @@ packages:
|
||||||
'@jest/test-result': 30.0.0-alpha.3
|
'@jest/test-result': 30.0.0-alpha.3
|
||||||
'@jest/transform': 30.0.0-alpha.3
|
'@jest/transform': 30.0.0-alpha.3
|
||||||
'@jest/types': 30.0.0-alpha.3
|
'@jest/types': 30.0.0-alpha.3
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
emittery: 0.13.1
|
emittery: 0.13.1
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
|
@ -3069,7 +3069,7 @@ packages:
|
||||||
'@jest/test-result': 30.0.0-alpha.3
|
'@jest/test-result': 30.0.0-alpha.3
|
||||||
'@jest/transform': 30.0.0-alpha.3
|
'@jest/transform': 30.0.0-alpha.3
|
||||||
'@jest/types': 30.0.0-alpha.3
|
'@jest/types': 30.0.0-alpha.3
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
cjs-module-lexer: 1.3.1
|
cjs-module-lexer: 1.3.1
|
||||||
collect-v8-coverage: 1.0.2
|
collect-v8-coverage: 1.0.2
|
||||||
|
@ -3122,7 +3122,7 @@ packages:
|
||||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/types': 29.6.3
|
'@jest/types': 29.6.3
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
ci-info: 3.9.0
|
ci-info: 3.9.0
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
|
@ -3134,7 +3134,7 @@ packages:
|
||||||
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
|
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/types': 30.0.0-alpha.3
|
'@jest/types': 30.0.0-alpha.3
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
ci-info: 4.0.0
|
ci-info: 4.0.0
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
|
@ -3159,7 +3159,7 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/test-result': 30.0.0-alpha.3
|
'@jest/test-result': 30.0.0-alpha.3
|
||||||
'@jest/types': 30.0.0-alpha.3
|
'@jest/types': 30.0.0-alpha.3
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
ansi-escapes: 4.3.2
|
ansi-escapes: 4.3.2
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
emittery: 0.13.1
|
emittery: 0.13.1
|
||||||
|
@ -3171,13 +3171,13 @@ packages:
|
||||||
resolution: {integrity: sha512-8lS9LxbEjOyBRz0Pdi6m3HYJ3feIi1tv0u7oqxjXvB1lMksq+IcSxaPTCcvJbIqt3WAFFYQnDs5I3NkJiEG5Ow==}
|
resolution: {integrity: sha512-8lS9LxbEjOyBRz0Pdi6m3HYJ3feIi1tv0u7oqxjXvB1lMksq+IcSxaPTCcvJbIqt3WAFFYQnDs5I3NkJiEG5Ow==}
|
||||||
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
|
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.8
|
||||||
jest-util: 30.0.0-alpha.3
|
jest-util: 30.0.0-alpha.3
|
||||||
merge-stream: 2.0.0
|
merge-stream: 2.0.0
|
||||||
supports-color: 8.1.1
|
supports-color: 8.1.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/jest@30.0.0-alpha.3(@types/node@20.12.7):
|
/jest@30.0.0-alpha.3(@types/node@20.12.8):
|
||||||
resolution: {integrity: sha512-oJndFRnG1Xsc1ybac44hGGj7+O4nT9losg8+8YDjNwDAXbYwvzyRgmCiPo6L/BROiAD8Z9qGgFRsFuGdpmQuFw==}
|
resolution: {integrity: sha512-oJndFRnG1Xsc1ybac44hGGj7+O4nT9losg8+8YDjNwDAXbYwvzyRgmCiPo6L/BROiAD8Z9qGgFRsFuGdpmQuFw==}
|
||||||
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
|
engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
@ -3190,7 +3190,7 @@ packages:
|
||||||
'@jest/core': 30.0.0-alpha.3
|
'@jest/core': 30.0.0-alpha.3
|
||||||
'@jest/types': 30.0.0-alpha.3
|
'@jest/types': 30.0.0-alpha.3
|
||||||
import-local: 3.1.0
|
import-local: 3.1.0
|
||||||
jest-cli: 30.0.0-alpha.3(@types/node@20.12.7)
|
jest-cli: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
|
@ -4017,7 +4017,7 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
bs-logger: 0.2.6
|
bs-logger: 0.2.6
|
||||||
fast-json-stable-stringify: 2.1.0
|
fast-json-stable-stringify: 2.1.0
|
||||||
jest: 30.0.0-alpha.3(@types/node@20.12.7)
|
jest: 30.0.0-alpha.3(@types/node@20.12.8)
|
||||||
jest-util: 29.7.0
|
jest-util: 29.7.0
|
||||||
json5: 2.2.3
|
json5: 2.2.3
|
||||||
lodash.memoize: 4.1.2
|
lodash.memoize: 4.1.2
|
||||||
|
@ -4047,7 +4047,7 @@ packages:
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/typescript-eslint@7.8.0(eslint@9.1.1)(typescript@5.4.5):
|
/typescript-eslint@7.8.0(eslint@9.2.0)(typescript@5.4.5):
|
||||||
resolution: {integrity: sha512-sheFG+/D8N/L7gC3WT0Q8sB97Nm573Yfr+vZFzl/4nBdYcmviBPtwGSX9TJ7wpVg28ocerKVOt+k2eGmHzcgVA==}
|
resolution: {integrity: sha512-sheFG+/D8N/L7gC3WT0Q8sB97Nm573Yfr+vZFzl/4nBdYcmviBPtwGSX9TJ7wpVg28ocerKVOt+k2eGmHzcgVA==}
|
||||||
engines: {node: ^18.18.0 || >=20.0.0}
|
engines: {node: ^18.18.0 || >=20.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -4057,10 +4057,10 @@ packages:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/eslint-plugin': 7.8.0(@typescript-eslint/parser@7.8.0)(eslint@9.1.1)(typescript@5.4.5)
|
'@typescript-eslint/eslint-plugin': 7.8.0(@typescript-eslint/parser@7.8.0)(eslint@9.2.0)(typescript@5.4.5)
|
||||||
'@typescript-eslint/parser': 7.8.0(eslint@9.1.1)(typescript@5.4.5)
|
'@typescript-eslint/parser': 7.8.0(eslint@9.2.0)(typescript@5.4.5)
|
||||||
'@typescript-eslint/utils': 7.8.0(eslint@9.1.1)(typescript@5.4.5)
|
'@typescript-eslint/utils': 7.8.0(eslint@9.2.0)(typescript@5.4.5)
|
||||||
eslint: 9.1.1
|
eslint: 9.2.0
|
||||||
typescript: 5.4.5
|
typescript: 5.4.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
|
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
|
||||||
{
|
{
|
||||||
"pnpmShrinkwrapHash": "c1a7aa8a614d364cced2ff0b11ae2cd00e133961",
|
"pnpmShrinkwrapHash": "ca7743cb8480b533789651d98878a7900c9455fa",
|
||||||
"preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f"
|
"preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f"
|
||||||
}
|
}
|
||||||
|
|
|
@ -333,10 +333,10 @@ export class AdbScrcpyClient {
|
||||||
async #parseDeviceMessages(controlStream: ReadableStream<Uint8Array>) {
|
async #parseDeviceMessages(controlStream: ReadableStream<Uint8Array>) {
|
||||||
const buffered = new BufferedReadableStream(controlStream);
|
const buffered = new BufferedReadableStream(controlStream);
|
||||||
while (true) {
|
while (true) {
|
||||||
const type = await buffered.readExactly(1);
|
const [type] = await buffered.readExactly(1);
|
||||||
if (!(await this.#options.parseDeviceMessage(type[0]!, buffered))) {
|
if (!(await this.#options.parseDeviceMessage(type!, buffered))) {
|
||||||
buffered
|
buffered
|
||||||
.cancel(new Error(`Unknown device message type ${type[0]}`))
|
.cancel(new Error(`Unknown device message type ${type!}`))
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,15 @@ export class AdbScrcpyForwardConnection extends AdbScrcpyConnection {
|
||||||
const buffered = new BufferedReadableStream(
|
const buffered = new BufferedReadableStream(
|
||||||
stream.readable,
|
stream.readable,
|
||||||
);
|
);
|
||||||
|
// Skip the dummy byte
|
||||||
|
// Google ADB forward tunnel listens on a socket on the computer,
|
||||||
|
// when a client connects to that socket, Google ADB will forward
|
||||||
|
// the connection to the socket on the device.
|
||||||
|
// However, connecting to that socket will always succeed immediately,
|
||||||
|
// which doesn't mean that Google ADB has connected to
|
||||||
|
// the socket on the device.
|
||||||
|
// Thus Scrcpy server sends a dummy byte to the socket, to let the client
|
||||||
|
// know that the connection is truly established.
|
||||||
await buffered.readExactly(1);
|
await buffered.readExactly(1);
|
||||||
return {
|
return {
|
||||||
readable: buffered.release(),
|
readable: buffered.release(),
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
"@yume-chan/struct": "workspace:^0.0.23"
|
"@yume-chan/struct": "workspace:^0.0.23"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.12.7",
|
"@types/node": "^20.12.8",
|
||||||
"@yume-chan/eslint-config": "workspace:^1.0.0",
|
"@yume-chan/eslint-config": "workspace:^1.0.0",
|
||||||
"@yume-chan/tsconfig": "workspace:^1.0.0",
|
"@yume-chan/tsconfig": "workspace:^1.0.0",
|
||||||
"jest": "^30.0.0-alpha.3",
|
"jest": "^30.0.0-alpha.3",
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "^30.0.0-alpha.3",
|
"@jest/globals": "^30.0.0-alpha.3",
|
||||||
"@types/node": "^20.12.7",
|
"@types/node": "^20.12.8",
|
||||||
"@yume-chan/eslint-config": "workspace:^1.0.0",
|
"@yume-chan/eslint-config": "workspace:^1.0.0",
|
||||||
"@yume-chan/tsconfig": "workspace:^1.0.0",
|
"@yume-chan/tsconfig": "workspace:^1.0.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
import { AutoDisposable } from "@yume-chan/event";
|
import { AutoDisposable } from "@yume-chan/event";
|
||||||
import { BufferedReadableStream } from "@yume-chan/stream-extra";
|
import { BufferedReadableStream } from "@yume-chan/stream-extra";
|
||||||
import Struct, { ExactReadableEndedError } from "@yume-chan/struct";
|
import Struct, { ExactReadableEndedError, encodeUtf8 } from "@yume-chan/struct";
|
||||||
|
|
||||||
import type { Adb, AdbIncomingSocketHandler } from "../adb.js";
|
import type { Adb, AdbIncomingSocketHandler } from "../adb.js";
|
||||||
import { decodeUtf8, hexToNumber } from "../utils/index.js";
|
import { hexToNumber } from "../utils/index.js";
|
||||||
|
|
||||||
export interface AdbForwardListener {
|
export interface AdbForwardListener {
|
||||||
deviceSerial: string;
|
deviceSerial: string;
|
||||||
|
@ -47,11 +47,21 @@ const AdbReverseErrorResponse = new Struct()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function readString(stream: BufferedReadableStream, length: number) {
|
// Like `hexToNumber`, it's much faster than first converting `buffer` to a string
|
||||||
const buffer = await stream.readExactly(length);
|
function decimalToNumber(buffer: Uint8Array) {
|
||||||
return decodeUtf8(buffer);
|
let value = 0;
|
||||||
|
for (const byte of buffer) {
|
||||||
|
// Like `parseInt`, return when it encounters a non-digit character
|
||||||
|
if (byte < 48 || byte > 57) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
value = value * 10 + byte - 48;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const OKAY = encodeUtf8("OKAY");
|
||||||
|
|
||||||
export class AdbReverseCommand extends AutoDisposable {
|
export class AdbReverseCommand extends AutoDisposable {
|
||||||
protected adb: Adb;
|
protected adb: Adb;
|
||||||
|
|
||||||
|
@ -70,10 +80,14 @@ export class AdbReverseCommand extends AutoDisposable {
|
||||||
|
|
||||||
protected async sendRequest(service: string) {
|
protected async sendRequest(service: string) {
|
||||||
const stream = await this.createBufferedStream(service);
|
const stream = await this.createBufferedStream(service);
|
||||||
const success = (await readString(stream, 4)) === "OKAY";
|
|
||||||
if (!success) {
|
const response = await stream.readExactly(4);
|
||||||
await AdbReverseErrorResponse.deserialize(stream);
|
for (let i = 0; i < 4; i += 1) {
|
||||||
|
if (response[i] !== OKAY[i]) {
|
||||||
|
await AdbReverseErrorResponse.deserialize(stream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,8 +124,8 @@ export class AdbReverseCommand extends AutoDisposable {
|
||||||
const position = stream.position;
|
const position = stream.position;
|
||||||
try {
|
try {
|
||||||
const length = hexToNumber(await stream.readExactly(4));
|
const length = hexToNumber(await stream.readExactly(4));
|
||||||
const port = await readString(stream, length);
|
const port = decimalToNumber(await stream.readExactly(length));
|
||||||
deviceAddress = `tcp:${Number.parseInt(port, 10)}`;
|
deviceAddress = `tcp:${port}`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (
|
if (
|
||||||
e instanceof ExactReadableEndedError &&
|
e instanceof ExactReadableEndedError &&
|
||||||
|
|
|
@ -139,14 +139,12 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
||||||
|
|
||||||
this.#stdin = new MaybeConsumable.WritableStream<Uint8Array>({
|
this.#stdin = new MaybeConsumable.WritableStream<Uint8Array>({
|
||||||
write: async (chunk) => {
|
write: async (chunk) => {
|
||||||
await MaybeConsumable.tryConsume(chunk, async (chunk) => {
|
await this.#writer.write(
|
||||||
await this.#writer.write(
|
AdbShellProtocolPacket.serialize({
|
||||||
AdbShellProtocolPacket.serialize({
|
id: AdbShellProtocolId.Stdin,
|
||||||
id: AdbShellProtocolId.Stdin,
|
data: chunk,
|
||||||
data: chunk,
|
}),
|
||||||
}),
|
);
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,8 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
|
||||||
this.#combiner = new BufferCombiner(bufferSize);
|
this.#combiner = new BufferCombiner(bufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #writeConsumable(buffer: Uint8Array) {
|
async #write(buffer: Uint8Array) {
|
||||||
|
// `#combiner` will reuse the buffer, so we need to use the Consumable pattern
|
||||||
await Consumable.WritableStream.write(this.#writer, buffer);
|
await Consumable.WritableStream.write(this.#writer, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
|
||||||
await this.#writeLock.wait();
|
await this.#writeLock.wait();
|
||||||
const buffer = this.#combiner.flush();
|
const buffer = this.#combiner.flush();
|
||||||
if (buffer) {
|
if (buffer) {
|
||||||
await this.#writeConsumable(buffer);
|
await this.#write(buffer);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.#writeLock.notifyOne();
|
this.#writeLock.notifyOne();
|
||||||
|
@ -55,7 +56,7 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
|
||||||
try {
|
try {
|
||||||
await this.#writeLock.wait();
|
await this.#writeLock.wait();
|
||||||
for (const buffer of this.#combiner.push(data)) {
|
for (const buffer of this.#combiner.push(data)) {
|
||||||
await this.#writeConsumable(buffer);
|
await this.#write(buffer);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.#writeLock.notifyOne();
|
this.#writeLock.notifyOne();
|
||||||
|
@ -63,11 +64,15 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
|
||||||
}
|
}
|
||||||
|
|
||||||
async readExactly(length: number) {
|
async readExactly(length: number) {
|
||||||
|
// The request may still be in the internal buffer.
|
||||||
|
// Call `flush` to send it before starting reading
|
||||||
await this.flush();
|
await this.flush();
|
||||||
return await this.#readable.readExactly(length);
|
return await this.#readable.readExactly(length);
|
||||||
}
|
}
|
||||||
|
|
||||||
release(): void {
|
release(): void {
|
||||||
|
// In theory, the writer shouldn't leave anything in the buffer,
|
||||||
|
// but to be safe, call `flush` to throw away any remaining data.
|
||||||
this.#combiner.flush();
|
this.#combiner.flush();
|
||||||
this.#socketLock.notifyOne();
|
this.#socketLock.notifyOne();
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,27 +24,56 @@ import { AdbCommand, calculateChecksum } from "./packet.js";
|
||||||
import { AdbDaemonSocketController } from "./socket.js";
|
import { AdbDaemonSocketController } from "./socket.js";
|
||||||
|
|
||||||
export interface AdbPacketDispatcherOptions {
|
export interface AdbPacketDispatcherOptions {
|
||||||
calculateChecksum: boolean;
|
|
||||||
/**
|
/**
|
||||||
* Before Android 9.0, ADB uses `char*` to parse service string,
|
* From Android 9.0, ADB stopped checking the checksum in packet header to improve performance.
|
||||||
|
*
|
||||||
|
* The value should be inferred from the device's ADB protocol version.
|
||||||
|
*/
|
||||||
|
calculateChecksum: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before Android 9.0, ADB uses `char*` to parse service strings,
|
||||||
* thus requires a null character to terminate.
|
* thus requires a null character to terminate.
|
||||||
*
|
*
|
||||||
* Usually it should have the same value as `calculateChecksum`.
|
* The value should be inferred from the device's ADB protocol version.
|
||||||
|
* Usually it should have the same value as `calculateChecksum`, since they both changed
|
||||||
|
* in Android 9.0.
|
||||||
*/
|
*/
|
||||||
appendNullToServiceString: boolean;
|
appendNullToServiceString: boolean;
|
||||||
maxPayloadSize: number;
|
|
||||||
/**
|
|
||||||
* The number of bytes the device can send before receiving an ack packet.
|
|
||||||
|
|
||||||
* Set to 0 or any negative value to disable delayed ack.
|
maxPayloadSize: number;
|
||||||
* Otherwise the value must be in the range of unsigned 32-bit integer.
|
|
||||||
*/
|
|
||||||
initialDelayedAckBytes: number;
|
|
||||||
/**
|
/**
|
||||||
* Whether to preserve the connection open after the `AdbPacketDispatcher` is closed.
|
* Whether to keep the `connection` open (don't call `writable.close` and `readable.cancel`)
|
||||||
|
* when `AdbPacketDispatcher.close` is called.
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
*/
|
*/
|
||||||
preserveConnection?: boolean | undefined;
|
preserveConnection?: boolean | undefined;
|
||||||
debugSlowRead?: boolean | undefined;
|
|
||||||
|
/**
|
||||||
|
* The number of bytes the device can send before receiving an ack packet.
|
||||||
|
* Using delayed ack can improve the throughput,
|
||||||
|
* especially when the device is connected over Wi-Fi (so the latency is higher).
|
||||||
|
*
|
||||||
|
* This must be the negotiated value between the client and device. If the device enabled
|
||||||
|
* delayed ack but the client didn't, the device will throw an error when the client sends
|
||||||
|
* the first `WRTE` packet. And vice versa.
|
||||||
|
*/
|
||||||
|
initialDelayedAckBytes: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set, the dispatcher will throw an error when
|
||||||
|
* one of the socket readable stalls for this amount of milliseconds.
|
||||||
|
*
|
||||||
|
* Because ADB is a multiplexed protocol, blocking one socket will also block all other sockets.
|
||||||
|
* It's important to always read from all sockets to prevent stalling.
|
||||||
|
*
|
||||||
|
* This option is helpful to detect bugs in the client code.
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
readTimeLimit?: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SocketOpenResult {
|
interface SocketOpenResult {
|
||||||
|
@ -225,12 +254,19 @@ export class AdbPacketDispatcher implements Closeable {
|
||||||
|
|
||||||
// Maybe the device is responding to a packet of last connection
|
// Maybe the device is responding to a packet of last connection
|
||||||
// Tell the device to close the socket
|
// Tell the device to close the socket
|
||||||
void this.sendPacket(AdbCommand.Close, packet.arg1, packet.arg0);
|
void this.sendPacket(
|
||||||
|
AdbCommand.Close,
|
||||||
|
packet.arg1,
|
||||||
|
packet.arg0,
|
||||||
|
EMPTY_UINT8_ARRAY,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#sendOkay(localId: number, remoteId: number, ackBytes: number) {
|
#sendOkay(localId: number, remoteId: number, ackBytes: number) {
|
||||||
let payload: Uint8Array;
|
let payload: Uint8Array;
|
||||||
if (this.options.initialDelayedAckBytes !== 0) {
|
if (this.options.initialDelayedAckBytes !== 0) {
|
||||||
|
// TODO: try reusing this buffer to reduce memory allocation
|
||||||
|
// However, that requires blocking reentrance of `sendOkay`, which might be more expensive
|
||||||
payload = new Uint8Array(4);
|
payload = new Uint8Array(4);
|
||||||
setUint32LittleEndian(payload, 0, ackBytes);
|
setUint32LittleEndian(payload, 0, ackBytes);
|
||||||
} else {
|
} else {
|
||||||
|
@ -241,22 +277,24 @@ export class AdbPacketDispatcher implements Closeable {
|
||||||
}
|
}
|
||||||
|
|
||||||
async #handleOpen(packet: AdbPacketData) {
|
async #handleOpen(packet: AdbPacketData) {
|
||||||
// `AsyncOperationManager` doesn't support skipping IDs
|
// Allocate a local ID for the socket from `#initializers`.
|
||||||
// Use `add` + `resolve` to simulate this behavior
|
// `AsyncOperationManager` doesn't directly support returning the next ID,
|
||||||
|
// so use `add` + `resolve` to simulate this
|
||||||
const [localId] = this.#initializers.add<number>();
|
const [localId] = this.#initializers.add<number>();
|
||||||
this.#initializers.resolve(localId, undefined);
|
this.#initializers.resolve(localId, undefined);
|
||||||
|
|
||||||
const remoteId = packet.arg0;
|
const remoteId = packet.arg0;
|
||||||
let initialDelayedAckBytes = packet.arg1;
|
let availableWriteBytes = packet.arg1;
|
||||||
const service = decodeUtf8(packet.payload);
|
const service = decodeUtf8(packet.payload);
|
||||||
|
|
||||||
|
// Check remote delayed ack enablement is consistent with local
|
||||||
if (this.options.initialDelayedAckBytes === 0) {
|
if (this.options.initialDelayedAckBytes === 0) {
|
||||||
if (initialDelayedAckBytes !== 0) {
|
if (availableWriteBytes !== 0) {
|
||||||
throw new Error("Invalid OPEN packet. arg1 should be 0");
|
throw new Error("Invalid OPEN packet. arg1 should be 0");
|
||||||
}
|
}
|
||||||
initialDelayedAckBytes = Infinity;
|
availableWriteBytes = Infinity;
|
||||||
} else {
|
} else {
|
||||||
if (initialDelayedAckBytes === 0) {
|
if (availableWriteBytes === 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Invalid OPEN packet. arg1 should be greater than 0",
|
"Invalid OPEN packet. arg1 should be greater than 0",
|
||||||
);
|
);
|
||||||
|
@ -265,7 +303,12 @@ export class AdbPacketDispatcher implements Closeable {
|
||||||
|
|
||||||
const handler = this.#incomingSocketHandlers.get(service);
|
const handler = this.#incomingSocketHandlers.get(service);
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
await this.sendPacket(AdbCommand.Close, 0, remoteId);
|
await this.sendPacket(
|
||||||
|
AdbCommand.Close,
|
||||||
|
0,
|
||||||
|
remoteId,
|
||||||
|
EMPTY_UINT8_ARRAY,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,8 +318,8 @@ export class AdbPacketDispatcher implements Closeable {
|
||||||
remoteId,
|
remoteId,
|
||||||
localCreated: false,
|
localCreated: false,
|
||||||
service,
|
service,
|
||||||
|
availableWriteBytes,
|
||||||
});
|
});
|
||||||
controller.ack(initialDelayedAckBytes);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await handler(controller.socket);
|
await handler(controller.socket);
|
||||||
|
@ -287,7 +330,12 @@ export class AdbPacketDispatcher implements Closeable {
|
||||||
this.options.initialDelayedAckBytes,
|
this.options.initialDelayedAckBytes,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.sendPacket(AdbCommand.Close, 0, remoteId);
|
await this.sendPacket(
|
||||||
|
AdbCommand.Close,
|
||||||
|
0,
|
||||||
|
remoteId,
|
||||||
|
EMPTY_UINT8_ARRAY,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,14 +346,8 @@ export class AdbPacketDispatcher implements Closeable {
|
||||||
}
|
}
|
||||||
|
|
||||||
let handled = false;
|
let handled = false;
|
||||||
await Promise.race([
|
|
||||||
delay(5000).then(() => {
|
const promises: Promise<void>[] = [
|
||||||
if (this.options.debugSlowRead && !handled) {
|
|
||||||
throw new Error(
|
|
||||||
`packet for \`${socket.service}\` not handled in 5 seconds`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
(async () => {
|
(async () => {
|
||||||
await socket.enqueue(packet.payload);
|
await socket.enqueue(packet.payload);
|
||||||
await this.#sendOkay(
|
await this.#sendOkay(
|
||||||
|
@ -315,9 +357,22 @@ export class AdbPacketDispatcher implements Closeable {
|
||||||
);
|
);
|
||||||
handled = true;
|
handled = true;
|
||||||
})(),
|
})(),
|
||||||
]);
|
];
|
||||||
|
|
||||||
return;
|
if (this.options.readTimeLimit) {
|
||||||
|
promises.push(
|
||||||
|
(async () => {
|
||||||
|
await delay(this.options.readTimeLimit!);
|
||||||
|
if (!handled) {
|
||||||
|
throw new Error(
|
||||||
|
`readable of \`${socket.service}\` has stalled for ${this.options.readTimeLimit} milliseconds`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.race(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createSocket(service: string): Promise<AdbSocket> {
|
async createSocket(service: string): Promise<AdbSocket> {
|
||||||
|
@ -342,8 +397,8 @@ export class AdbPacketDispatcher implements Closeable {
|
||||||
remoteId,
|
remoteId,
|
||||||
localCreated: true,
|
localCreated: true,
|
||||||
service,
|
service,
|
||||||
|
availableWriteBytes,
|
||||||
});
|
});
|
||||||
controller.ack(availableWriteBytes);
|
|
||||||
this.#sockets.set(localId, controller);
|
this.#sockets.set(localId, controller);
|
||||||
|
|
||||||
return controller.socket;
|
return controller.socket;
|
||||||
|
@ -365,7 +420,8 @@ export class AdbPacketDispatcher implements Closeable {
|
||||||
command: AdbCommand,
|
command: AdbCommand,
|
||||||
arg0: number,
|
arg0: number,
|
||||||
arg1: number,
|
arg1: number,
|
||||||
payload: string | Uint8Array = EMPTY_UINT8_ARRAY,
|
// PERF: It's slightly faster to not use default parameter values
|
||||||
|
payload: string | Uint8Array,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (typeof payload === "string") {
|
if (typeof payload === "string") {
|
||||||
payload = encodeUtf8(payload);
|
payload = encodeUtf8(payload);
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
import type { Consumable } from "@yume-chan/stream-extra";
|
import { Consumable, TransformStream } from "@yume-chan/stream-extra";
|
||||||
import {
|
|
||||||
ConsumableReadableStream,
|
|
||||||
TransformStream,
|
|
||||||
} from "@yume-chan/stream-extra";
|
|
||||||
import Struct from "@yume-chan/struct";
|
import Struct from "@yume-chan/struct";
|
||||||
|
|
||||||
export enum AdbCommand {
|
export enum AdbCommand {
|
||||||
|
@ -65,7 +61,7 @@ export class AdbPacketSerializeStream extends TransformStream<
|
||||||
const init = chunk as AdbPacketInit & AdbPacketHeaderInit;
|
const init = chunk as AdbPacketInit & AdbPacketHeaderInit;
|
||||||
init.payloadLength = init.payload.byteLength;
|
init.payloadLength = init.payload.byteLength;
|
||||||
|
|
||||||
await ConsumableReadableStream.enqueue(
|
await Consumable.ReadableStream.enqueue(
|
||||||
controller,
|
controller,
|
||||||
AdbPacketHeader.serialize(init, headerBuffer),
|
AdbPacketHeader.serialize(init, headerBuffer),
|
||||||
);
|
);
|
||||||
|
@ -74,7 +70,7 @@ export class AdbPacketSerializeStream extends TransformStream<
|
||||||
// USB protocol preserves packet boundaries,
|
// USB protocol preserves packet boundaries,
|
||||||
// so we must write payload separately as native ADB does,
|
// so we must write payload separately as native ADB does,
|
||||||
// otherwise the read operation on device will fail.
|
// otherwise the read operation on device will fail.
|
||||||
await ConsumableReadableStream.enqueue(
|
await Consumable.ReadableStream.enqueue(
|
||||||
controller,
|
controller,
|
||||||
init.payload,
|
init.payload,
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
type WritableStream,
|
type WritableStream,
|
||||||
type WritableStreamDefaultController,
|
type WritableStreamDefaultController,
|
||||||
} from "@yume-chan/stream-extra";
|
} from "@yume-chan/stream-extra";
|
||||||
|
import { EMPTY_UINT8_ARRAY } from "@yume-chan/struct";
|
||||||
|
|
||||||
import type { AdbSocket } from "../adb.js";
|
import type { AdbSocket } from "../adb.js";
|
||||||
|
|
||||||
|
@ -23,11 +24,15 @@ export interface AdbDaemonSocketInfo {
|
||||||
service: string;
|
service: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdbDaemonSocketConstructionOptions
|
export interface AdbDaemonSocketInit extends AdbDaemonSocketInfo {
|
||||||
extends AdbDaemonSocketInfo {
|
|
||||||
dispatcher: AdbPacketDispatcher;
|
dispatcher: AdbPacketDispatcher;
|
||||||
|
|
||||||
highWaterMark?: number | undefined;
|
highWaterMark?: number | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial delayed ack byte count, or `Infinity` if delayed ack is disabled.
|
||||||
|
*/
|
||||||
|
availableWriteBytes: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AdbDaemonSocketController
|
export class AdbDaemonSocketController
|
||||||
|
@ -72,7 +77,7 @@ export class AdbDaemonSocketController
|
||||||
*/
|
*/
|
||||||
#availableWriteBytes = 0;
|
#availableWriteBytes = 0;
|
||||||
|
|
||||||
constructor(options: AdbDaemonSocketConstructionOptions) {
|
constructor(options: AdbDaemonSocketInit) {
|
||||||
this.#dispatcher = options.dispatcher;
|
this.#dispatcher = options.dispatcher;
|
||||||
this.localId = options.localId;
|
this.localId = options.localId;
|
||||||
this.remoteId = options.remoteId;
|
this.remoteId = options.remoteId;
|
||||||
|
@ -102,6 +107,7 @@ export class AdbDaemonSocketController
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#socket = new AdbDaemonSocket(this);
|
this.#socket = new AdbDaemonSocket(this);
|
||||||
|
this.#availableWriteBytes = options.availableWriteBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #writeChunk(data: Uint8Array, signal: AbortSignal) {
|
async #writeChunk(data: Uint8Array, signal: AbortSignal) {
|
||||||
|
@ -172,6 +178,7 @@ export class AdbDaemonSocketController
|
||||||
AdbCommand.Close,
|
AdbCommand.Close,
|
||||||
this.localId,
|
this.localId,
|
||||||
this.remoteId,
|
this.remoteId,
|
||||||
|
EMPTY_UINT8_ARRAY,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,26 +56,52 @@ export type AdbDaemonConnection = ReadableWritablePair<
|
||||||
Consumable<AdbPacketInit>
|
Consumable<AdbPacketInit>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
interface AdbDaemonAuthenticationOptions {
|
export interface AdbDaemonAuthenticationOptions {
|
||||||
serial: string;
|
serial: string;
|
||||||
connection: AdbDaemonConnection;
|
connection: AdbDaemonConnection;
|
||||||
credentialStore: AdbCredentialStore;
|
credentialStore: AdbCredentialStore;
|
||||||
authenticators?: AdbAuthenticator[];
|
authenticators?: AdbAuthenticator[];
|
||||||
features?: readonly AdbFeature[];
|
features?: readonly AdbFeature[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of bytes the device can send before receiving an ack packet.
|
* The number of bytes the device can send before receiving an ack packet.
|
||||||
|
* Using delayed ack can improve the throughput,
|
||||||
|
* especially when the device is connected over Wi-Fi (so the latency is higher).
|
||||||
*
|
*
|
||||||
* Set to 0 or any negative value to disable delayed ack in handshake.
|
* Set to 0 or any negative value to disable delayed ack in handshake.
|
||||||
* Otherwise the value must be in the range of unsigned 32-bit integer.
|
* Otherwise the value must be in the range of unsigned 32-bit integer.
|
||||||
*
|
*
|
||||||
* Delayed ack requires Android 14, this option is ignored on older versions.
|
* Delayed ack was added in Android 14,
|
||||||
|
* this option will be ignored when the device doesn't support it.
|
||||||
|
*
|
||||||
|
* @default ADB_DAEMON_DEFAULT_INITIAL_PAYLOAD_SIZE
|
||||||
*/
|
*/
|
||||||
initialDelayedAckBytes?: number;
|
initialDelayedAckBytes?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to preserve the connection open after the `AdbDaemonTransport` is closed.
|
* Whether to keep the `connection` open (don't call `writable.close` and `readable.cancel`)
|
||||||
|
* when `AdbDaemonTransport.close` is called.
|
||||||
|
*
|
||||||
|
* Note that when `authenticate` fails,
|
||||||
|
* no matter which value this option has,
|
||||||
|
* the `connection` is always kept open, so it can be used in another `authenticate` call.
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
*/
|
*/
|
||||||
preserveConnection?: boolean | undefined;
|
preserveConnection?: boolean | undefined;
|
||||||
debugSlowRead?: boolean | undefined;
|
|
||||||
|
/**
|
||||||
|
* When set, the transport will throw an error when
|
||||||
|
* one of the socket readable stalls for this amount of milliseconds.
|
||||||
|
*
|
||||||
|
* Because ADB is a multiplexed protocol, blocking one socket will also block all other sockets.
|
||||||
|
* It's important to always read from all sockets to prevent stalling.
|
||||||
|
*
|
||||||
|
* This option is helpful to detect bugs in the client code.
|
||||||
|
*
|
||||||
|
* @default undefined
|
||||||
|
*/
|
||||||
|
readTimeLimit?: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AdbDaemonSocketConnectorConstructionOptions {
|
interface AdbDaemonSocketConnectorConstructionOptions {
|
||||||
|
@ -85,20 +111,39 @@ interface AdbDaemonSocketConnectorConstructionOptions {
|
||||||
maxPayloadSize: number;
|
maxPayloadSize: number;
|
||||||
banner: string;
|
banner: string;
|
||||||
features?: readonly AdbFeature[];
|
features?: readonly AdbFeature[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of bytes the device can send before receiving an ack packet.
|
* The number of bytes the device can send before receiving an ack packet.
|
||||||
|
* Using delayed ack can improve the throughput,
|
||||||
|
* especially when the device is connected over Wi-Fi (so the latency is higher).
|
||||||
*
|
*
|
||||||
* Set to 0 or any negative value to disable delayed ack in handshake.
|
* When `features` doesn't include `AdbFeature.DelayedAck`, it must be set to 0. Otherwise,
|
||||||
* Otherwise the value must be in the range of unsigned 32-bit integer.
|
* the value must be in the range of unsigned 32-bit integer. If the device enabled
|
||||||
*
|
* delayed ack but the client didn't, the device will throw an error when the client sends
|
||||||
* Delayed ack requires Android 14, this option is ignored on older versions.
|
* the first data packet. And vice versa.
|
||||||
*/
|
*/
|
||||||
initialDelayedAckBytes?: number;
|
initialDelayedAckBytes: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to preserve the connection open after the `AdbDaemonTransport` is closed.
|
* Whether to keep the `connection` open (don't call `writable.close` and `readable.cancel`)
|
||||||
|
* when `AdbDaemonTransport.close` is called.
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
*/
|
*/
|
||||||
preserveConnection?: boolean | undefined;
|
preserveConnection?: boolean | undefined;
|
||||||
debugSlowRead?: boolean | undefined;
|
|
||||||
|
/**
|
||||||
|
* When set, the transport will throw an error when
|
||||||
|
* one of the socket readable stalls for this amount of milliseconds.
|
||||||
|
*
|
||||||
|
* Because ADB is a multiplexed protocol, blocking one socket will also block all other sockets.
|
||||||
|
* It's important to always read from all sockets to prevent stalling.
|
||||||
|
*
|
||||||
|
* This option is helpful to detect bugs in the client code.
|
||||||
|
*
|
||||||
|
* @default undefined
|
||||||
|
*/
|
||||||
|
readTimeLimit?: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AdbDaemonTransport implements AdbTransport {
|
export class AdbDaemonTransport implements AdbTransport {
|
||||||
|
@ -106,9 +151,9 @@ export class AdbDaemonTransport implements AdbTransport {
|
||||||
* Authenticates the connection and creates an `AdbDaemonTransport` instance
|
* Authenticates the connection and creates an `AdbDaemonTransport` instance
|
||||||
* that can be used by `Adb` class.
|
* that can be used by `Adb` class.
|
||||||
*
|
*
|
||||||
* If an authentication process failed, it's possible to call `authenticate` again
|
* If an authentication process failed,
|
||||||
* on the same connection. Because every time the device receives a `CNXN` packet,
|
* no matter which value the `preserveConnection` option has,
|
||||||
* it resets all internal state, and starts a new authentication process.
|
* the `connection` is always kept open, so it can be used in another `authenticate` call.
|
||||||
*/
|
*/
|
||||||
static async authenticate({
|
static async authenticate({
|
||||||
serial,
|
serial,
|
||||||
|
@ -278,7 +323,7 @@ export class AdbDaemonTransport implements AdbTransport {
|
||||||
version,
|
version,
|
||||||
banner,
|
banner,
|
||||||
features = ADB_DAEMON_DEFAULT_FEATURES,
|
features = ADB_DAEMON_DEFAULT_FEATURES,
|
||||||
initialDelayedAckBytes = ADB_DAEMON_DEFAULT_INITIAL_PAYLOAD_SIZE,
|
initialDelayedAckBytes,
|
||||||
...options
|
...options
|
||||||
}: AdbDaemonSocketConnectorConstructionOptions) {
|
}: AdbDaemonSocketConnectorConstructionOptions) {
|
||||||
this.#serial = serial;
|
this.#serial = serial;
|
||||||
|
|
|
@ -27,7 +27,12 @@ import {
|
||||||
import type { AdbIncomingSocketHandler, AdbSocket, Closeable } from "../adb.js";
|
import type { AdbIncomingSocketHandler, AdbSocket, Closeable } from "../adb.js";
|
||||||
import { AdbBanner } from "../banner.js";
|
import { AdbBanner } from "../banner.js";
|
||||||
import type { AdbFeature } from "../features.js";
|
import type { AdbFeature } from "../features.js";
|
||||||
import { NOOP, hexToNumber, numberToHex, unreachable } from "../utils/index.js";
|
import {
|
||||||
|
NOOP,
|
||||||
|
hexToNumber,
|
||||||
|
unreachable,
|
||||||
|
write4HexDigits,
|
||||||
|
} from "../utils/index.js";
|
||||||
|
|
||||||
import { AdbServerTransport } from "./transport.js";
|
import { AdbServerTransport } from "./transport.js";
|
||||||
|
|
||||||
|
@ -76,6 +81,23 @@ export interface AdbServerDevice {
|
||||||
transportId: bigint;
|
transportId: bigint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sequenceEqual(a: Uint8Array, b: Uint8Array): boolean {
|
||||||
|
if (a.length !== b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < a.length; i += 1) {
|
||||||
|
if (a[i] !== b[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OKAY = encodeUtf8("OKAY");
|
||||||
|
const FAIL = encodeUtf8("FAIL");
|
||||||
|
|
||||||
export class AdbServerClient {
|
export class AdbServerClient {
|
||||||
static readonly VERSION = 41;
|
static readonly VERSION = 41;
|
||||||
|
|
||||||
|
@ -99,8 +121,27 @@ export class AdbServerClient {
|
||||||
return stream.readExactly(length);
|
return stream.readExactly(length);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((valueBuffer) => {
|
.then((buffer) => {
|
||||||
return decodeUtf8(valueBuffer);
|
// TODO: Investigate using stream mode `TextDecoder` for long strings.
|
||||||
|
// Because concatenating strings uses rope data structure,
|
||||||
|
// which only points to the original strings and doesn't copy the data,
|
||||||
|
// it's more efficient than concatenating `Uint8Array`s.
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// const decoder = new TextDecoder();
|
||||||
|
// let result = '';
|
||||||
|
// for await (const chunk of stream.iterateExactly(length)) {
|
||||||
|
// result += decoder.decode(chunk, { stream: true });
|
||||||
|
// }
|
||||||
|
// result += decoder.decode();
|
||||||
|
// return result;
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// Although, it will be super complex to use `SyncPromise` with async iterator,
|
||||||
|
// `stream.iterateExactly` need to return an
|
||||||
|
// `Iterator<Uint8Array | Promise<Uint8Array>>` instead of a true async iterator.
|
||||||
|
// Maybe `SyncPromise` should support async iterators directly.
|
||||||
|
return decodeUtf8(buffer);
|
||||||
})
|
})
|
||||||
.valueOrPromise();
|
.valueOrPromise();
|
||||||
}
|
}
|
||||||
|
@ -109,27 +150,29 @@ export class AdbServerClient {
|
||||||
writer: WritableStreamDefaultWriter<Uint8Array>,
|
writer: WritableStreamDefaultWriter<Uint8Array>,
|
||||||
value: string,
|
value: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const valueBuffer = encodeUtf8(value);
|
// TODO: investigate using `encodeUtf8("0000" + value)` then modifying the length
|
||||||
const buffer = new Uint8Array(4 + valueBuffer.length);
|
// That way allocates a new string (hopefully only a rope) instead of a new buffer
|
||||||
buffer.set(numberToHex(valueBuffer.length));
|
const encoded = encodeUtf8(value);
|
||||||
buffer.set(valueBuffer, 4);
|
const buffer = new Uint8Array(4 + encoded.length);
|
||||||
|
write4HexDigits(buffer, 0, encoded.length);
|
||||||
|
buffer.set(encoded, 4);
|
||||||
await writer.write(buffer);
|
await writer.write(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async readOkay(
|
static async readOkay(
|
||||||
stream: ExactReadable | AsyncExactReadable,
|
stream: ExactReadable | AsyncExactReadable,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const response = decodeUtf8(await stream.readExactly(4));
|
const response = await stream.readExactly(4);
|
||||||
if (response === "OKAY") {
|
if (sequenceEqual(response, OKAY)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response === "FAIL") {
|
if (sequenceEqual(response, FAIL)) {
|
||||||
const reason = await AdbServerClient.readString(stream);
|
const reason = await AdbServerClient.readString(stream);
|
||||||
throw new Error(reason);
|
throw new Error(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unexpected response: ${response}`);
|
throw new Error(`Unexpected response: ${decodeUtf8(response)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(
|
async connect(
|
||||||
|
|
|
@ -79,40 +79,51 @@ export function encodeBase64(
|
||||||
output.byteOffset + output.length - (paddingLength + 1) <=
|
output.byteOffset + output.length - (paddingLength + 1) <=
|
||||||
input.byteOffset + input.length
|
input.byteOffset + input.length
|
||||||
) {
|
) {
|
||||||
// Output ends before input ends
|
// Output ends before input ends.
|
||||||
// So output won't catch up with input.
|
// Can encode forwards, because writing output won't catch up with reading input.
|
||||||
|
|
||||||
// Depends on padding length,
|
// The output end is subtracted by `(paddingLength + 1)` because
|
||||||
// it's possible to write 1-3 bytes after input ends.
|
// depending on padding length, it's possible to write 1-3 extra bytes after input ends.
|
||||||
// spell: disable-next-line
|
|
||||||
// | aaaaaabb | | | |
|
|
||||||
// | aaaaaa | bb0000 | = | = |
|
|
||||||
//
|
//
|
||||||
// spell: disable-next-line
|
// The following diagrams show how the last read from input and the last write to output
|
||||||
// | aaaaaabb | bbbbcccc | | |
|
// are not conflicting.
|
||||||
// | aaaaaa | bbbbbb | cccc00 | = |
|
|
||||||
//
|
//
|
||||||
// spell: disable-next-line
|
// spell: disable
|
||||||
// | aaaaaabb | bbbbcccc | ccdddddd | |
|
//
|
||||||
// | aaaaaa | bbbbbb | cccccc | dddddd |
|
// `paddingLength === 2` can write 3 extra bytes:
|
||||||
|
//
|
||||||
|
// input: | aaaaaabb | | | |
|
||||||
|
// output: | aaaaaa | bb0000 | = | = |
|
||||||
|
//
|
||||||
|
// `paddingLength === 1` can write 2 extra bytes:
|
||||||
|
//
|
||||||
|
// input: | aaaaaabb | bbbbcccc | | |
|
||||||
|
// output: | aaaaaa | bbbbbb | cccc00 | = |
|
||||||
|
//
|
||||||
|
// `paddingLength === 0` can write 1 extra byte:
|
||||||
|
//
|
||||||
|
// input: | aaaaaabb | bbbbcccc | ccdddddd | |
|
||||||
|
// output: | aaaaaa | bbbbbb | cccccc | dddddd |
|
||||||
|
//
|
||||||
|
// spell: enable
|
||||||
|
|
||||||
// Must encode forwards.
|
|
||||||
encodeForward(input, output, paddingLength);
|
encodeForward(input, output, paddingLength);
|
||||||
} else if (output.byteOffset >= input.byteOffset - 1) {
|
} else if (output.byteOffset >= input.byteOffset - 1) {
|
||||||
// Output starts after input starts
|
// Output starts after input starts
|
||||||
// So in backwards, output can't catch up with input.
|
// So in backwards, writing output won't catch up with reading input.
|
||||||
|
|
||||||
// Because first 3 bytes becomes 4 bytes,
|
// The input start is subtracted by `1`, Because as the first 3 bytes becomes 4 bytes,
|
||||||
// it's possible to write 1 byte before input starts.
|
// it's possible to write 1 extra byte before input starts.
|
||||||
// spell: disable-next-line
|
// spell: disable-next-line
|
||||||
// | | aaaaaabb | bbbbcccc | ccdddddd |
|
// input: | | aaaaaabb | bbbbcccc | ccdddddd |
|
||||||
// | aaaaaa | bbbbbb | cccccc | dddddd |
|
// output: | aaaaaa | bbbbbb | cccccc | dddddd |
|
||||||
|
|
||||||
// Must encode backwards.
|
// Must encode backwards.
|
||||||
encodeBackward(input, output, paddingLength);
|
encodeBackward(input, output, paddingLength);
|
||||||
} else {
|
} else {
|
||||||
// Input is in the middle of output,
|
// Input is in the middle of output,
|
||||||
// not possible to read neither first or last three bytes,
|
// It's not possible to read either the first or the last three bytes
|
||||||
|
// before they are overwritten by the output.
|
||||||
throw new Error("input and output cannot overlap");
|
throw new Error("input and output cannot overlap");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,23 +36,25 @@ export function hexToNumber(data: Uint8Array): number {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function numberToHex(value: number) {
|
export function write4HexDigits(
|
||||||
const result = new Uint8Array(4);
|
buffer: Uint8Array,
|
||||||
let index = 3;
|
index: number,
|
||||||
while (index >= 0 && value > 0) {
|
value: number,
|
||||||
|
) {
|
||||||
|
const start = index;
|
||||||
|
index += 3;
|
||||||
|
while (index >= start && value > 0) {
|
||||||
const digit = value & 0xf;
|
const digit = value & 0xf;
|
||||||
value >>= 4;
|
value >>= 4;
|
||||||
if (digit < 10) {
|
if (digit < 10) {
|
||||||
result[index] = digit + 48;
|
buffer[index] = digit + 48; // '0'
|
||||||
} else {
|
} else {
|
||||||
result[index] = digit + 87;
|
buffer[index] = digit + 87; // 'a' - 10
|
||||||
}
|
}
|
||||||
index -= 1;
|
index -= 1;
|
||||||
}
|
}
|
||||||
while (index >= 0) {
|
while (index >= start) {
|
||||||
// '0'
|
buffer[index] = 48; // '0'
|
||||||
result[index] = 48;
|
|
||||||
index -= 1;
|
index -= 1;
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -353,6 +353,7 @@ export async function deserializeAndroidLogEntry(
|
||||||
): Promise<AndroidLogEntry> {
|
): Promise<AndroidLogEntry> {
|
||||||
const entry = (await LoggerEntry.deserialize(stream)) as AndroidLogEntry;
|
const entry = (await LoggerEntry.deserialize(stream)) as AndroidLogEntry;
|
||||||
if (entry.headerSize !== LoggerEntry.size) {
|
if (entry.headerSize !== LoggerEntry.size) {
|
||||||
|
// Skip unknown fields
|
||||||
await stream.readExactly(entry.headerSize - LoggerEntry.size);
|
await stream.readExactly(entry.headerSize - LoggerEntry.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,6 @@
|
||||||
"gh-release-fetch": "^4.0.3"
|
"gh-release-fetch": "^4.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.12.7"
|
"@types/node": "^20.12.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "^30.0.0-alpha.3",
|
"@jest/globals": "^30.0.0-alpha.3",
|
||||||
"@types/node": "^20.12.7",
|
"@types/node": "^20.12.8",
|
||||||
"@yume-chan/eslint-config": "workspace:^1.0.0",
|
"@yume-chan/eslint-config": "workspace:^1.0.0",
|
||||||
"@yume-chan/tsconfig": "workspace:^1.0.0",
|
"@yume-chan/tsconfig": "workspace:^1.0.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
|
|
@ -344,8 +344,11 @@ export class WebCodecsVideoDecoder implements ScrcpyVideoDecoder {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebCodecs requires configuration data to be with the first frame.
|
// For H.264 and H.265, when the stream is in Annex B format
|
||||||
|
// (which Scrcpy uses, as Android MediaCodec produces),
|
||||||
|
// configuration data needs to be combined with the first frame data.
|
||||||
// https://www.w3.org/TR/webcodecs-avc-codec-registration/#encodedvideochunk-type
|
// https://www.w3.org/TR/webcodecs-avc-codec-registration/#encodedvideochunk-type
|
||||||
|
// AV1 doesn't need to do this, the handling code also doesn't set `#config`.
|
||||||
let data: Uint8Array;
|
let data: Uint8Array;
|
||||||
if (this.#config !== undefined) {
|
if (this.#config !== undefined) {
|
||||||
data = new Uint8Array(
|
data = new Uint8Array(
|
||||||
|
|
|
@ -91,145 +91,6 @@ export function* annexBSplitNalu(buffer: Uint8Array): Generator<Uint8Array> {
|
||||||
yield buffer.subarray(start, buffer.length);
|
yield buffer.subarray(start, buffer.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove emulation prevention bytes from a H.264/H.265 NAL Unit.
|
|
||||||
*
|
|
||||||
* The input is not modified.
|
|
||||||
* If the input doesn't contain any emulation prevention bytes,
|
|
||||||
* the input is returned as-is.
|
|
||||||
* Otherwise, a new `Uint8Array` is created and returned.
|
|
||||||
*/
|
|
||||||
export function naluRemoveEmulation(buffer: Uint8Array) {
|
|
||||||
// output will be created when first emulation prevention byte is found
|
|
||||||
let output: Uint8Array | undefined;
|
|
||||||
let outputOffset = 0;
|
|
||||||
|
|
||||||
let zeroCount = 0;
|
|
||||||
let inEmulation = false;
|
|
||||||
|
|
||||||
let i = 0;
|
|
||||||
scan: for (; i < buffer.length; i += 1) {
|
|
||||||
const byte = buffer[i]!;
|
|
||||||
|
|
||||||
if (byte === 0x00) {
|
|
||||||
zeroCount += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current byte is not zero
|
|
||||||
const prevZeroCount = zeroCount;
|
|
||||||
zeroCount = 0;
|
|
||||||
|
|
||||||
if (prevZeroCount < 2) {
|
|
||||||
// zero or one `0x00`s are acceptable
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (byte === 0x01) {
|
|
||||||
// Unexpected start code
|
|
||||||
throw new Error("Invalid data");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevZeroCount > 2) {
|
|
||||||
// Too much `0x00`s
|
|
||||||
throw new Error("Invalid data");
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (byte) {
|
|
||||||
case 0x02:
|
|
||||||
// Didn't find why, but 7.4.1 NAL unit semantics forbids `0x000002` appearing in NAL units
|
|
||||||
throw new Error("Invalid data");
|
|
||||||
case 0x03:
|
|
||||||
// `0x000003` is the "emulation_prevention_three_byte"
|
|
||||||
// `0x00000300`, `0x00000301`, `0x00000302` and `0x00000303` represent
|
|
||||||
// `0x000000`, `0x000001`, `0x000002` and `0x000003` respectively
|
|
||||||
inEmulation = true;
|
|
||||||
|
|
||||||
// Create output and copy the data before the emulation prevention byte
|
|
||||||
// Output size is unknown, so we use the input size as an upper bound
|
|
||||||
output = new Uint8Array(buffer.length - 1);
|
|
||||||
output.set(buffer.subarray(0, i));
|
|
||||||
outputOffset = i;
|
|
||||||
i += 1;
|
|
||||||
break scan;
|
|
||||||
default:
|
|
||||||
// `0x000004` or larger are as-is
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!output) {
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Continue at the byte after the emulation prevention byte
|
|
||||||
for (; i < buffer.length; i += 1) {
|
|
||||||
const byte = buffer[i]!;
|
|
||||||
|
|
||||||
output[outputOffset] = byte;
|
|
||||||
outputOffset += 1;
|
|
||||||
|
|
||||||
if (inEmulation) {
|
|
||||||
if (byte > 0x03) {
|
|
||||||
// `0x00000304` or larger are invalid
|
|
||||||
throw new Error("Invalid data");
|
|
||||||
}
|
|
||||||
|
|
||||||
// `00000300000300` results in `0000000000` (both `0x03` are removed)
|
|
||||||
// which means the `0x00` after `0x03` also counts
|
|
||||||
if (byte === 0x00) {
|
|
||||||
zeroCount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
inEmulation = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (byte === 0x00) {
|
|
||||||
zeroCount += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const prevZeroCount = zeroCount;
|
|
||||||
zeroCount = 0;
|
|
||||||
|
|
||||||
if (prevZeroCount < 2) {
|
|
||||||
// zero or one `0x00`s are acceptable
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (byte === 0x01) {
|
|
||||||
// Unexpected start code
|
|
||||||
throw new Error("Invalid data");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevZeroCount > 2) {
|
|
||||||
// Too much `0x00`s
|
|
||||||
throw new Error("Invalid data");
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (byte) {
|
|
||||||
case 0x02:
|
|
||||||
// Didn't find why, but 7.4.1 NAL unit semantics forbids `0x000002` appearing in NAL units
|
|
||||||
throw new Error("Invalid data");
|
|
||||||
case 0x03:
|
|
||||||
// `0x000003` is the "emulation_prevention_three_byte"
|
|
||||||
// `0x00000300`, `0x00000301`, `0x00000302` and `0x00000303` represent
|
|
||||||
// `0x000000`, `0x000001`, `0x000002` and `0x000003` respectively
|
|
||||||
inEmulation = true;
|
|
||||||
|
|
||||||
// Remove the emulation prevention byte
|
|
||||||
outputOffset -= 1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// `0x000004` or larger are as-is
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return output.subarray(0, outputOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NaluSodbBitReader {
|
export class NaluSodbBitReader {
|
||||||
readonly #nalu: Uint8Array;
|
readonly #nalu: Uint8Array;
|
||||||
// logical length is `#byteLength * 8 + (7 - #stopBitIndex)`
|
// logical length is `#byteLength * 8 + (7 - #stopBitIndex)`
|
||||||
|
@ -269,6 +130,7 @@ export class NaluSodbBitReader {
|
||||||
constructor(nalu: Uint8Array) {
|
constructor(nalu: Uint8Array) {
|
||||||
this.#nalu = nalu;
|
this.#nalu = nalu;
|
||||||
|
|
||||||
|
// Search for the last bit being `1`, also known as the stop bit
|
||||||
for (let i = nalu.length - 1; i >= 0; i -= 1) {
|
for (let i = nalu.length - 1; i >= 0; i -= 1) {
|
||||||
if (this.#nalu[i] === 0) {
|
if (this.#nalu[i] === 0) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -292,16 +154,18 @@ export class NaluSodbBitReader {
|
||||||
this.#byte = this.#nalu[this.#bytePosition]!;
|
this.#byte = this.#nalu[this.#bytePosition]!;
|
||||||
|
|
||||||
// If the current sequence is `0x000003`, skip to the next byte.
|
// If the current sequence is `0x000003`, skip to the next byte.
|
||||||
// `annexBSplitNalu` had validated the input, so don't need to check here.
|
// `annexBSplitNalu` had validated the input, so skip the check here
|
||||||
if (this.#zeroCount === 2 && this.#byte === 3) {
|
if (this.#zeroCount === 2 && this.#byte === 3) {
|
||||||
this.#zeroCount = 0;
|
this.#zeroCount = 0;
|
||||||
this.#bytePosition += 1;
|
this.#bytePosition += 1;
|
||||||
|
// Call `#loadByte` again, because if the next byte is `0x00`,
|
||||||
|
// it need to be counted in `#zeroCount` as well.
|
||||||
this.#loadByte();
|
this.#loadByte();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// `0x00000301` becomes `0x000001`, so only the `0x03` byte needs to be skipped
|
// `0x00000301` becomes `0x000001`, so only the `0x03` byte needs to be skipped
|
||||||
// The `0x00` bytes are still returned as-is
|
// All `0x00` bytes are returned as-is
|
||||||
if (this.#byte === 0) {
|
if (this.#byte === 0) {
|
||||||
this.#zeroCount += 1;
|
this.#zeroCount += 1;
|
||||||
} else {
|
} else {
|
||||||
|
@ -338,7 +202,13 @@ export class NaluSodbBitReader {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ensurePositionValid() {
|
#checkSkipPosition() {
|
||||||
|
// This is different from `ended`,
|
||||||
|
// as it allows the bit position to be at the stop bit.
|
||||||
|
// In this case, there is no more bits to read, `ended` is `true`,
|
||||||
|
// and the next `next` call will throw an error.
|
||||||
|
// However, it's still a valid position for `skip`, which can skip all remaining bits,
|
||||||
|
// and stop at the end position.
|
||||||
if (
|
if (
|
||||||
this.#bytePosition >= this.#byteLength &&
|
this.#bytePosition >= this.#byteLength &&
|
||||||
this.#bitPosition < this.#stopBitIndex
|
this.#bitPosition < this.#stopBitIndex
|
||||||
|
@ -350,24 +220,29 @@ export class NaluSodbBitReader {
|
||||||
skip(length: number) {
|
skip(length: number) {
|
||||||
if (length <= this.#bitPosition + 1) {
|
if (length <= this.#bitPosition + 1) {
|
||||||
this.#bitPosition -= length;
|
this.#bitPosition -= length;
|
||||||
this.#ensurePositionValid();
|
this.#checkSkipPosition();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Because of emulation prevention bytes,
|
||||||
|
// we don't know how many bits are left in the NAL,
|
||||||
|
// nor how many bits should be skipped.
|
||||||
|
// So we need to check each byte.
|
||||||
|
|
||||||
length -= this.#bitPosition + 1;
|
length -= this.#bitPosition + 1;
|
||||||
this.#bytePosition += 1;
|
this.#bytePosition += 1;
|
||||||
this.#bitPosition = 7;
|
this.#bitPosition = 7;
|
||||||
this.#loadByte();
|
this.#loadByte();
|
||||||
this.#ensurePositionValid();
|
this.#checkSkipPosition();
|
||||||
|
|
||||||
for (; length >= 8; length -= 8) {
|
for (; length >= 8; length -= 8) {
|
||||||
this.#bytePosition += 1;
|
this.#bytePosition += 1;
|
||||||
this.#loadByte();
|
this.#loadByte();
|
||||||
this.#ensurePositionValid();
|
this.#checkSkipPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#bitPosition = 7 - length;
|
this.#bitPosition = 7 - length;
|
||||||
this.#ensurePositionValid();
|
this.#checkSkipPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
decodeExponentialGolombNumber(): number {
|
decodeExponentialGolombNumber(): number {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { getUint16 } from "@yume-chan/no-data-view";
|
import { getUint16 } from "@yume-chan/no-data-view";
|
||||||
import type { NumberFieldType } from "@yume-chan/struct";
|
import type { NumberFieldVariant } from "@yume-chan/struct";
|
||||||
import { NumberFieldDefinition } from "@yume-chan/struct";
|
import { NumberFieldDefinition } from "@yume-chan/struct";
|
||||||
|
|
||||||
export function clamp(value: number, min: number, max: number): number {
|
export function clamp(value: number, min: number, max: number): number {
|
||||||
|
@ -14,7 +14,7 @@ export function clamp(value: number, min: number, max: number): number {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ScrcpyFloatToUint16NumberType: NumberFieldType = {
|
export const ScrcpyUnsignedFloatNumberVariant: NumberFieldVariant = {
|
||||||
size: 2,
|
size: 2,
|
||||||
signed: false,
|
signed: false,
|
||||||
deserialize(array, littleEndian) {
|
deserialize(array, littleEndian) {
|
||||||
|
@ -30,6 +30,6 @@ export const ScrcpyFloatToUint16NumberType: NumberFieldType = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ScrcpyFloatToUint16FieldDefinition = new NumberFieldDefinition(
|
export const ScrcpyUnsignedFloatFieldDefinition = new NumberFieldDefinition(
|
||||||
ScrcpyFloatToUint16NumberType,
|
ScrcpyUnsignedFloatNumberVariant,
|
||||||
);
|
);
|
|
@ -1,5 +1,5 @@
|
||||||
export * from "./codec-options.js";
|
export * from "./codec-options.js";
|
||||||
export * from "./float-to-uint16.js";
|
export * from "./float.js";
|
||||||
export * from "./init.js";
|
export * from "./init.js";
|
||||||
export * from "./message.js";
|
export * from "./message.js";
|
||||||
export * from "./options.js";
|
export * from "./options.js";
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
ScrcpyControlMessageType,
|
ScrcpyControlMessageType,
|
||||||
} from "../../control/index.js";
|
} from "../../control/index.js";
|
||||||
|
|
||||||
import { ScrcpyFloatToUint16FieldDefinition } from "./float-to-uint16.js";
|
import { ScrcpyUnsignedFloatFieldDefinition } from "./float.js";
|
||||||
|
|
||||||
export const SCRCPY_CONTROL_MESSAGE_TYPES_1_16: readonly ScrcpyControlMessageType[] =
|
export const SCRCPY_CONTROL_MESSAGE_TYPES_1_16: readonly ScrcpyControlMessageType[] =
|
||||||
[
|
[
|
||||||
|
@ -38,7 +38,7 @@ export const ScrcpyInjectTouchControlMessage1_16 = new Struct()
|
||||||
.uint32("pointerY")
|
.uint32("pointerY")
|
||||||
.uint16("screenWidth")
|
.uint16("screenWidth")
|
||||||
.uint16("screenHeight")
|
.uint16("screenHeight")
|
||||||
.field("pressure", ScrcpyFloatToUint16FieldDefinition)
|
.field("pressure", ScrcpyUnsignedFloatFieldDefinition)
|
||||||
.uint32("buttons");
|
.uint32("buttons");
|
||||||
|
|
||||||
export type ScrcpyInjectTouchControlMessage1_16 =
|
export type ScrcpyInjectTouchControlMessage1_16 =
|
||||||
|
|
|
@ -83,13 +83,20 @@ export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
|
||||||
return order.map((key) => toScrcpyOptionValue(options[key], "-"));
|
return order.map((key) => toScrcpyOptionValue(options[key], "-"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a fixed-length, null-terminated string.
|
||||||
|
* @param stream The stream to read from
|
||||||
|
* @param maxLength The maximum length of the string, including the null terminator, in bytes
|
||||||
|
* @returns The parsed string, without the null terminator
|
||||||
|
*/
|
||||||
static async parseCString(
|
static async parseCString(
|
||||||
stream: AsyncExactReadable,
|
stream: AsyncExactReadable,
|
||||||
maxLength: number,
|
maxLength: number,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
let result = decodeUtf8(await stream.readExactly(maxLength));
|
const buffer = await stream.readExactly(maxLength);
|
||||||
result = result.substring(0, result.indexOf("\0"));
|
// If null terminator is not found, `subarray(0, -1)` will remove the last byte
|
||||||
return result;
|
// But since it's a invalid case, it's fine
|
||||||
|
return decodeUtf8(buffer.subarray(0, buffer.indexOf(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static async parseUint16BE(stream: AsyncExactReadable): Promise<number> {
|
static async parseUint16BE(stream: AsyncExactReadable): Promise<number> {
|
||||||
|
|
|
@ -3,29 +3,29 @@ import { describe, expect, it } from "@jest/globals";
|
||||||
import { ScrcpyControlMessageType } from "../../control/index.js";
|
import { ScrcpyControlMessageType } from "../../control/index.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ScrcpyFloatToInt16NumberType,
|
|
||||||
ScrcpyScrollController1_25,
|
ScrcpyScrollController1_25,
|
||||||
|
ScrcpySignedFloatNumberVariant,
|
||||||
} from "./scroll.js";
|
} from "./scroll.js";
|
||||||
|
|
||||||
describe("ScrcpyFloatToInt16NumberType", () => {
|
describe("ScrcpyFloatToInt16NumberType", () => {
|
||||||
it("should serialize", () => {
|
it("should serialize", () => {
|
||||||
const dataView = new DataView(new ArrayBuffer(2));
|
const dataView = new DataView(new ArrayBuffer(2));
|
||||||
ScrcpyFloatToInt16NumberType.serialize(dataView, 0, -1, true);
|
ScrcpySignedFloatNumberVariant.serialize(dataView, 0, -1, true);
|
||||||
expect(dataView.getInt16(0, true)).toBe(-0x8000);
|
expect(dataView.getInt16(0, true)).toBe(-0x8000);
|
||||||
|
|
||||||
ScrcpyFloatToInt16NumberType.serialize(dataView, 0, 0, true);
|
ScrcpySignedFloatNumberVariant.serialize(dataView, 0, 0, true);
|
||||||
expect(dataView.getInt16(0, true)).toBe(0);
|
expect(dataView.getInt16(0, true)).toBe(0);
|
||||||
|
|
||||||
ScrcpyFloatToInt16NumberType.serialize(dataView, 0, 1, true);
|
ScrcpySignedFloatNumberVariant.serialize(dataView, 0, 1, true);
|
||||||
expect(dataView.getInt16(0, true)).toBe(0x7fff);
|
expect(dataView.getInt16(0, true)).toBe(0x7fff);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should clamp input values", () => {
|
it("should clamp input values", () => {
|
||||||
const dataView = new DataView(new ArrayBuffer(2));
|
const dataView = new DataView(new ArrayBuffer(2));
|
||||||
ScrcpyFloatToInt16NumberType.serialize(dataView, 0, -2, true);
|
ScrcpySignedFloatNumberVariant.serialize(dataView, 0, -2, true);
|
||||||
expect(dataView.getInt16(0, true)).toBe(-0x8000);
|
expect(dataView.getInt16(0, true)).toBe(-0x8000);
|
||||||
|
|
||||||
ScrcpyFloatToInt16NumberType.serialize(dataView, 0, 2, true);
|
ScrcpySignedFloatNumberVariant.serialize(dataView, 0, 2, true);
|
||||||
expect(dataView.getInt16(0, true)).toBe(0x7fff);
|
expect(dataView.getInt16(0, true)).toBe(0x7fff);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -34,13 +34,13 @@ describe("ScrcpyFloatToInt16NumberType", () => {
|
||||||
const view = new Uint8Array(dataView.buffer);
|
const view = new Uint8Array(dataView.buffer);
|
||||||
|
|
||||||
dataView.setInt16(0, -0x8000, true);
|
dataView.setInt16(0, -0x8000, true);
|
||||||
expect(ScrcpyFloatToInt16NumberType.deserialize(view, true)).toBe(-1);
|
expect(ScrcpySignedFloatNumberVariant.deserialize(view, true)).toBe(-1);
|
||||||
|
|
||||||
dataView.setInt16(0, 0, true);
|
dataView.setInt16(0, 0, true);
|
||||||
expect(ScrcpyFloatToInt16NumberType.deserialize(view, true)).toBe(0);
|
expect(ScrcpySignedFloatNumberVariant.deserialize(view, true)).toBe(0);
|
||||||
|
|
||||||
dataView.setInt16(0, 0x7fff, true);
|
dataView.setInt16(0, 0x7fff, true);
|
||||||
expect(ScrcpyFloatToInt16NumberType.deserialize(view, true)).toBe(1);
|
expect(ScrcpySignedFloatNumberVariant.deserialize(view, true)).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { getInt16 } from "@yume-chan/no-data-view";
|
import { getInt16 } from "@yume-chan/no-data-view";
|
||||||
import type { NumberFieldType } from "@yume-chan/struct";
|
import type { NumberFieldVariant } from "@yume-chan/struct";
|
||||||
import Struct, { NumberFieldDefinition } from "@yume-chan/struct";
|
import Struct, { NumberFieldDefinition } from "@yume-chan/struct";
|
||||||
|
|
||||||
import type { ScrcpyInjectScrollControlMessage } from "../../control/index.js";
|
import type { ScrcpyInjectScrollControlMessage } from "../../control/index.js";
|
||||||
|
@ -7,7 +7,7 @@ import { ScrcpyControlMessageType } from "../../control/index.js";
|
||||||
import type { ScrcpyScrollController } from "../1_16/index.js";
|
import type { ScrcpyScrollController } from "../1_16/index.js";
|
||||||
import { clamp } from "../1_16/index.js";
|
import { clamp } from "../1_16/index.js";
|
||||||
|
|
||||||
export const ScrcpyFloatToInt16NumberType: NumberFieldType = {
|
export const ScrcpySignedFloatNumberVariant: NumberFieldVariant = {
|
||||||
size: 2,
|
size: 2,
|
||||||
signed: true,
|
signed: true,
|
||||||
deserialize(array, littleEndian) {
|
deserialize(array, littleEndian) {
|
||||||
|
@ -23,8 +23,8 @@ export const ScrcpyFloatToInt16NumberType: NumberFieldType = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const ScrcpyFloatToInt16FieldDefinition = new NumberFieldDefinition(
|
const ScrcpySignedFloatFieldDefinition = new NumberFieldDefinition(
|
||||||
ScrcpyFloatToInt16NumberType,
|
ScrcpySignedFloatNumberVariant,
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ScrcpyInjectScrollControlMessage1_25 = new Struct()
|
export const ScrcpyInjectScrollControlMessage1_25 = new Struct()
|
||||||
|
@ -33,8 +33,8 @@ export const ScrcpyInjectScrollControlMessage1_25 = new Struct()
|
||||||
.uint32("pointerY")
|
.uint32("pointerY")
|
||||||
.uint16("screenWidth")
|
.uint16("screenWidth")
|
||||||
.uint16("screenHeight")
|
.uint16("screenHeight")
|
||||||
.field("scrollX", ScrcpyFloatToInt16FieldDefinition)
|
.field("scrollX", ScrcpySignedFloatFieldDefinition)
|
||||||
.field("scrollY", ScrcpyFloatToInt16FieldDefinition)
|
.field("scrollY", ScrcpySignedFloatFieldDefinition)
|
||||||
.int32("buttons");
|
.int32("buttons");
|
||||||
|
|
||||||
export type ScrcpyInjectScrollControlMessage1_25 =
|
export type ScrcpyInjectScrollControlMessage1_25 =
|
||||||
|
|
|
@ -14,8 +14,8 @@ import type {
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CodecOptions,
|
CodecOptions,
|
||||||
ScrcpyFloatToUint16FieldDefinition,
|
|
||||||
ScrcpyOptions1_16,
|
ScrcpyOptions1_16,
|
||||||
|
ScrcpyUnsignedFloatFieldDefinition,
|
||||||
} from "./1_16/index.js";
|
} from "./1_16/index.js";
|
||||||
import { ScrcpyOptions1_21 } from "./1_21.js";
|
import { ScrcpyOptions1_21 } from "./1_21.js";
|
||||||
import type { ScrcpyOptionsInit1_24 } from "./1_24.js";
|
import type { ScrcpyOptionsInit1_24 } from "./1_24.js";
|
||||||
|
@ -38,7 +38,7 @@ export const ScrcpyInjectTouchControlMessage2_0 = new Struct()
|
||||||
.uint32("pointerY")
|
.uint32("pointerY")
|
||||||
.uint16("screenWidth")
|
.uint16("screenWidth")
|
||||||
.uint16("screenHeight")
|
.uint16("screenHeight")
|
||||||
.field("pressure", ScrcpyFloatToUint16FieldDefinition)
|
.field("pressure", ScrcpyUnsignedFloatFieldDefinition)
|
||||||
.uint32("actionButton")
|
.uint32("actionButton")
|
||||||
.uint32("buttons");
|
.uint32("buttons");
|
||||||
|
|
||||||
|
@ -107,6 +107,58 @@ export class ScrcpyOptions2_0 extends ScrcpyOptionsBase<
|
||||||
ScrcpyOptionsInit2_0,
|
ScrcpyOptionsInit2_0,
|
||||||
ScrcpyOptions1_25
|
ScrcpyOptions1_25
|
||||||
> {
|
> {
|
||||||
|
static async parseAudioMetadata(
|
||||||
|
stream: ReadableStream<Uint8Array>,
|
||||||
|
sendCodecMeta: boolean,
|
||||||
|
mapMetadata: (value: number) => ScrcpyAudioCodec,
|
||||||
|
getOptionCodec: () => ScrcpyAudioCodec,
|
||||||
|
): Promise<ScrcpyAudioStreamMetadata> {
|
||||||
|
const buffered = new BufferedReadableStream(stream);
|
||||||
|
|
||||||
|
const buffer = await buffered.readExactly(4);
|
||||||
|
// Treat it as a 32-bit number for simpler comparisons
|
||||||
|
const codecMetadataValue = getUint32BigEndian(buffer, 0);
|
||||||
|
// Server will send `0x00_00_00_00` and `0x00_00_00_01` even if `sendCodecMeta` is false
|
||||||
|
switch (codecMetadataValue) {
|
||||||
|
case 0x00_00_00_00:
|
||||||
|
return {
|
||||||
|
type: "disabled",
|
||||||
|
};
|
||||||
|
case 0x00_00_00_01:
|
||||||
|
return {
|
||||||
|
type: "errored",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendCodecMeta) {
|
||||||
|
return {
|
||||||
|
type: "success",
|
||||||
|
codec: mapMetadata(codecMetadataValue),
|
||||||
|
stream: buffered.release(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "success",
|
||||||
|
// Infer codec from `audioCodec` option
|
||||||
|
codec: getOptionCodec(),
|
||||||
|
stream: new PushReadableStream<Uint8Array>(async (controller) => {
|
||||||
|
// Put the first 4 bytes back
|
||||||
|
await controller.enqueue(buffer);
|
||||||
|
|
||||||
|
const stream = buffered.release();
|
||||||
|
const reader = stream.getReader();
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await controller.enqueue(value);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static readonly DEFAULTS = {
|
static readonly DEFAULTS = {
|
||||||
...omit(ScrcpyOptions1_24.DEFAULTS, [
|
...omit(ScrcpyOptions1_24.DEFAULTS, [
|
||||||
"bitRate",
|
"bitRate",
|
||||||
|
@ -255,81 +307,34 @@ export class ScrcpyOptions2_0 extends ScrcpyOptionsBase<
|
||||||
override parseAudioStreamMetadata(
|
override parseAudioStreamMetadata(
|
||||||
stream: ReadableStream<Uint8Array>,
|
stream: ReadableStream<Uint8Array>,
|
||||||
): ValueOrPromise<ScrcpyAudioStreamMetadata> {
|
): ValueOrPromise<ScrcpyAudioStreamMetadata> {
|
||||||
return (async (): Promise<ScrcpyAudioStreamMetadata> => {
|
return ScrcpyOptions2_0.parseAudioMetadata(
|
||||||
const buffered = new BufferedReadableStream(stream);
|
stream,
|
||||||
const buffer = await buffered.readExactly(4);
|
this.value.sendCodecMeta,
|
||||||
|
(value) => {
|
||||||
const codecMetadataValue = getUint32BigEndian(buffer, 0);
|
switch (value) {
|
||||||
// Server will send `0x00_00_00_00` and `0x00_00_00_01` even if `sendCodecMeta` is false
|
|
||||||
switch (codecMetadataValue) {
|
|
||||||
case 0x00_00_00_00:
|
|
||||||
return {
|
|
||||||
type: "disabled",
|
|
||||||
};
|
|
||||||
case 0x00_00_00_01:
|
|
||||||
return {
|
|
||||||
type: "errored",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.value.sendCodecMeta) {
|
|
||||||
let codec: ScrcpyAudioCodec;
|
|
||||||
switch (codecMetadataValue) {
|
|
||||||
case ScrcpyAudioCodec.OPUS.metadataValue:
|
|
||||||
codec = ScrcpyAudioCodec.OPUS;
|
|
||||||
break;
|
|
||||||
case ScrcpyAudioCodec.AAC.metadataValue:
|
|
||||||
codec = ScrcpyAudioCodec.AAC;
|
|
||||||
break;
|
|
||||||
case ScrcpyAudioCodec.RAW.metadataValue:
|
case ScrcpyAudioCodec.RAW.metadataValue:
|
||||||
codec = ScrcpyAudioCodec.RAW;
|
return ScrcpyAudioCodec.RAW;
|
||||||
break;
|
case ScrcpyAudioCodec.OPUS.metadataValue:
|
||||||
|
return ScrcpyAudioCodec.OPUS;
|
||||||
|
case ScrcpyAudioCodec.AAC.metadataValue:
|
||||||
|
return ScrcpyAudioCodec.AAC;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unknown audio codec metadata value: ${codecMetadataValue}`,
|
`Unknown audio codec metadata value: ${value}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return {
|
},
|
||||||
type: "success",
|
() => {
|
||||||
codec,
|
switch (this.value.audioCodec) {
|
||||||
stream: buffered.release(),
|
case "raw":
|
||||||
};
|
return ScrcpyAudioCodec.RAW;
|
||||||
}
|
case "opus":
|
||||||
|
return ScrcpyAudioCodec.OPUS;
|
||||||
// Infer codec from `audioCodec` option
|
case "aac":
|
||||||
let codec: ScrcpyAudioCodec;
|
return ScrcpyAudioCodec.AAC;
|
||||||
switch (this.value.audioCodec) {
|
}
|
||||||
case "opus":
|
},
|
||||||
codec = ScrcpyAudioCodec.OPUS;
|
);
|
||||||
break;
|
|
||||||
case "aac":
|
|
||||||
codec = ScrcpyAudioCodec.AAC;
|
|
||||||
break;
|
|
||||||
case "raw":
|
|
||||||
codec = ScrcpyAudioCodec.RAW;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: "success",
|
|
||||||
codec,
|
|
||||||
stream: new PushReadableStream<Uint8Array>(
|
|
||||||
async (controller) => {
|
|
||||||
// Put the first 4 bytes back
|
|
||||||
await controller.enqueue(buffer);
|
|
||||||
|
|
||||||
const stream = buffered.release();
|
|
||||||
const reader = stream.getReader();
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await reader.read();
|
|
||||||
if (done) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
await controller.enqueue(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override serializeInjectTouchControlMessage(
|
override serializeInjectTouchControlMessage(
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
|
import type { ReadableStream } from "@yume-chan/stream-extra";
|
||||||
import { ScrcpyOptions1_21 } from "./1_21.js";
|
import { ScrcpyOptions1_21 } from "./1_21.js";
|
||||||
import { omit } from "./2_0.js";
|
import { ScrcpyOptions2_0, omit } from "./2_0.js";
|
||||||
import { ScrcpyOptions2_2, type ScrcpyOptionsInit2_2 } from "./2_2.js";
|
import { ScrcpyOptions2_2, type ScrcpyOptionsInit2_2 } from "./2_2.js";
|
||||||
import { ScrcpyOptionsBase } from "./types.js";
|
import { ScrcpyOptionsBase } from "./types.js";
|
||||||
|
import type { ValueOrPromise } from "@yume-chan/struct";
|
||||||
|
import { ScrcpyAudioCodec, type ScrcpyAudioStreamMetadata } from "./codec.js";
|
||||||
|
|
||||||
export interface ScrcpyOptionsInit2_3
|
export interface ScrcpyOptionsInit2_3
|
||||||
extends Omit<ScrcpyOptionsInit2_2, "audioCodec"> {
|
extends Omit<ScrcpyOptionsInit2_2, "audioCodec"> {
|
||||||
audioCodec?: "raw" | "opus" | "aac" | "flac" | undefined;
|
audioCodec?: "raw" | "opus" | "aac" | "flac";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ScrcpyOptions2_3 extends ScrcpyOptionsBase<
|
export class ScrcpyOptions2_3 extends ScrcpyOptionsBase<
|
||||||
|
@ -30,4 +33,41 @@ export class ScrcpyOptions2_3 extends ScrcpyOptionsBase<
|
||||||
override serialize(): string[] {
|
override serialize(): string[] {
|
||||||
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
|
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override parseAudioStreamMetadata(
|
||||||
|
stream: ReadableStream<Uint8Array>,
|
||||||
|
): ValueOrPromise<ScrcpyAudioStreamMetadata> {
|
||||||
|
return ScrcpyOptions2_0.parseAudioMetadata(
|
||||||
|
stream,
|
||||||
|
this.value.sendCodecMeta,
|
||||||
|
(value) => {
|
||||||
|
switch (value) {
|
||||||
|
case ScrcpyAudioCodec.RAW.metadataValue:
|
||||||
|
return ScrcpyAudioCodec.RAW;
|
||||||
|
case ScrcpyAudioCodec.OPUS.metadataValue:
|
||||||
|
return ScrcpyAudioCodec.OPUS;
|
||||||
|
case ScrcpyAudioCodec.AAC.metadataValue:
|
||||||
|
return ScrcpyAudioCodec.AAC;
|
||||||
|
case ScrcpyAudioCodec.FLAC.metadataValue:
|
||||||
|
return ScrcpyAudioCodec.FLAC;
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unknown audio codec metadata value: ${value}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
switch (this.value.audioCodec) {
|
||||||
|
case "raw":
|
||||||
|
return ScrcpyAudioCodec.RAW;
|
||||||
|
case "opus":
|
||||||
|
return ScrcpyAudioCodec.OPUS;
|
||||||
|
case "aac":
|
||||||
|
return ScrcpyAudioCodec.AAC;
|
||||||
|
case "flac":
|
||||||
|
return ScrcpyAudioCodec.FLAC;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { PromiseResolver } from "@yume-chan/async";
|
import { PromiseResolver } from "@yume-chan/async";
|
||||||
|
import { EMPTY_UINT8_ARRAY } from "@yume-chan/struct";
|
||||||
|
|
||||||
import type { ReadableStreamDefaultController } from "./stream.js";
|
import type { ReadableStreamDefaultController } from "./stream.js";
|
||||||
import { ReadableStream, WritableStream } from "./stream.js";
|
import { ReadableStream, WritableStream } from "./stream.js";
|
||||||
|
@ -83,7 +84,8 @@ export interface ConcatBufferReadableStream
|
||||||
* If you want to decode the result as string,
|
* If you want to decode the result as string,
|
||||||
* prefer `.pipeThrough(new DecodeUtf8Stream()).pipeThrough(new ConcatStringStream())`,
|
* prefer `.pipeThrough(new DecodeUtf8Stream()).pipeThrough(new ConcatStringStream())`,
|
||||||
* than `.pipeThough(new ConcatBufferStream()).pipeThrough(new DecodeUtf8Stream())`,
|
* than `.pipeThough(new ConcatBufferStream()).pipeThrough(new DecodeUtf8Stream())`,
|
||||||
* because concatenating strings is faster than concatenating `Uint8Array`s.
|
* because of JavaScript engine optimizations,
|
||||||
|
* concatenating strings is faster than concatenating `Uint8Array`s.
|
||||||
*/
|
*/
|
||||||
export class ConcatBufferStream {
|
export class ConcatBufferStream {
|
||||||
#segments: Uint8Array[] = [];
|
#segments: Uint8Array[] = [];
|
||||||
|
@ -99,7 +101,7 @@ export class ConcatBufferStream {
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
switch (this.#segments.length) {
|
switch (this.#segments.length) {
|
||||||
case 0:
|
case 0:
|
||||||
result = new Uint8Array(0);
|
result = EMPTY_UINT8_ARRAY;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
result = this.#segments[0]!;
|
result = this.#segments[0]!;
|
||||||
|
|
|
@ -7,30 +7,9 @@ import type {
|
||||||
} from "./stream.js";
|
} from "./stream.js";
|
||||||
import {
|
import {
|
||||||
WritableStream as NativeWritableStream,
|
WritableStream as NativeWritableStream,
|
||||||
ReadableStream,
|
ReadableStream as NativeReadableStream,
|
||||||
} from "./stream.js";
|
} from "./stream.js";
|
||||||
|
import { createTask, type Task } from "./task.js";
|
||||||
interface Task {
|
|
||||||
run<T>(callback: () => T): T;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Console {
|
|
||||||
createTask(name: string): Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GlobalExtension {
|
|
||||||
console: Console;
|
|
||||||
}
|
|
||||||
|
|
||||||
// `createTask` allows browser DevTools to track the call stack across async boundaries.
|
|
||||||
const { console } = globalThis as unknown as GlobalExtension;
|
|
||||||
const createTask: Console["createTask"] =
|
|
||||||
console.createTask?.bind(console) ??
|
|
||||||
(() => ({
|
|
||||||
run(callback) {
|
|
||||||
return callback();
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
||||||
return typeof value === "object" && value !== null && "then" in value;
|
return typeof value === "object" && value !== null && "then" in value;
|
||||||
|
@ -148,83 +127,78 @@ export namespace Consumable {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConsumableReadableStreamController<T> {
|
export interface ReadableStreamController<T> {
|
||||||
enqueue(chunk: T): Promise<void>;
|
enqueue(chunk: T): Promise<void>;
|
||||||
close(): void;
|
close(): void;
|
||||||
error(reason: unknown): void;
|
error(reason: unknown): void;
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConsumableReadableStreamSource<T> {
|
|
||||||
start?(
|
|
||||||
controller: ConsumableReadableStreamController<T>,
|
|
||||||
): void | PromiseLike<void>;
|
|
||||||
pull?(
|
|
||||||
controller: ConsumableReadableStreamController<T>,
|
|
||||||
): void | PromiseLike<void>;
|
|
||||||
cancel?(reason: unknown): void | PromiseLike<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ConsumableReadableStream<T> extends ReadableStream<Consumable<T>> {
|
|
||||||
static async enqueue<T>(
|
|
||||||
controller: { enqueue: (chunk: Consumable<T>) => void },
|
|
||||||
chunk: T,
|
|
||||||
) {
|
|
||||||
const output = new Consumable(chunk);
|
|
||||||
controller.enqueue(output);
|
|
||||||
await output.consumed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
export interface ReadableStreamSource<T> {
|
||||||
source: ConsumableReadableStreamSource<T>,
|
start?(
|
||||||
strategy?: QueuingStrategy<T>,
|
controller: ReadableStreamController<T>,
|
||||||
) {
|
): void | PromiseLike<void>;
|
||||||
let wrappedController:
|
pull?(
|
||||||
| ConsumableReadableStreamController<T>
|
controller: ReadableStreamController<T>,
|
||||||
| undefined;
|
): void | PromiseLike<void>;
|
||||||
|
cancel?(reason: unknown): void | PromiseLike<void>;
|
||||||
|
}
|
||||||
|
|
||||||
let wrappedStrategy: QueuingStrategy<Consumable<T>> | undefined;
|
export class ReadableStream<T> extends NativeReadableStream<Consumable<T>> {
|
||||||
if (strategy) {
|
static async enqueue<T>(
|
||||||
wrappedStrategy = {};
|
controller: { enqueue: (chunk: Consumable<T>) => void },
|
||||||
if ("highWaterMark" in strategy) {
|
chunk: T,
|
||||||
wrappedStrategy.highWaterMark = strategy.highWaterMark;
|
) {
|
||||||
}
|
const output = new Consumable(chunk);
|
||||||
if ("size" in strategy) {
|
controller.enqueue(output);
|
||||||
wrappedStrategy.size = (chunk) => {
|
await output.consumed;
|
||||||
return strategy.size!(chunk.value);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
super(
|
constructor(
|
||||||
{
|
source: ReadableStreamSource<T>,
|
||||||
async start(controller) {
|
strategy?: QueuingStrategy<T>,
|
||||||
wrappedController = {
|
) {
|
||||||
async enqueue(chunk) {
|
let wrappedController: ReadableStreamController<T> | undefined;
|
||||||
await ConsumableReadableStream.enqueue(
|
|
||||||
controller,
|
|
||||||
chunk,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
controller.close();
|
|
||||||
},
|
|
||||||
error(reason) {
|
|
||||||
controller.error(reason);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
await source.start?.(wrappedController);
|
let wrappedStrategy: QueuingStrategy<Consumable<T>> | undefined;
|
||||||
|
if (strategy) {
|
||||||
|
wrappedStrategy = {};
|
||||||
|
if ("highWaterMark" in strategy) {
|
||||||
|
wrappedStrategy.highWaterMark = strategy.highWaterMark;
|
||||||
|
}
|
||||||
|
if ("size" in strategy) {
|
||||||
|
wrappedStrategy.size = (chunk) => {
|
||||||
|
return strategy.size!(chunk.value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
async start(controller) {
|
||||||
|
wrappedController = {
|
||||||
|
async enqueue(chunk) {
|
||||||
|
await ReadableStream.enqueue(controller, chunk);
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
controller.close();
|
||||||
|
},
|
||||||
|
error(reason) {
|
||||||
|
controller.error(reason);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await source.start?.(wrappedController);
|
||||||
|
},
|
||||||
|
async pull() {
|
||||||
|
await source.pull?.(wrappedController!);
|
||||||
|
},
|
||||||
|
async cancel(reason) {
|
||||||
|
await source.cancel?.(reason);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
async pull() {
|
wrappedStrategy,
|
||||||
await source.pull?.(wrappedController!);
|
);
|
||||||
},
|
}
|
||||||
async cancel(reason) {
|
|
||||||
await source.cancel?.(reason);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wrappedStrategy,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { describe, expect, it, jest } from "@jest/globals";
|
import { describe, expect, it, jest } from "@jest/globals";
|
||||||
|
|
||||||
import { ConsumableReadableStream } from "./consumable.js";
|
import { Consumable } from "./consumable.js";
|
||||||
import { DistributionStream } from "./distribution.js";
|
import { DistributionStream } from "./distribution.js";
|
||||||
import { MaybeConsumable } from "./maybe-consumable.js";
|
import { MaybeConsumable } from "./maybe-consumable.js";
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ async function testInputOutput(
|
||||||
const write = jest.fn((chunk: Uint8Array) => {
|
const write = jest.fn((chunk: Uint8Array) => {
|
||||||
void chunk;
|
void chunk;
|
||||||
});
|
});
|
||||||
await new ConsumableReadableStream<Uint8Array>({
|
await new Consumable.ReadableStream<Uint8Array>({
|
||||||
async start(controller) {
|
async start(controller) {
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
for (const length of inputLengths) {
|
for (const length of inputLengths) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ConsumableReadableStream } from "./consumable.js";
|
import { Consumable } from "./consumable.js";
|
||||||
import { MaybeConsumable } from "./maybe-consumable.js";
|
import { MaybeConsumable } from "./maybe-consumable.js";
|
||||||
import { TransformStream } from "./stream.js";
|
import { TransformStream } from "./stream.js";
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ export class DistributionStream extends TransformStream<
|
||||||
await MaybeConsumable.tryConsume(chunk, async (chunk) => {
|
await MaybeConsumable.tryConsume(chunk, async (chunk) => {
|
||||||
if (combiner) {
|
if (combiner) {
|
||||||
for (const buffer of combiner.push(chunk)) {
|
for (const buffer of combiner.push(chunk)) {
|
||||||
await ConsumableReadableStream.enqueue(
|
await Consumable.ReadableStream.enqueue(
|
||||||
controller,
|
controller,
|
||||||
buffer,
|
buffer,
|
||||||
);
|
);
|
||||||
|
@ -100,7 +100,7 @@ export class DistributionStream extends TransformStream<
|
||||||
let available = chunk.byteLength;
|
let available = chunk.byteLength;
|
||||||
while (available > 0) {
|
while (available > 0) {
|
||||||
const end = offset + size;
|
const end = offset + size;
|
||||||
await ConsumableReadableStream.enqueue(
|
await Consumable.ReadableStream.enqueue(
|
||||||
controller,
|
controller,
|
||||||
chunk.subarray(offset, end),
|
chunk.subarray(offset, end),
|
||||||
);
|
);
|
||||||
|
|
|
@ -13,5 +13,6 @@ export * from "./split-string.js";
|
||||||
export * from "./stream.js";
|
export * from "./stream.js";
|
||||||
export * from "./struct-deserialize.js";
|
export * from "./struct-deserialize.js";
|
||||||
export * from "./struct-serialize.js";
|
export * from "./struct-serialize.js";
|
||||||
|
export * from "./task.js";
|
||||||
export * from "./wrap-readable.js";
|
export * from "./wrap-readable.js";
|
||||||
export * from "./wrap-writable.js";
|
export * from "./wrap-writable.js";
|
||||||
|
|
21
libraries/stream-extra/src/task.ts
Normal file
21
libraries/stream-extra/src/task.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
export interface Task {
|
||||||
|
run<T>(callback: () => T): T;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Console {
|
||||||
|
createTask(name: string): Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GlobalExtension {
|
||||||
|
console?: Console;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `createTask` allows browser DevTools to track the call stack across async boundaries.
|
||||||
|
const global = globalThis as unknown as GlobalExtension;
|
||||||
|
export const createTask: (name: string) => Task =
|
||||||
|
global.console?.createTask?.bind(global.console) ??
|
||||||
|
(() => ({
|
||||||
|
run(callback) {
|
||||||
|
return callback();
|
||||||
|
},
|
||||||
|
}));
|
|
@ -13,11 +13,11 @@ import type { ValueOrPromise } from "./utils.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BigIntFieldDefinition,
|
BigIntFieldDefinition,
|
||||||
BigIntFieldType,
|
BigIntFieldVariant,
|
||||||
BufferFieldSubType,
|
BufferFieldConverter,
|
||||||
FixedLengthBufferLikeFieldDefinition,
|
FixedLengthBufferLikeFieldDefinition,
|
||||||
NumberFieldDefinition,
|
NumberFieldDefinition,
|
||||||
NumberFieldType,
|
NumberFieldVariant,
|
||||||
VariableLengthBufferLikeFieldDefinition,
|
VariableLengthBufferLikeFieldDefinition,
|
||||||
} from "./index.js";
|
} from "./index.js";
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ describe("Struct", () => {
|
||||||
|
|
||||||
const definition = struct.fields[0]![1] as NumberFieldDefinition;
|
const definition = struct.fields[0]![1] as NumberFieldDefinition;
|
||||||
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
||||||
expect(definition.type).toBe(NumberFieldType.Int8);
|
expect(definition.variant).toBe(NumberFieldVariant.Int8);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("`uint8` should append an `uint8` field", () => {
|
it("`uint8` should append an `uint8` field", () => {
|
||||||
|
@ -130,7 +130,7 @@ describe("Struct", () => {
|
||||||
|
|
||||||
const definition = struct.fields[0]![1] as NumberFieldDefinition;
|
const definition = struct.fields[0]![1] as NumberFieldDefinition;
|
||||||
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
||||||
expect(definition.type).toBe(NumberFieldType.Uint8);
|
expect(definition.variant).toBe(NumberFieldVariant.Uint8);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("`int16` should append an `int16` field", () => {
|
it("`int16` should append an `int16` field", () => {
|
||||||
|
@ -140,7 +140,7 @@ describe("Struct", () => {
|
||||||
|
|
||||||
const definition = struct.fields[0]![1] as NumberFieldDefinition;
|
const definition = struct.fields[0]![1] as NumberFieldDefinition;
|
||||||
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
||||||
expect(definition.type).toBe(NumberFieldType.Int16);
|
expect(definition.variant).toBe(NumberFieldVariant.Int16);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("`uint16` should append an `uint16` field", () => {
|
it("`uint16` should append an `uint16` field", () => {
|
||||||
|
@ -150,7 +150,7 @@ describe("Struct", () => {
|
||||||
|
|
||||||
const definition = struct.fields[0]![1] as NumberFieldDefinition;
|
const definition = struct.fields[0]![1] as NumberFieldDefinition;
|
||||||
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
||||||
expect(definition.type).toBe(NumberFieldType.Uint16);
|
expect(definition.variant).toBe(NumberFieldVariant.Uint16);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("`int32` should append an `int32` field", () => {
|
it("`int32` should append an `int32` field", () => {
|
||||||
|
@ -160,7 +160,7 @@ describe("Struct", () => {
|
||||||
|
|
||||||
const definition = struct.fields[0]![1] as NumberFieldDefinition;
|
const definition = struct.fields[0]![1] as NumberFieldDefinition;
|
||||||
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
||||||
expect(definition.type).toBe(NumberFieldType.Int32);
|
expect(definition.variant).toBe(NumberFieldVariant.Int32);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("`uint32` should append an `uint32` field", () => {
|
it("`uint32` should append an `uint32` field", () => {
|
||||||
|
@ -170,7 +170,7 @@ describe("Struct", () => {
|
||||||
|
|
||||||
const definition = struct.fields[0]![1] as NumberFieldDefinition;
|
const definition = struct.fields[0]![1] as NumberFieldDefinition;
|
||||||
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
expect(definition).toBeInstanceOf(NumberFieldDefinition);
|
||||||
expect(definition.type).toBe(NumberFieldType.Uint32);
|
expect(definition.variant).toBe(NumberFieldVariant.Uint32);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("`int64` should append an `int64` field", () => {
|
it("`int64` should append an `int64` field", () => {
|
||||||
|
@ -180,7 +180,7 @@ describe("Struct", () => {
|
||||||
|
|
||||||
const definition = struct.fields[0]![1] as BigIntFieldDefinition;
|
const definition = struct.fields[0]![1] as BigIntFieldDefinition;
|
||||||
expect(definition).toBeInstanceOf(BigIntFieldDefinition);
|
expect(definition).toBeInstanceOf(BigIntFieldDefinition);
|
||||||
expect(definition.type).toBe(BigIntFieldType.Int64);
|
expect(definition.variant).toBe(BigIntFieldVariant.Int64);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("`uint64` should append an `uint64` field", () => {
|
it("`uint64` should append an `uint64` field", () => {
|
||||||
|
@ -190,7 +190,7 @@ describe("Struct", () => {
|
||||||
|
|
||||||
const definition = struct.fields[0]![1] as BigIntFieldDefinition;
|
const definition = struct.fields[0]![1] as BigIntFieldDefinition;
|
||||||
expect(definition).toBeInstanceOf(BigIntFieldDefinition);
|
expect(definition).toBeInstanceOf(BigIntFieldDefinition);
|
||||||
expect(definition.type).toBe(BigIntFieldType.Uint64);
|
expect(definition.variant).toBe(BigIntFieldVariant.Uint64);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#uint8ArrayLike", () => {
|
describe("#uint8ArrayLike", () => {
|
||||||
|
@ -205,7 +205,9 @@ describe("Struct", () => {
|
||||||
expect(definition).toBeInstanceOf(
|
expect(definition).toBeInstanceOf(
|
||||||
FixedLengthBufferLikeFieldDefinition,
|
FixedLengthBufferLikeFieldDefinition,
|
||||||
);
|
);
|
||||||
expect(definition.type).toBeInstanceOf(BufferFieldSubType);
|
expect(definition.converter).toBeInstanceOf(
|
||||||
|
BufferFieldConverter,
|
||||||
|
);
|
||||||
expect(definition.options.length).toBe(10);
|
expect(definition.options.length).toBe(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -219,7 +221,9 @@ describe("Struct", () => {
|
||||||
expect(definition).toBeInstanceOf(
|
expect(definition).toBeInstanceOf(
|
||||||
FixedLengthBufferLikeFieldDefinition,
|
FixedLengthBufferLikeFieldDefinition,
|
||||||
);
|
);
|
||||||
expect(definition.type).toBeInstanceOf(BufferFieldSubType);
|
expect(definition.converter).toBeInstanceOf(
|
||||||
|
BufferFieldConverter,
|
||||||
|
);
|
||||||
expect(definition.options.length).toBe(10);
|
expect(definition.options.length).toBe(10);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -237,7 +241,9 @@ describe("Struct", () => {
|
||||||
expect(definition).toBeInstanceOf(
|
expect(definition).toBeInstanceOf(
|
||||||
VariableLengthBufferLikeFieldDefinition,
|
VariableLengthBufferLikeFieldDefinition,
|
||||||
);
|
);
|
||||||
expect(definition.type).toBeInstanceOf(BufferFieldSubType);
|
expect(definition.converter).toBeInstanceOf(
|
||||||
|
BufferFieldConverter,
|
||||||
|
);
|
||||||
expect(definition.options.lengthField).toBe("barLength");
|
expect(definition.options.lengthField).toBe("barLength");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -253,7 +259,9 @@ describe("Struct", () => {
|
||||||
expect(definition).toBeInstanceOf(
|
expect(definition).toBeInstanceOf(
|
||||||
VariableLengthBufferLikeFieldDefinition,
|
VariableLengthBufferLikeFieldDefinition,
|
||||||
);
|
);
|
||||||
expect(definition.type).toBeInstanceOf(BufferFieldSubType);
|
expect(definition.converter).toBeInstanceOf(
|
||||||
|
BufferFieldConverter,
|
||||||
|
);
|
||||||
expect(definition.options.lengthField).toBe("barLength");
|
expect(definition.options.lengthField).toBe("barLength");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -270,19 +278,31 @@ describe("Struct", () => {
|
||||||
|
|
||||||
const field0 = struct.fields[0]!;
|
const field0 = struct.fields[0]!;
|
||||||
expect(field0).toHaveProperty("0", "int8");
|
expect(field0).toHaveProperty("0", "int8");
|
||||||
expect(field0[1]).toHaveProperty("type", NumberFieldType.Int8);
|
expect(field0[1]).toHaveProperty(
|
||||||
|
"variant",
|
||||||
|
NumberFieldVariant.Int8,
|
||||||
|
);
|
||||||
|
|
||||||
const field1 = struct.fields[1]!;
|
const field1 = struct.fields[1]!;
|
||||||
expect(field1).toHaveProperty("0", "int16");
|
expect(field1).toHaveProperty("0", "int16");
|
||||||
expect(field1[1]).toHaveProperty("type", NumberFieldType.Int16);
|
expect(field1[1]).toHaveProperty(
|
||||||
|
"variant",
|
||||||
|
NumberFieldVariant.Int16,
|
||||||
|
);
|
||||||
|
|
||||||
const field2 = struct.fields[2]!;
|
const field2 = struct.fields[2]!;
|
||||||
expect(field2).toHaveProperty("0", "int32");
|
expect(field2).toHaveProperty("0", "int32");
|
||||||
expect(field2[1]).toHaveProperty("type", NumberFieldType.Int32);
|
expect(field2[1]).toHaveProperty(
|
||||||
|
"variant",
|
||||||
|
NumberFieldVariant.Int32,
|
||||||
|
);
|
||||||
|
|
||||||
const field3 = struct.fields[3]!;
|
const field3 = struct.fields[3]!;
|
||||||
expect(field3).toHaveProperty("0", "int64");
|
expect(field3).toHaveProperty("0", "int64");
|
||||||
expect(field3[1]).toHaveProperty("type", BigIntFieldType.Int64);
|
expect(field3[1]).toHaveProperty(
|
||||||
|
"variant",
|
||||||
|
BigIntFieldVariant.Int64,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -16,19 +16,19 @@ import {
|
||||||
} from "./basic/index.js";
|
} from "./basic/index.js";
|
||||||
import { SyncPromise } from "./sync-promise.js";
|
import { SyncPromise } from "./sync-promise.js";
|
||||||
import type {
|
import type {
|
||||||
BufferFieldSubType,
|
BufferFieldConverter,
|
||||||
FixedLengthBufferLikeFieldOptions,
|
FixedLengthBufferLikeFieldOptions,
|
||||||
LengthField,
|
LengthField,
|
||||||
VariableLengthBufferLikeFieldOptions,
|
VariableLengthBufferLikeFieldOptions,
|
||||||
} from "./types/index.js";
|
} from "./types/index.js";
|
||||||
import {
|
import {
|
||||||
BigIntFieldDefinition,
|
BigIntFieldDefinition,
|
||||||
BigIntFieldType,
|
BigIntFieldVariant,
|
||||||
FixedLengthBufferLikeFieldDefinition,
|
FixedLengthBufferLikeFieldDefinition,
|
||||||
NumberFieldDefinition,
|
NumberFieldDefinition,
|
||||||
NumberFieldType,
|
NumberFieldVariant,
|
||||||
StringBufferFieldSubType,
|
StringBufferFieldConverter,
|
||||||
Uint8ArrayBufferFieldSubType,
|
Uint8ArrayBufferFieldConverter,
|
||||||
VariableLengthBufferLikeFieldDefinition,
|
VariableLengthBufferLikeFieldDefinition,
|
||||||
} from "./types/index.js";
|
} from "./types/index.js";
|
||||||
import type { Evaluate, Identity, Overwrite, ValueOrPromise } from "./utils.js";
|
import type { Evaluate, Identity, Overwrite, ValueOrPromise } from "./utils.js";
|
||||||
|
@ -86,7 +86,7 @@ interface ArrayBufferLikeFieldCreator<
|
||||||
*/
|
*/
|
||||||
<
|
<
|
||||||
TName extends PropertyKey,
|
TName extends PropertyKey,
|
||||||
TType extends BufferFieldSubType<unknown, unknown>,
|
TType extends BufferFieldConverter<unknown, unknown>,
|
||||||
TTypeScriptType = TType["TTypeScriptType"],
|
TTypeScriptType = TType["TTypeScriptType"],
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
|
@ -110,7 +110,7 @@ interface ArrayBufferLikeFieldCreator<
|
||||||
*/
|
*/
|
||||||
<
|
<
|
||||||
TName extends PropertyKey,
|
TName extends PropertyKey,
|
||||||
TType extends BufferFieldSubType<unknown, unknown>,
|
TType extends BufferFieldConverter<unknown, unknown>,
|
||||||
TOptions extends VariableLengthBufferLikeFieldOptions<TFields>,
|
TOptions extends VariableLengthBufferLikeFieldOptions<TFields>,
|
||||||
TTypeScriptType = TType["TTypeScriptType"],
|
TTypeScriptType = TType["TTypeScriptType"],
|
||||||
>(
|
>(
|
||||||
|
@ -136,7 +136,7 @@ interface BoundArrayBufferLikeFieldDefinitionCreator<
|
||||||
TOmitInitKey extends PropertyKey,
|
TOmitInitKey extends PropertyKey,
|
||||||
TExtra extends object,
|
TExtra extends object,
|
||||||
TPostDeserialized,
|
TPostDeserialized,
|
||||||
TType extends BufferFieldSubType<unknown, unknown>,
|
TType extends BufferFieldConverter<unknown, unknown>,
|
||||||
> {
|
> {
|
||||||
<TName extends PropertyKey, TTypeScriptType = TType["TTypeScriptType"]>(
|
<TName extends PropertyKey, TTypeScriptType = TType["TTypeScriptType"]>(
|
||||||
name: TName,
|
name: TName,
|
||||||
|
@ -351,7 +351,7 @@ export class Struct<
|
||||||
|
|
||||||
#number<
|
#number<
|
||||||
TName extends PropertyKey,
|
TName extends PropertyKey,
|
||||||
TType extends NumberFieldType = NumberFieldType,
|
TType extends NumberFieldVariant = NumberFieldVariant,
|
||||||
TTypeScriptType = number,
|
TTypeScriptType = number,
|
||||||
>(name: TName, type: TType, typeScriptType?: TTypeScriptType) {
|
>(name: TName, type: TType, typeScriptType?: TTypeScriptType) {
|
||||||
return this.field(
|
return this.field(
|
||||||
|
@ -367,7 +367,7 @@ export class Struct<
|
||||||
name: TName,
|
name: TName,
|
||||||
typeScriptType?: TTypeScriptType,
|
typeScriptType?: TTypeScriptType,
|
||||||
) {
|
) {
|
||||||
return this.#number(name, NumberFieldType.Int8, typeScriptType);
|
return this.#number(name, NumberFieldVariant.Int8, typeScriptType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -377,7 +377,7 @@ export class Struct<
|
||||||
name: TName,
|
name: TName,
|
||||||
typeScriptType?: TTypeScriptType,
|
typeScriptType?: TTypeScriptType,
|
||||||
) {
|
) {
|
||||||
return this.#number(name, NumberFieldType.Uint8, typeScriptType);
|
return this.#number(name, NumberFieldVariant.Uint8, typeScriptType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -387,7 +387,7 @@ export class Struct<
|
||||||
name: TName,
|
name: TName,
|
||||||
typeScriptType?: TTypeScriptType,
|
typeScriptType?: TTypeScriptType,
|
||||||
) {
|
) {
|
||||||
return this.#number(name, NumberFieldType.Int16, typeScriptType);
|
return this.#number(name, NumberFieldVariant.Int16, typeScriptType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -397,7 +397,7 @@ export class Struct<
|
||||||
name: TName,
|
name: TName,
|
||||||
typeScriptType?: TTypeScriptType,
|
typeScriptType?: TTypeScriptType,
|
||||||
) {
|
) {
|
||||||
return this.#number(name, NumberFieldType.Uint16, typeScriptType);
|
return this.#number(name, NumberFieldVariant.Uint16, typeScriptType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -407,7 +407,7 @@ export class Struct<
|
||||||
name: TName,
|
name: TName,
|
||||||
typeScriptType?: TTypeScriptType,
|
typeScriptType?: TTypeScriptType,
|
||||||
) {
|
) {
|
||||||
return this.#number(name, NumberFieldType.Int32, typeScriptType);
|
return this.#number(name, NumberFieldVariant.Int32, typeScriptType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -417,12 +417,12 @@ export class Struct<
|
||||||
name: TName,
|
name: TName,
|
||||||
typeScriptType?: TTypeScriptType,
|
typeScriptType?: TTypeScriptType,
|
||||||
) {
|
) {
|
||||||
return this.#number(name, NumberFieldType.Uint32, typeScriptType);
|
return this.#number(name, NumberFieldVariant.Uint32, typeScriptType);
|
||||||
}
|
}
|
||||||
|
|
||||||
#bigint<
|
#bigint<
|
||||||
TName extends PropertyKey,
|
TName extends PropertyKey,
|
||||||
TType extends BigIntFieldType = BigIntFieldType,
|
TType extends BigIntFieldVariant = BigIntFieldVariant,
|
||||||
TTypeScriptType = TType["TTypeScriptType"],
|
TTypeScriptType = TType["TTypeScriptType"],
|
||||||
>(name: TName, type: TType, typeScriptType?: TTypeScriptType) {
|
>(name: TName, type: TType, typeScriptType?: TTypeScriptType) {
|
||||||
return this.field(
|
return this.field(
|
||||||
|
@ -438,9 +438,9 @@ export class Struct<
|
||||||
*/
|
*/
|
||||||
int64<
|
int64<
|
||||||
TName extends PropertyKey,
|
TName extends PropertyKey,
|
||||||
TTypeScriptType = BigIntFieldType["TTypeScriptType"],
|
TTypeScriptType = BigIntFieldVariant["TTypeScriptType"],
|
||||||
>(name: TName, typeScriptType?: TTypeScriptType) {
|
>(name: TName, typeScriptType?: TTypeScriptType) {
|
||||||
return this.#bigint(name, BigIntFieldType.Int64, typeScriptType);
|
return this.#bigint(name, BigIntFieldVariant.Int64, typeScriptType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -450,9 +450,9 @@ export class Struct<
|
||||||
*/
|
*/
|
||||||
uint64<
|
uint64<
|
||||||
TName extends PropertyKey,
|
TName extends PropertyKey,
|
||||||
TTypeScriptType = BigIntFieldType["TTypeScriptType"],
|
TTypeScriptType = BigIntFieldVariant["TTypeScriptType"],
|
||||||
>(name: TName, typeScriptType?: TTypeScriptType) {
|
>(name: TName, typeScriptType?: TTypeScriptType) {
|
||||||
return this.#bigint(name, BigIntFieldType.Uint64, typeScriptType);
|
return this.#bigint(name, BigIntFieldVariant.Uint64, typeScriptType);
|
||||||
}
|
}
|
||||||
|
|
||||||
#arrayBufferLike: ArrayBufferLikeFieldCreator<
|
#arrayBufferLike: ArrayBufferLikeFieldCreator<
|
||||||
|
@ -462,7 +462,7 @@ export class Struct<
|
||||||
TPostDeserialized
|
TPostDeserialized
|
||||||
> = (
|
> = (
|
||||||
name: PropertyKey,
|
name: PropertyKey,
|
||||||
type: BufferFieldSubType,
|
type: BufferFieldConverter,
|
||||||
options:
|
options:
|
||||||
| FixedLengthBufferLikeFieldOptions
|
| FixedLengthBufferLikeFieldOptions
|
||||||
| VariableLengthBufferLikeFieldOptions,
|
| VariableLengthBufferLikeFieldOptions,
|
||||||
|
@ -485,7 +485,7 @@ export class Struct<
|
||||||
TOmitInitKey,
|
TOmitInitKey,
|
||||||
TExtra,
|
TExtra,
|
||||||
TPostDeserialized,
|
TPostDeserialized,
|
||||||
Uint8ArrayBufferFieldSubType
|
Uint8ArrayBufferFieldConverter
|
||||||
> = (
|
> = (
|
||||||
name: PropertyKey,
|
name: PropertyKey,
|
||||||
options: unknown,
|
options: unknown,
|
||||||
|
@ -493,7 +493,7 @@ export class Struct<
|
||||||
): never => {
|
): never => {
|
||||||
return this.#arrayBufferLike(
|
return this.#arrayBufferLike(
|
||||||
name,
|
name,
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
options as never,
|
options as never,
|
||||||
typeScriptType,
|
typeScriptType,
|
||||||
) as never;
|
) as never;
|
||||||
|
@ -504,7 +504,7 @@ export class Struct<
|
||||||
TOmitInitKey,
|
TOmitInitKey,
|
||||||
TExtra,
|
TExtra,
|
||||||
TPostDeserialized,
|
TPostDeserialized,
|
||||||
StringBufferFieldSubType
|
StringBufferFieldConverter
|
||||||
> = (
|
> = (
|
||||||
name: PropertyKey,
|
name: PropertyKey,
|
||||||
options: unknown,
|
options: unknown,
|
||||||
|
@ -512,7 +512,7 @@ export class Struct<
|
||||||
): never => {
|
): never => {
|
||||||
return this.#arrayBufferLike(
|
return this.#arrayBufferLike(
|
||||||
name,
|
name,
|
||||||
StringBufferFieldSubType.Instance,
|
StringBufferFieldConverter.Instance,
|
||||||
options as never,
|
options as never,
|
||||||
typeScriptType,
|
typeScriptType,
|
||||||
) as never;
|
) as never;
|
||||||
|
|
|
@ -14,53 +14,57 @@ import { StructFieldDefinition, StructFieldValue } from "../basic/index.js";
|
||||||
import { SyncPromise } from "../sync-promise.js";
|
import { SyncPromise } from "../sync-promise.js";
|
||||||
import type { ValueOrPromise } from "../utils.js";
|
import type { ValueOrPromise } from "../utils.js";
|
||||||
|
|
||||||
type GetBigInt64 = (
|
export type BigIntDeserializer = (
|
||||||
array: Uint8Array,
|
array: Uint8Array,
|
||||||
byteOffset: number,
|
byteOffset: number,
|
||||||
littleEndian: boolean,
|
littleEndian: boolean,
|
||||||
) => bigint;
|
) => bigint;
|
||||||
|
|
||||||
type SetBigInt64 = (
|
export type BigIntSerializer = (
|
||||||
array: Uint8Array,
|
array: Uint8Array,
|
||||||
byteOffset: number,
|
byteOffset: number,
|
||||||
value: bigint,
|
value: bigint,
|
||||||
littleEndian: boolean,
|
littleEndian: boolean,
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
export class BigIntFieldType {
|
export class BigIntFieldVariant {
|
||||||
readonly TTypeScriptType!: bigint;
|
readonly TTypeScriptType!: bigint;
|
||||||
|
|
||||||
readonly size: number;
|
readonly size: number;
|
||||||
|
|
||||||
readonly getter: GetBigInt64;
|
readonly deserialize: BigIntDeserializer;
|
||||||
|
|
||||||
readonly setter: SetBigInt64;
|
readonly serialize: BigIntSerializer;
|
||||||
|
|
||||||
constructor(size: number, getter: GetBigInt64, setter: SetBigInt64) {
|
constructor(
|
||||||
|
size: number,
|
||||||
|
deserialize: BigIntDeserializer,
|
||||||
|
serialize: BigIntSerializer,
|
||||||
|
) {
|
||||||
this.size = size;
|
this.size = size;
|
||||||
this.getter = getter;
|
this.deserialize = deserialize;
|
||||||
this.setter = setter;
|
this.serialize = serialize;
|
||||||
}
|
}
|
||||||
|
|
||||||
static readonly Int64 = new BigIntFieldType(8, getInt64, setInt64);
|
static readonly Int64 = new BigIntFieldVariant(8, getInt64, setInt64);
|
||||||
|
|
||||||
static readonly Uint64 = new BigIntFieldType(8, getUint64, setUint64);
|
static readonly Uint64 = new BigIntFieldVariant(8, getUint64, setUint64);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BigIntFieldDefinition<
|
export class BigIntFieldDefinition<
|
||||||
TType extends BigIntFieldType = BigIntFieldType,
|
TVariant extends BigIntFieldVariant = BigIntFieldVariant,
|
||||||
TTypeScriptType = TType["TTypeScriptType"],
|
TTypeScriptType = TVariant["TTypeScriptType"],
|
||||||
> extends StructFieldDefinition<void, TTypeScriptType> {
|
> extends StructFieldDefinition<void, TTypeScriptType> {
|
||||||
readonly type: TType;
|
readonly variant: TVariant;
|
||||||
|
|
||||||
constructor(type: TType, typescriptType?: TTypeScriptType) {
|
constructor(variant: TVariant, typescriptType?: TTypeScriptType) {
|
||||||
void typescriptType;
|
void typescriptType;
|
||||||
super();
|
super();
|
||||||
this.type = type;
|
this.variant = variant;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSize(): number {
|
getSize(): number {
|
||||||
return this.type.size;
|
return this.variant.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
create(
|
create(
|
||||||
|
@ -90,7 +94,11 @@ export class BigIntFieldDefinition<
|
||||||
return stream.readExactly(this.getSize());
|
return stream.readExactly(this.getSize());
|
||||||
})
|
})
|
||||||
.then((array) => {
|
.then((array) => {
|
||||||
const value = this.type.getter(array, 0, options.littleEndian);
|
const value = this.variant.deserialize(
|
||||||
|
array,
|
||||||
|
0,
|
||||||
|
options.littleEndian,
|
||||||
|
);
|
||||||
return this.create(options, struct, value as never);
|
return this.create(options, struct, value as never);
|
||||||
})
|
})
|
||||||
.valueOrPromise();
|
.valueOrPromise();
|
||||||
|
@ -98,14 +106,14 @@ export class BigIntFieldDefinition<
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BigIntFieldValue<
|
export class BigIntFieldValue<
|
||||||
TDefinition extends BigIntFieldDefinition<BigIntFieldType, unknown>,
|
TDefinition extends BigIntFieldDefinition<BigIntFieldVariant, unknown>,
|
||||||
> extends StructFieldValue<TDefinition> {
|
> extends StructFieldValue<TDefinition> {
|
||||||
override serialize(
|
override serialize(
|
||||||
dataView: DataView,
|
dataView: DataView,
|
||||||
array: Uint8Array,
|
array: Uint8Array,
|
||||||
offset: number,
|
offset: number,
|
||||||
): void {
|
): void {
|
||||||
this.definition.type.setter(
|
this.definition.variant.serialize(
|
||||||
array,
|
array,
|
||||||
offset,
|
offset,
|
||||||
this.value as never,
|
this.value as never,
|
||||||
|
|
|
@ -3,12 +3,12 @@ import { describe, expect, it, jest } from "@jest/globals";
|
||||||
import type { ExactReadable } from "../../basic/index.js";
|
import type { ExactReadable } from "../../basic/index.js";
|
||||||
import { StructDefaultOptions, StructValue } from "../../basic/index.js";
|
import { StructDefaultOptions, StructValue } from "../../basic/index.js";
|
||||||
|
|
||||||
import type { BufferFieldSubType } from "./base.js";
|
import type { BufferFieldConverter } from "./base.js";
|
||||||
import {
|
import {
|
||||||
BufferLikeFieldDefinition,
|
BufferLikeFieldDefinition,
|
||||||
EMPTY_UINT8_ARRAY,
|
EMPTY_UINT8_ARRAY,
|
||||||
StringBufferFieldSubType,
|
StringBufferFieldConverter,
|
||||||
Uint8ArrayBufferFieldSubType,
|
Uint8ArrayBufferFieldConverter,
|
||||||
} from "./base.js";
|
} from "./base.js";
|
||||||
|
|
||||||
class MockDeserializationStream implements ExactReadable {
|
class MockDeserializationStream implements ExactReadable {
|
||||||
|
@ -23,37 +23,37 @@ describe("Types", () => {
|
||||||
describe("Buffer", () => {
|
describe("Buffer", () => {
|
||||||
describe("Uint8ArrayBufferFieldSubType", () => {
|
describe("Uint8ArrayBufferFieldSubType", () => {
|
||||||
it("should have a static instance", () => {
|
it("should have a static instance", () => {
|
||||||
expect(Uint8ArrayBufferFieldSubType.Instance).toBeInstanceOf(
|
expect(Uint8ArrayBufferFieldConverter.Instance).toBeInstanceOf(
|
||||||
Uint8ArrayBufferFieldSubType,
|
Uint8ArrayBufferFieldConverter,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("`#toBuffer` should return the same `Uint8Array`", () => {
|
it("`#toBuffer` should return the same `Uint8Array`", () => {
|
||||||
const array = new Uint8Array(10);
|
const array = new Uint8Array(10);
|
||||||
expect(
|
expect(
|
||||||
Uint8ArrayBufferFieldSubType.Instance.toBuffer(array),
|
Uint8ArrayBufferFieldConverter.Instance.toBuffer(array),
|
||||||
).toBe(array);
|
).toBe(array);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("`#fromBuffer` should return the same `Uint8Array`", () => {
|
it("`#fromBuffer` should return the same `Uint8Array`", () => {
|
||||||
const buffer = new Uint8Array(10);
|
const buffer = new Uint8Array(10);
|
||||||
expect(
|
expect(
|
||||||
Uint8ArrayBufferFieldSubType.Instance.toValue(buffer),
|
Uint8ArrayBufferFieldConverter.Instance.toValue(buffer),
|
||||||
).toBe(buffer);
|
).toBe(buffer);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("`#getSize` should return the `byteLength` of the `Uint8Array`", () => {
|
it("`#getSize` should return the `byteLength` of the `Uint8Array`", () => {
|
||||||
const array = new Uint8Array(10);
|
const array = new Uint8Array(10);
|
||||||
expect(
|
expect(
|
||||||
Uint8ArrayBufferFieldSubType.Instance.getSize(array),
|
Uint8ArrayBufferFieldConverter.Instance.getSize(array),
|
||||||
).toBe(10);
|
).toBe(10);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("StringBufferFieldSubType", () => {
|
describe("StringBufferFieldSubType", () => {
|
||||||
it("should have a static instance", () => {
|
it("should have a static instance", () => {
|
||||||
expect(StringBufferFieldSubType.Instance).toBeInstanceOf(
|
expect(StringBufferFieldConverter.Instance).toBeInstanceOf(
|
||||||
StringBufferFieldSubType,
|
StringBufferFieldConverter,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -61,25 +61,27 @@ describe("Types", () => {
|
||||||
const text = "foo";
|
const text = "foo";
|
||||||
const array = new Uint8Array(Buffer.from(text, "utf-8"));
|
const array = new Uint8Array(Buffer.from(text, "utf-8"));
|
||||||
expect(
|
expect(
|
||||||
StringBufferFieldSubType.Instance.toBuffer(text),
|
StringBufferFieldConverter.Instance.toBuffer(text),
|
||||||
).toEqual(array);
|
).toEqual(array);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("`#fromBuffer` should return the encoded ArrayBuffer", () => {
|
it("`#fromBuffer` should return the encoded ArrayBuffer", () => {
|
||||||
const text = "foo";
|
const text = "foo";
|
||||||
const array = new Uint8Array(Buffer.from(text, "utf-8"));
|
const array = new Uint8Array(Buffer.from(text, "utf-8"));
|
||||||
expect(StringBufferFieldSubType.Instance.toValue(array)).toBe(
|
expect(StringBufferFieldConverter.Instance.toValue(array)).toBe(
|
||||||
text,
|
text,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("`#getSize` should return -1", () => {
|
it("`#getSize` should return -1", () => {
|
||||||
expect(StringBufferFieldSubType.Instance.getSize()).toBe(-1);
|
expect(StringBufferFieldConverter.Instance.getSize()).toBe(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
class MockArrayBufferFieldDefinition<
|
class MockArrayBufferFieldDefinition<
|
||||||
TType extends BufferFieldSubType,
|
TType extends BufferFieldConverter,
|
||||||
> extends BufferLikeFieldDefinition<TType, number> {
|
> extends BufferLikeFieldDefinition<TType, number> {
|
||||||
getSize(): number {
|
getSize(): number {
|
||||||
return this.options;
|
return this.options;
|
||||||
|
@ -90,7 +92,7 @@ describe("Types", () => {
|
||||||
it("should work with `Uint8ArrayBufferFieldSubType`", () => {
|
it("should work with `Uint8ArrayBufferFieldSubType`", () => {
|
||||||
const size = 10;
|
const size = 10;
|
||||||
const definition = new MockArrayBufferFieldDefinition(
|
const definition = new MockArrayBufferFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
size,
|
size,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -114,7 +116,7 @@ describe("Types", () => {
|
||||||
it("should work when `#getSize` returns `0`", () => {
|
it("should work when `#getSize` returns `0`", () => {
|
||||||
const size = 0;
|
const size = 0;
|
||||||
const definition = new MockArrayBufferFieldDefinition(
|
const definition = new MockArrayBufferFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
size,
|
size,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -143,7 +145,7 @@ describe("Types", () => {
|
||||||
it("should clear `array` field", () => {
|
it("should clear `array` field", () => {
|
||||||
const size = 0;
|
const size = 0;
|
||||||
const definition = new MockArrayBufferFieldDefinition(
|
const definition = new MockArrayBufferFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
size,
|
size,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -169,7 +171,7 @@ describe("Types", () => {
|
||||||
it("should be able to serialize with cached `array`", () => {
|
it("should be able to serialize with cached `array`", () => {
|
||||||
const size = 0;
|
const size = 0;
|
||||||
const definition = new MockArrayBufferFieldDefinition(
|
const definition = new MockArrayBufferFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
size,
|
size,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -197,7 +199,7 @@ describe("Types", () => {
|
||||||
it("should be able to serialize a modified value", () => {
|
it("should be able to serialize a modified value", () => {
|
||||||
const size = 0;
|
const size = 0;
|
||||||
const definition = new MockArrayBufferFieldDefinition(
|
const definition = new MockArrayBufferFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
size,
|
size,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -11,46 +11,49 @@ import type { ValueOrPromise } from "../../utils.js";
|
||||||
import { decodeUtf8, encodeUtf8 } from "../../utils.js";
|
import { decodeUtf8, encodeUtf8 } from "../../utils.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for all types that
|
* A converter for buffer-like fields.
|
||||||
* can be converted from an `Uint8Array` when deserialized,
|
* It converts `Uint8Array`s to custom-typed values when deserializing,
|
||||||
* and need to be converted to an `Uint8Array` when serializing
|
* and convert values back to `Uint8Array`s when serializing.
|
||||||
*
|
*
|
||||||
* @template TValue The actual TypeScript type of this type
|
* @template TValue The type of the value that the converter converts to/from `Uint8Array`.
|
||||||
* @template TTypeScriptType Optional another type (should be compatible with `TType`)
|
* @template TTypeScriptType Optionally another type to refine `TValue`.
|
||||||
* specified by user when creating field definitions.
|
* For example, `TValue` is `string`, and `TTypeScriptType` is `"foo" | "bar"`.
|
||||||
|
* `TValue` is specified by the developer when creating an converter implementation,
|
||||||
|
* `TTypeScriptType` is specified by the user when creating a field.
|
||||||
*/
|
*/
|
||||||
export abstract class BufferFieldSubType<
|
export abstract class BufferFieldConverter<
|
||||||
TValue = unknown,
|
TValue = unknown,
|
||||||
TTypeScriptType = TValue,
|
TTypeScriptType = TValue,
|
||||||
> {
|
> {
|
||||||
readonly TTypeScriptType!: TTypeScriptType;
|
readonly TTypeScriptType!: TTypeScriptType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When implemented in derived classes, converts the type-specific `value` to an `Uint8Array`
|
* When implemented in derived classes, converts the custom `value` to an `Uint8Array`
|
||||||
*
|
*
|
||||||
* This function should be "pure", i.e.,
|
* This function should be "pure", i.e.,
|
||||||
* same `value` should always be converted to `Uint8Array`s that have same content.
|
* same `value` should always be converted to `Uint8Array`s that have same content.
|
||||||
*/
|
*/
|
||||||
abstract toBuffer(value: TValue): Uint8Array;
|
abstract toBuffer(value: TValue): Uint8Array;
|
||||||
|
|
||||||
/** When implemented in derived classes, converts the `Uint8Array` to a type-specific value */
|
/** When implemented in derived classes, converts the `Uint8Array` to a custom value */
|
||||||
abstract toValue(array: Uint8Array): TValue;
|
abstract toValue(array: Uint8Array): TValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When implemented in derived classes, gets the size in byte of the type-specific `value`.
|
* When implemented in derived classes, gets the size in byte of the custom `value`.
|
||||||
*
|
*
|
||||||
* If the size can't be calculated without first converting the `value` back to an `Uint8Array`,
|
* If the size can't be determined without first converting the `value` back to an `Uint8Array`,
|
||||||
* implementer can returns `-1`, so the caller will get its size by first converting it to
|
* the implementer should return `undefined`. In which case, the caller will call `toBuffer` to
|
||||||
* an `Uint8Array` (and cache the result).
|
* convert the value to a `Uint8Array`, then read the length of the `Uint8Array`. The caller can
|
||||||
|
* cache the result so the serialization process doesn't need to call `toBuffer` again.
|
||||||
*/
|
*/
|
||||||
abstract getSize(value: TValue): number;
|
abstract getSize(value: TValue): number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An `BufferFieldSubType` that's actually an `Uint8Array` */
|
/** An identity converter, doesn't convert to anything else. */
|
||||||
export class Uint8ArrayBufferFieldSubType<
|
export class Uint8ArrayBufferFieldConverter<
|
||||||
TTypeScriptType = Uint8Array,
|
TTypeScriptType = Uint8Array,
|
||||||
> extends BufferFieldSubType<Uint8Array, TTypeScriptType> {
|
> extends BufferFieldConverter<Uint8Array, TTypeScriptType> {
|
||||||
static readonly Instance = new Uint8ArrayBufferFieldSubType();
|
static readonly Instance = new Uint8ArrayBufferFieldConverter();
|
||||||
|
|
||||||
protected constructor() {
|
protected constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -65,15 +68,15 @@ export class Uint8ArrayBufferFieldSubType<
|
||||||
}
|
}
|
||||||
|
|
||||||
getSize(value: Uint8Array): number {
|
getSize(value: Uint8Array): number {
|
||||||
return value.byteLength;
|
return value.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An `BufferFieldSubType` that converts between `Uint8Array` and `string` */
|
/** An `BufferFieldSubType` that converts between `Uint8Array` and `string` */
|
||||||
export class StringBufferFieldSubType<
|
export class StringBufferFieldConverter<
|
||||||
TTypeScriptType = string,
|
TTypeScriptType = string,
|
||||||
> extends BufferFieldSubType<string, TTypeScriptType> {
|
> extends BufferFieldConverter<string, TTypeScriptType> {
|
||||||
static readonly Instance = new StringBufferFieldSubType();
|
static readonly Instance = new StringBufferFieldConverter();
|
||||||
|
|
||||||
toBuffer(value: string): Uint8Array {
|
toBuffer(value: string): Uint8Array {
|
||||||
return encodeUtf8(value);
|
return encodeUtf8(value);
|
||||||
|
@ -83,31 +86,29 @@ export class StringBufferFieldSubType<
|
||||||
return decodeUtf8(array);
|
return decodeUtf8(array);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSize(): number {
|
getSize(): number | undefined {
|
||||||
// Return `-1`, so `BufferLikeFieldDefinition` will
|
// See the note in `BufferFieldConverter.getSize`
|
||||||
// convert this `value` into an `Uint8Array` (and cache the result),
|
return undefined;
|
||||||
// Then get the size from that `Uint8Array`
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EMPTY_UINT8_ARRAY = new Uint8Array(0);
|
export const EMPTY_UINT8_ARRAY = new Uint8Array(0);
|
||||||
|
|
||||||
export abstract class BufferLikeFieldDefinition<
|
export abstract class BufferLikeFieldDefinition<
|
||||||
TType extends BufferFieldSubType<any, any> = BufferFieldSubType<
|
TConverter extends BufferFieldConverter<
|
||||||
unknown,
|
unknown,
|
||||||
unknown
|
unknown
|
||||||
>,
|
> = BufferFieldConverter<unknown, unknown>,
|
||||||
TOptions = void,
|
TOptions = void,
|
||||||
TOmitInitKey extends PropertyKey = never,
|
TOmitInitKey extends PropertyKey = never,
|
||||||
TTypeScriptType = TType["TTypeScriptType"],
|
TTypeScriptType = TConverter["TTypeScriptType"],
|
||||||
> extends StructFieldDefinition<TOptions, TTypeScriptType, TOmitInitKey> {
|
> extends StructFieldDefinition<TOptions, TTypeScriptType, TOmitInitKey> {
|
||||||
readonly type: TType;
|
readonly converter: TConverter;
|
||||||
readonly TTypeScriptType!: TTypeScriptType;
|
readonly TTypeScriptType!: TTypeScriptType;
|
||||||
|
|
||||||
constructor(type: TType, options: TOptions) {
|
constructor(converter: TConverter, options: TOptions) {
|
||||||
super(options);
|
super(options);
|
||||||
this.type = type;
|
this.converter = converter;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getDeserializeSize(struct: StructValue): number {
|
protected getDeserializeSize(struct: StructValue): number {
|
||||||
|
@ -151,7 +152,7 @@ export abstract class BufferLikeFieldDefinition<
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((array) => {
|
.then((array) => {
|
||||||
const value = this.type.toValue(array) as TTypeScriptType;
|
const value = this.converter.toValue(array) as TTypeScriptType;
|
||||||
return this.create(options, struct, value, array);
|
return this.create(options, struct, value, array);
|
||||||
})
|
})
|
||||||
.valueOrPromise();
|
.valueOrPromise();
|
||||||
|
@ -160,7 +161,7 @@ export abstract class BufferLikeFieldDefinition<
|
||||||
|
|
||||||
export class BufferLikeFieldValue<
|
export class BufferLikeFieldValue<
|
||||||
TDefinition extends BufferLikeFieldDefinition<
|
TDefinition extends BufferLikeFieldDefinition<
|
||||||
BufferFieldSubType<any, any>,
|
BufferFieldConverter<unknown, unknown>,
|
||||||
any,
|
any,
|
||||||
any,
|
any,
|
||||||
any
|
any
|
||||||
|
@ -191,7 +192,7 @@ export class BufferLikeFieldValue<
|
||||||
array: Uint8Array,
|
array: Uint8Array,
|
||||||
offset: number,
|
offset: number,
|
||||||
): void {
|
): void {
|
||||||
this.array ??= this.definition.type.toBuffer(this.value);
|
this.array ??= this.definition.converter.toBuffer(this.value);
|
||||||
array.set(this.array, offset);
|
array.set(this.array, offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { describe, expect, it } from "@jest/globals";
|
import { describe, expect, it } from "@jest/globals";
|
||||||
|
|
||||||
import { Uint8ArrayBufferFieldSubType } from "./base.js";
|
import { Uint8ArrayBufferFieldConverter } from "./base.js";
|
||||||
import { FixedLengthBufferLikeFieldDefinition } from "./fixed-length.js";
|
import { FixedLengthBufferLikeFieldDefinition } from "./fixed-length.js";
|
||||||
|
|
||||||
describe("Types", () => {
|
describe("Types", () => {
|
||||||
|
@ -8,7 +8,7 @@ describe("Types", () => {
|
||||||
describe("#getSize", () => {
|
describe("#getSize", () => {
|
||||||
it("should return size in its options", () => {
|
it("should return size in its options", () => {
|
||||||
const definition = new FixedLengthBufferLikeFieldDefinition(
|
const definition = new FixedLengthBufferLikeFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
{ length: 10 },
|
{ length: 10 },
|
||||||
);
|
);
|
||||||
expect(definition.getSize()).toBe(10);
|
expect(definition.getSize()).toBe(10);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { BufferFieldSubType } from "./base.js";
|
import type { BufferFieldConverter } from "./base.js";
|
||||||
import { BufferLikeFieldDefinition } from "./base.js";
|
import { BufferLikeFieldDefinition } from "./base.js";
|
||||||
|
|
||||||
export interface FixedLengthBufferLikeFieldOptions {
|
export interface FixedLengthBufferLikeFieldOptions {
|
||||||
|
@ -6,11 +6,16 @@ export interface FixedLengthBufferLikeFieldOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FixedLengthBufferLikeFieldDefinition<
|
export class FixedLengthBufferLikeFieldDefinition<
|
||||||
TType extends BufferFieldSubType = BufferFieldSubType,
|
TConverter extends BufferFieldConverter = BufferFieldConverter,
|
||||||
TOptions extends
|
TOptions extends
|
||||||
FixedLengthBufferLikeFieldOptions = FixedLengthBufferLikeFieldOptions,
|
FixedLengthBufferLikeFieldOptions = FixedLengthBufferLikeFieldOptions,
|
||||||
TTypeScriptType = TType["TTypeScriptType"],
|
TTypeScriptType = TConverter["TTypeScriptType"],
|
||||||
> extends BufferLikeFieldDefinition<TType, TOptions, never, TTypeScriptType> {
|
> extends BufferLikeFieldDefinition<
|
||||||
|
TConverter,
|
||||||
|
TOptions,
|
||||||
|
never,
|
||||||
|
TTypeScriptType
|
||||||
|
> {
|
||||||
getSize(): number {
|
getSize(): number {
|
||||||
return this.options.length;
|
return this.options.length;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,9 @@ import {
|
||||||
} from "../../basic/index.js";
|
} from "../../basic/index.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BufferFieldSubType,
|
BufferFieldConverter,
|
||||||
EMPTY_UINT8_ARRAY,
|
EMPTY_UINT8_ARRAY,
|
||||||
Uint8ArrayBufferFieldSubType,
|
Uint8ArrayBufferFieldConverter,
|
||||||
} from "./base.js";
|
} from "./base.js";
|
||||||
import {
|
import {
|
||||||
VariableLengthBufferLikeFieldDefinition,
|
VariableLengthBufferLikeFieldDefinition,
|
||||||
|
@ -334,7 +334,7 @@ describe("Types", () => {
|
||||||
|
|
||||||
const arrayBufferFieldDefinition =
|
const arrayBufferFieldDefinition =
|
||||||
new VariableLengthBufferLikeFieldDefinition(
|
new VariableLengthBufferLikeFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -371,7 +371,7 @@ describe("Types", () => {
|
||||||
|
|
||||||
const arrayBufferFieldDefinition =
|
const arrayBufferFieldDefinition =
|
||||||
new VariableLengthBufferLikeFieldDefinition(
|
new VariableLengthBufferLikeFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -409,7 +409,7 @@ describe("Types", () => {
|
||||||
|
|
||||||
const arrayBufferFieldDefinition =
|
const arrayBufferFieldDefinition =
|
||||||
new VariableLengthBufferLikeFieldDefinition(
|
new VariableLengthBufferLikeFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -433,7 +433,7 @@ describe("Types", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#getSize", () => {
|
describe("#getSize", () => {
|
||||||
class MockArrayBufferFieldType extends BufferFieldSubType<Uint8Array> {
|
class MockArrayBufferFieldType extends BufferFieldConverter<Uint8Array> {
|
||||||
override toBuffer = jest.fn((value: Uint8Array): Uint8Array => {
|
override toBuffer = jest.fn((value: Uint8Array): Uint8Array => {
|
||||||
return value;
|
return value;
|
||||||
});
|
});
|
||||||
|
@ -444,12 +444,14 @@ describe("Types", () => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
size = 0;
|
size: number | undefined = 0;
|
||||||
|
|
||||||
override getSize = jest.fn((value: Uint8Array): number => {
|
override getSize = jest.fn(
|
||||||
void value;
|
(value: Uint8Array): number | undefined => {
|
||||||
return this.size;
|
void value;
|
||||||
});
|
return this.size;
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should return cached size if exist", () => {
|
it("should return cached size if exist", () => {
|
||||||
|
@ -546,7 +548,7 @@ describe("Types", () => {
|
||||||
value,
|
value,
|
||||||
);
|
);
|
||||||
|
|
||||||
arrayBufferFieldType.size = -1;
|
arrayBufferFieldType.size = undefined;
|
||||||
expect(bufferFieldValue.getSize()).toBe(100);
|
expect(bufferFieldValue.getSize()).toBe(100);
|
||||||
expect(arrayBufferFieldType.toValue).toHaveBeenCalledTimes(0);
|
expect(arrayBufferFieldType.toValue).toHaveBeenCalledTimes(0);
|
||||||
expect(arrayBufferFieldType.toBuffer).toHaveBeenCalledTimes(1);
|
expect(arrayBufferFieldType.toBuffer).toHaveBeenCalledTimes(1);
|
||||||
|
@ -566,7 +568,7 @@ describe("Types", () => {
|
||||||
|
|
||||||
const arrayBufferFieldDefinition =
|
const arrayBufferFieldDefinition =
|
||||||
new VariableLengthBufferLikeFieldDefinition(
|
new VariableLengthBufferLikeFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -596,7 +598,7 @@ describe("Types", () => {
|
||||||
|
|
||||||
const arrayBufferFieldDefinition =
|
const arrayBufferFieldDefinition =
|
||||||
new VariableLengthBufferLikeFieldDefinition(
|
new VariableLengthBufferLikeFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -622,7 +624,7 @@ describe("Types", () => {
|
||||||
describe("#getSize", () => {
|
describe("#getSize", () => {
|
||||||
it("should always return `0`", () => {
|
it("should always return `0`", () => {
|
||||||
const definition = new VariableLengthBufferLikeFieldDefinition(
|
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
{ lengthField: "foo" },
|
{ lengthField: "foo" },
|
||||||
);
|
);
|
||||||
expect(definition.getSize()).toBe(0);
|
expect(definition.getSize()).toBe(0);
|
||||||
|
@ -638,7 +640,7 @@ describe("Types", () => {
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const definition = new VariableLengthBufferLikeFieldDefinition(
|
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -660,7 +662,7 @@ describe("Types", () => {
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const definition = new VariableLengthBufferLikeFieldDefinition(
|
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -683,7 +685,7 @@ describe("Types", () => {
|
||||||
|
|
||||||
const radix = 8;
|
const radix = 8;
|
||||||
const definition = new VariableLengthBufferLikeFieldDefinition(
|
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
{ lengthField, lengthFieldRadix: radix },
|
{ lengthField, lengthFieldRadix: radix },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -709,7 +711,7 @@ describe("Types", () => {
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const definition = new VariableLengthBufferLikeFieldDefinition(
|
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -742,7 +744,7 @@ describe("Types", () => {
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const definition = new VariableLengthBufferLikeFieldDefinition(
|
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
Uint8ArrayBufferFieldSubType.Instance,
|
Uint8ArrayBufferFieldConverter.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import type {
|
||||||
import { StructFieldValue } from "../../basic/index.js";
|
import { StructFieldValue } from "../../basic/index.js";
|
||||||
import type { KeysOfType } from "../../utils.js";
|
import type { KeysOfType } from "../../utils.js";
|
||||||
|
|
||||||
import type { BufferFieldSubType } from "./base.js";
|
import type { BufferFieldConverter } from "./base.js";
|
||||||
import { BufferLikeFieldDefinition, BufferLikeFieldValue } from "./base.js";
|
import { BufferLikeFieldDefinition, BufferLikeFieldValue } from "./base.js";
|
||||||
|
|
||||||
export type LengthField<TFields> = KeysOfType<TFields, number | string>;
|
export type LengthField<TFields> = KeysOfType<TFields, number | string>;
|
||||||
|
@ -35,12 +35,12 @@ export interface VariableLengthBufferLikeFieldOptions<
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VariableLengthBufferLikeFieldDefinition<
|
export class VariableLengthBufferLikeFieldDefinition<
|
||||||
TType extends BufferFieldSubType = BufferFieldSubType,
|
TConverter extends BufferFieldConverter = BufferFieldConverter,
|
||||||
TOptions extends
|
TOptions extends
|
||||||
VariableLengthBufferLikeFieldOptions = VariableLengthBufferLikeFieldOptions,
|
VariableLengthBufferLikeFieldOptions = VariableLengthBufferLikeFieldOptions,
|
||||||
TTypeScriptType = TType["TTypeScriptType"],
|
TTypeScriptType = TConverter["TTypeScriptType"],
|
||||||
> extends BufferLikeFieldDefinition<
|
> extends BufferLikeFieldDefinition<
|
||||||
TType,
|
TConverter,
|
||||||
TOptions,
|
TOptions,
|
||||||
TOptions["lengthField"],
|
TOptions["lengthField"],
|
||||||
TTypeScriptType
|
TTypeScriptType
|
||||||
|
@ -106,14 +106,24 @@ export class VariableLengthBufferLikeStructFieldValue<
|
||||||
}
|
}
|
||||||
|
|
||||||
override getSize() {
|
override getSize() {
|
||||||
if (this.length === undefined) {
|
if (this.length !== undefined) {
|
||||||
this.length = this.definition.type.getSize(this.value);
|
// Have cached length
|
||||||
if (this.length === -1) {
|
return this.length;
|
||||||
this.array = this.definition.type.toBuffer(this.value);
|
|
||||||
this.length = this.array.byteLength;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.length = this.definition.converter.getSize(this.value);
|
||||||
|
if (this.length !== undefined) {
|
||||||
|
if (this.length < 0) {
|
||||||
|
throw new Error("Invalid length");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The converter knows the size
|
||||||
|
return this.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The converter doesn't know the size, so we need to convert to buffer first
|
||||||
|
this.array = this.definition.converter.toBuffer(this.value);
|
||||||
|
this.length = this.array.byteLength;
|
||||||
return this.length;
|
return this.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@ import { describe, expect, it, jest, test } from "@jest/globals";
|
||||||
import type { ExactReadable } from "../basic/index.js";
|
import type { ExactReadable } from "../basic/index.js";
|
||||||
import { StructDefaultOptions, StructValue } from "../basic/index.js";
|
import { StructDefaultOptions, StructValue } from "../basic/index.js";
|
||||||
|
|
||||||
import { NumberFieldDefinition, NumberFieldType } from "./number.js";
|
import { NumberFieldDefinition, NumberFieldVariant } from "./number.js";
|
||||||
|
|
||||||
function testEndian(
|
function testEndian(
|
||||||
type: NumberFieldType,
|
type: NumberFieldVariant,
|
||||||
min: number,
|
min: number,
|
||||||
max: number,
|
max: number,
|
||||||
littleEndian: boolean,
|
littleEndian: boolean,
|
||||||
|
@ -55,7 +55,7 @@ function testEndian(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function testDeserialize(type: NumberFieldType) {
|
function testDeserialize(type: NumberFieldVariant) {
|
||||||
if (type.size === 1) {
|
if (type.size === 1) {
|
||||||
if (type.signed) {
|
if (type.signed) {
|
||||||
const MIN = -(2 ** (type.size * 8 - 1));
|
const MIN = -(2 ** (type.size * 8 - 1));
|
||||||
|
@ -89,65 +89,65 @@ function testDeserialize(type: NumberFieldType) {
|
||||||
|
|
||||||
describe("Types", () => {
|
describe("Types", () => {
|
||||||
describe("Number", () => {
|
describe("Number", () => {
|
||||||
describe("NumberFieldType", () => {
|
describe("NumberFieldVariant", () => {
|
||||||
describe("Int8", () => {
|
describe("Int8", () => {
|
||||||
const key = "Int8";
|
const key = "Int8";
|
||||||
|
|
||||||
test("basic", () => {
|
test("basic", () => {
|
||||||
expect(NumberFieldType[key]).toHaveProperty("size", 1);
|
expect(NumberFieldVariant[key]).toHaveProperty("size", 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
testDeserialize(NumberFieldType[key]);
|
testDeserialize(NumberFieldVariant[key]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Uint8", () => {
|
describe("Uint8", () => {
|
||||||
const key = "Uint8";
|
const key = "Uint8";
|
||||||
|
|
||||||
test("basic", () => {
|
test("basic", () => {
|
||||||
expect(NumberFieldType[key]).toHaveProperty("size", 1);
|
expect(NumberFieldVariant[key]).toHaveProperty("size", 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
testDeserialize(NumberFieldType[key]);
|
testDeserialize(NumberFieldVariant[key]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Int16", () => {
|
describe("Int16", () => {
|
||||||
const key = "Int16";
|
const key = "Int16";
|
||||||
|
|
||||||
test("basic", () => {
|
test("basic", () => {
|
||||||
expect(NumberFieldType[key]).toHaveProperty("size", 2);
|
expect(NumberFieldVariant[key]).toHaveProperty("size", 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
testDeserialize(NumberFieldType[key]);
|
testDeserialize(NumberFieldVariant[key]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Uint16", () => {
|
describe("Uint16", () => {
|
||||||
const key = "Uint16";
|
const key = "Uint16";
|
||||||
|
|
||||||
test("basic", () => {
|
test("basic", () => {
|
||||||
expect(NumberFieldType[key]).toHaveProperty("size", 2);
|
expect(NumberFieldVariant[key]).toHaveProperty("size", 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
testDeserialize(NumberFieldType[key]);
|
testDeserialize(NumberFieldVariant[key]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Int32", () => {
|
describe("Int32", () => {
|
||||||
const key = "Int32";
|
const key = "Int32";
|
||||||
|
|
||||||
test("basic", () => {
|
test("basic", () => {
|
||||||
expect(NumberFieldType[key]).toHaveProperty("size", 4);
|
expect(NumberFieldVariant[key]).toHaveProperty("size", 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
testDeserialize(NumberFieldType[key]);
|
testDeserialize(NumberFieldVariant[key]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Uint32", () => {
|
describe("Uint32", () => {
|
||||||
const key = "Uint32";
|
const key = "Uint32";
|
||||||
|
|
||||||
test("basic", () => {
|
test("basic", () => {
|
||||||
expect(NumberFieldType[key]).toHaveProperty("size", 4);
|
expect(NumberFieldVariant[key]).toHaveProperty("size", 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
testDeserialize(NumberFieldType[key]);
|
testDeserialize(NumberFieldVariant[key]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -156,32 +156,32 @@ describe("Types", () => {
|
||||||
it("should return size of its type", () => {
|
it("should return size of its type", () => {
|
||||||
expect(
|
expect(
|
||||||
new NumberFieldDefinition(
|
new NumberFieldDefinition(
|
||||||
NumberFieldType.Int8,
|
NumberFieldVariant.Int8,
|
||||||
).getSize(),
|
).getSize(),
|
||||||
).toBe(1);
|
).toBe(1);
|
||||||
expect(
|
expect(
|
||||||
new NumberFieldDefinition(
|
new NumberFieldDefinition(
|
||||||
NumberFieldType.Uint8,
|
NumberFieldVariant.Uint8,
|
||||||
).getSize(),
|
).getSize(),
|
||||||
).toBe(1);
|
).toBe(1);
|
||||||
expect(
|
expect(
|
||||||
new NumberFieldDefinition(
|
new NumberFieldDefinition(
|
||||||
NumberFieldType.Int16,
|
NumberFieldVariant.Int16,
|
||||||
).getSize(),
|
).getSize(),
|
||||||
).toBe(2);
|
).toBe(2);
|
||||||
expect(
|
expect(
|
||||||
new NumberFieldDefinition(
|
new NumberFieldDefinition(
|
||||||
NumberFieldType.Uint16,
|
NumberFieldVariant.Uint16,
|
||||||
).getSize(),
|
).getSize(),
|
||||||
).toBe(2);
|
).toBe(2);
|
||||||
expect(
|
expect(
|
||||||
new NumberFieldDefinition(
|
new NumberFieldDefinition(
|
||||||
NumberFieldType.Int32,
|
NumberFieldVariant.Int32,
|
||||||
).getSize(),
|
).getSize(),
|
||||||
).toBe(4);
|
).toBe(4);
|
||||||
expect(
|
expect(
|
||||||
new NumberFieldDefinition(
|
new NumberFieldDefinition(
|
||||||
NumberFieldType.Uint32,
|
NumberFieldVariant.Uint32,
|
||||||
).getSize(),
|
).getSize(),
|
||||||
).toBe(4);
|
).toBe(4);
|
||||||
});
|
});
|
||||||
|
@ -195,7 +195,7 @@ describe("Types", () => {
|
||||||
const stream: ExactReadable = { position: 0, readExactly };
|
const stream: ExactReadable = { position: 0, readExactly };
|
||||||
|
|
||||||
const definition = new NumberFieldDefinition(
|
const definition = new NumberFieldDefinition(
|
||||||
NumberFieldType.Uint8,
|
NumberFieldVariant.Uint8,
|
||||||
);
|
);
|
||||||
const struct = new StructValue({});
|
const struct = new StructValue({});
|
||||||
const value = definition.deserialize(
|
const value = definition.deserialize(
|
||||||
|
@ -207,7 +207,7 @@ describe("Types", () => {
|
||||||
expect(value.get()).toBe(1);
|
expect(value.get()).toBe(1);
|
||||||
expect(readExactly).toHaveBeenCalledTimes(1);
|
expect(readExactly).toHaveBeenCalledTimes(1);
|
||||||
expect(readExactly).toHaveBeenCalledWith(
|
expect(readExactly).toHaveBeenCalledWith(
|
||||||
NumberFieldType.Uint8.size,
|
NumberFieldVariant.Uint8.size,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -218,7 +218,7 @@ describe("Types", () => {
|
||||||
const stream: ExactReadable = { position: 0, readExactly };
|
const stream: ExactReadable = { position: 0, readExactly };
|
||||||
|
|
||||||
const definition = new NumberFieldDefinition(
|
const definition = new NumberFieldDefinition(
|
||||||
NumberFieldType.Uint16,
|
NumberFieldVariant.Uint16,
|
||||||
);
|
);
|
||||||
const struct = new StructValue({});
|
const struct = new StructValue({});
|
||||||
const value = definition.deserialize(
|
const value = definition.deserialize(
|
||||||
|
@ -230,7 +230,7 @@ describe("Types", () => {
|
||||||
expect(value.get()).toBe((1 << 8) | 2);
|
expect(value.get()).toBe((1 << 8) | 2);
|
||||||
expect(readExactly).toHaveBeenCalledTimes(1);
|
expect(readExactly).toHaveBeenCalledTimes(1);
|
||||||
expect(readExactly).toHaveBeenCalledWith(
|
expect(readExactly).toHaveBeenCalledWith(
|
||||||
NumberFieldType.Uint16.size,
|
NumberFieldVariant.Uint16.size,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -241,7 +241,7 @@ describe("Types", () => {
|
||||||
const stream: ExactReadable = { position: 0, readExactly };
|
const stream: ExactReadable = { position: 0, readExactly };
|
||||||
|
|
||||||
const definition = new NumberFieldDefinition(
|
const definition = new NumberFieldDefinition(
|
||||||
NumberFieldType.Uint16,
|
NumberFieldVariant.Uint16,
|
||||||
);
|
);
|
||||||
const struct = new StructValue({});
|
const struct = new StructValue({});
|
||||||
const value = definition.deserialize(
|
const value = definition.deserialize(
|
||||||
|
@ -253,7 +253,7 @@ describe("Types", () => {
|
||||||
expect(value.get()).toBe((2 << 8) | 1);
|
expect(value.get()).toBe((2 << 8) | 1);
|
||||||
expect(readExactly).toHaveBeenCalledTimes(1);
|
expect(readExactly).toHaveBeenCalledTimes(1);
|
||||||
expect(readExactly).toHaveBeenCalledWith(
|
expect(readExactly).toHaveBeenCalledWith(
|
||||||
NumberFieldType.Uint16.size,
|
NumberFieldVariant.Uint16.size,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -265,37 +265,37 @@ describe("Types", () => {
|
||||||
const struct = new StructValue({});
|
const struct = new StructValue({});
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
new NumberFieldDefinition(NumberFieldType.Int8)
|
new NumberFieldDefinition(NumberFieldVariant.Int8)
|
||||||
.create(StructDefaultOptions, struct, 42)
|
.create(StructDefaultOptions, struct, 42)
|
||||||
.getSize(),
|
.getSize(),
|
||||||
).toBe(1);
|
).toBe(1);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
new NumberFieldDefinition(NumberFieldType.Uint8)
|
new NumberFieldDefinition(NumberFieldVariant.Uint8)
|
||||||
.create(StructDefaultOptions, struct, 42)
|
.create(StructDefaultOptions, struct, 42)
|
||||||
.getSize(),
|
.getSize(),
|
||||||
).toBe(1);
|
).toBe(1);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
new NumberFieldDefinition(NumberFieldType.Int16)
|
new NumberFieldDefinition(NumberFieldVariant.Int16)
|
||||||
.create(StructDefaultOptions, struct, 42)
|
.create(StructDefaultOptions, struct, 42)
|
||||||
.getSize(),
|
.getSize(),
|
||||||
).toBe(2);
|
).toBe(2);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
new NumberFieldDefinition(NumberFieldType.Uint16)
|
new NumberFieldDefinition(NumberFieldVariant.Uint16)
|
||||||
.create(StructDefaultOptions, struct, 42)
|
.create(StructDefaultOptions, struct, 42)
|
||||||
.getSize(),
|
.getSize(),
|
||||||
).toBe(2);
|
).toBe(2);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
new NumberFieldDefinition(NumberFieldType.Int32)
|
new NumberFieldDefinition(NumberFieldVariant.Int32)
|
||||||
.create(StructDefaultOptions, struct, 42)
|
.create(StructDefaultOptions, struct, 42)
|
||||||
.getSize(),
|
.getSize(),
|
||||||
).toBe(4);
|
).toBe(4);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
new NumberFieldDefinition(NumberFieldType.Uint32)
|
new NumberFieldDefinition(NumberFieldVariant.Uint32)
|
||||||
.create(StructDefaultOptions, struct, 42)
|
.create(StructDefaultOptions, struct, 42)
|
||||||
.getSize(),
|
.getSize(),
|
||||||
).toBe(4);
|
).toBe(4);
|
||||||
|
@ -305,7 +305,7 @@ describe("Types", () => {
|
||||||
describe("#serialize", () => {
|
describe("#serialize", () => {
|
||||||
it("should serialize uint8", () => {
|
it("should serialize uint8", () => {
|
||||||
const definition = new NumberFieldDefinition(
|
const definition = new NumberFieldDefinition(
|
||||||
NumberFieldType.Int8,
|
NumberFieldVariant.Int8,
|
||||||
);
|
);
|
||||||
const struct = new StructValue({});
|
const struct = new StructValue({});
|
||||||
const value = definition.create(
|
const value = definition.create(
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { StructFieldDefinition, StructFieldValue } from "../basic/index.js";
|
||||||
import { SyncPromise } from "../sync-promise.js";
|
import { SyncPromise } from "../sync-promise.js";
|
||||||
import type { ValueOrPromise } from "../utils.js";
|
import type { ValueOrPromise } from "../utils.js";
|
||||||
|
|
||||||
export interface NumberFieldType {
|
export interface NumberFieldVariant {
|
||||||
signed: boolean;
|
signed: boolean;
|
||||||
size: number;
|
size: number;
|
||||||
deserialize(array: Uint8Array, littleEndian: boolean): number;
|
deserialize(array: Uint8Array, littleEndian: boolean): number;
|
||||||
|
@ -27,8 +27,8 @@ export interface NumberFieldType {
|
||||||
): void;
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace NumberFieldType {
|
export namespace NumberFieldVariant {
|
||||||
export const Uint8: NumberFieldType = {
|
export const Uint8: NumberFieldVariant = {
|
||||||
signed: false,
|
signed: false,
|
||||||
size: 1,
|
size: 1,
|
||||||
deserialize(array) {
|
deserialize(array) {
|
||||||
|
@ -39,7 +39,7 @@ export namespace NumberFieldType {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Int8: NumberFieldType = {
|
export const Int8: NumberFieldVariant = {
|
||||||
signed: true,
|
signed: true,
|
||||||
size: 1,
|
size: 1,
|
||||||
deserialize(array) {
|
deserialize(array) {
|
||||||
|
@ -51,7 +51,7 @@ export namespace NumberFieldType {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Uint16: NumberFieldType = {
|
export const Uint16: NumberFieldVariant = {
|
||||||
signed: false,
|
signed: false,
|
||||||
size: 2,
|
size: 2,
|
||||||
deserialize(array, littleEndian) {
|
deserialize(array, littleEndian) {
|
||||||
|
@ -65,7 +65,7 @@ export namespace NumberFieldType {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Int16: NumberFieldType = {
|
export const Int16: NumberFieldVariant = {
|
||||||
signed: true,
|
signed: true,
|
||||||
size: 2,
|
size: 2,
|
||||||
deserialize(array, littleEndian) {
|
deserialize(array, littleEndian) {
|
||||||
|
@ -76,7 +76,7 @@ export namespace NumberFieldType {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Uint32: NumberFieldType = {
|
export const Uint32: NumberFieldVariant = {
|
||||||
signed: false,
|
signed: false,
|
||||||
size: 4,
|
size: 4,
|
||||||
deserialize(array, littleEndian) {
|
deserialize(array, littleEndian) {
|
||||||
|
@ -87,7 +87,7 @@ export namespace NumberFieldType {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Int32: NumberFieldType = {
|
export const Int32: NumberFieldVariant = {
|
||||||
signed: true,
|
signed: true,
|
||||||
size: 4,
|
size: 4,
|
||||||
deserialize(array, littleEndian) {
|
deserialize(array, littleEndian) {
|
||||||
|
@ -100,19 +100,19 @@ export namespace NumberFieldType {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NumberFieldDefinition<
|
export class NumberFieldDefinition<
|
||||||
TType extends NumberFieldType = NumberFieldType,
|
TVariant extends NumberFieldVariant = NumberFieldVariant,
|
||||||
TTypeScriptType = number,
|
TTypeScriptType = number,
|
||||||
> extends StructFieldDefinition<void, TTypeScriptType> {
|
> extends StructFieldDefinition<void, TTypeScriptType> {
|
||||||
readonly type: TType;
|
readonly variant: TVariant;
|
||||||
|
|
||||||
constructor(type: TType, typescriptType?: TTypeScriptType) {
|
constructor(variant: TVariant, typescriptType?: TTypeScriptType) {
|
||||||
void typescriptType;
|
void typescriptType;
|
||||||
super();
|
super();
|
||||||
this.type = type;
|
this.variant = variant;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSize(): number {
|
getSize(): number {
|
||||||
return this.type.size;
|
return this.variant.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
create(
|
create(
|
||||||
|
@ -142,7 +142,7 @@ export class NumberFieldDefinition<
|
||||||
return stream.readExactly(this.getSize());
|
return stream.readExactly(this.getSize());
|
||||||
})
|
})
|
||||||
.then((array) => {
|
.then((array) => {
|
||||||
const value = this.type.deserialize(
|
const value = this.variant.deserialize(
|
||||||
array,
|
array,
|
||||||
options.littleEndian,
|
options.littleEndian,
|
||||||
);
|
);
|
||||||
|
@ -153,10 +153,10 @@ export class NumberFieldDefinition<
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NumberFieldValue<
|
export class NumberFieldValue<
|
||||||
TDefinition extends NumberFieldDefinition<NumberFieldType, unknown>,
|
TDefinition extends NumberFieldDefinition<NumberFieldVariant, unknown>,
|
||||||
> extends StructFieldValue<TDefinition> {
|
> extends StructFieldValue<TDefinition> {
|
||||||
serialize(dataView: DataView, array: Uint8Array, offset: number): void {
|
serialize(dataView: DataView, array: Uint8Array, offset: number): void {
|
||||||
this.definition.type.serialize(
|
this.definition.variant.serialize(
|
||||||
dataView,
|
dataView,
|
||||||
offset,
|
offset,
|
||||||
this.value as never,
|
this.value as never,
|
||||||
|
|
|
@ -88,5 +88,7 @@ export function encodeUtf8(input: string): Uint8Array {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decodeUtf8(buffer: ArrayBufferView | ArrayBuffer): string {
|
export function decodeUtf8(buffer: ArrayBufferView | ArrayBuffer): string {
|
||||||
|
// `TextDecoder` has internal states in stream mode,
|
||||||
|
// but we don't use stream mode here, so it's safe to reuse the instance
|
||||||
return Utf8Decoder.decode(buffer);
|
return Utf8Decoder.decode(buffer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
"run-eslint": "run-eslint.js"
|
"run-eslint": "run-eslint.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint/js": "^9.1.1",
|
"@eslint/js": "^9.2.0",
|
||||||
"@types/node": "^20.12.7",
|
"@types/node": "^20.12.8",
|
||||||
"eslint": "^9.1.1",
|
"eslint": "^9.2.0",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
"typescript-eslint": "^7.8.0"
|
"typescript-eslint": "^7.8.0"
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue