yuzuthread 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +4 -0
- package/.eslintrc.js +25 -0
- package/.prettierrc +4 -0
- package/AGENTS.md +23 -0
- package/Dockerfile.puppeteer +42 -0
- package/LICENSE +22 -0
- package/README.md +159 -0
- package/dist/src/.nfs.200510fa.6938 +4 -0
- package/index.ts +4 -0
- package/package.json +84 -0
- package/tsconfig.json +20 -0
- package/tsconfig.test.json +17 -0
package/.eslintignore
ADDED
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
parser: '@typescript-eslint/parser',
|
|
3
|
+
parserOptions: {
|
|
4
|
+
project: 'tsconfig.json',
|
|
5
|
+
tsconfigRootDir : __dirname,
|
|
6
|
+
sourceType: 'module',
|
|
7
|
+
},
|
|
8
|
+
plugins: ['@typescript-eslint/eslint-plugin'],
|
|
9
|
+
extends: [
|
|
10
|
+
'plugin:@typescript-eslint/recommended',
|
|
11
|
+
'plugin:prettier/recommended',
|
|
12
|
+
],
|
|
13
|
+
root: true,
|
|
14
|
+
env: {
|
|
15
|
+
node: true,
|
|
16
|
+
jest: true,
|
|
17
|
+
},
|
|
18
|
+
ignorePatterns: ['.eslintrc.js'],
|
|
19
|
+
rules: {
|
|
20
|
+
'@typescript-eslint/interface-name-prefix': 'off',
|
|
21
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
22
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
23
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
24
|
+
},
|
|
25
|
+
};
|
package/.prettierrc
ADDED
package/AGENTS.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 项目要求
|
|
2
|
+
|
|
3
|
+
## 项目规范
|
|
4
|
+
|
|
5
|
+
- 能用 lambda 函数就用 lambda 函数,而不是 function。
|
|
6
|
+
- 如果表达「类」类型,不要用 function,而是用 AnyClass 或者 ClassType<T>。
|
|
7
|
+
- 如果写的代码属于和本业务无关的工具函数,那么写在 src/utility 下新开一个文件。并且要专门为这个文件写单元测试。
|
|
8
|
+
- 和业务无关的类型放在 src/utility/types.ts 里。如果类型太长,那么另开文件。
|
|
9
|
+
- 对于写的任何一个小方法,都要写单元测试 .spec.ts 然后跑一下验证对不对。
|
|
10
|
+
- 测试的时候禁止同时跑两个命令,否则可能会有冲突。
|
|
11
|
+
|
|
12
|
+
## 项目目标
|
|
13
|
+
|
|
14
|
+
实现一个和 nanolith 差不多的库,可以用类创建 worker。
|
|
15
|
+
|
|
16
|
+
## 参考代码
|
|
17
|
+
|
|
18
|
+
下面的代码库可以作为参考,但是不要改里面的文件。
|
|
19
|
+
注意不要尝试去 nanolith 找 typed-struct 相关的,不可能有的。
|
|
20
|
+
|
|
21
|
+
- typed-struct: /Users/nanahira/nas-toa/workspace/ref/typed-struct
|
|
22
|
+
- nanolith: /Users/nanahira/nas-toa/workspace/ref/typed-struct
|
|
23
|
+
- typed-reflector: /Users/nanahira/nas-toa/workspace/typed-reflector
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
FROM node:lts-trixie-slim as base
|
|
2
|
+
LABEL Author="Nanahira <nanahira@momobako.com>"
|
|
3
|
+
|
|
4
|
+
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
|
5
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
6
|
+
|
|
7
|
+
RUN set -eux; \
|
|
8
|
+
apt-get update; \
|
|
9
|
+
apt-get install -y --no-install-recommends curl ca-certificates gnupg; \
|
|
10
|
+
install -d -m 0755 /etc/apt/keyrings; \
|
|
11
|
+
curl -fsSL https://dl.google.com/linux/linux_signing_key.pub \
|
|
12
|
+
| gpg --dearmor -o /etc/apt/keyrings/google-linux.gpg; \
|
|
13
|
+
chmod a+r /etc/apt/keyrings/google-linux.gpg; \
|
|
14
|
+
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/google-linux.gpg] https://dl.google.com/linux/chrome/deb stable main" \
|
|
15
|
+
> /etc/apt/sources.list.d/google-chrome.list; \
|
|
16
|
+
apt-get update; \
|
|
17
|
+
apt-get install -y --no-install-recommends \
|
|
18
|
+
python3 build-essential git \
|
|
19
|
+
google-chrome-stable \
|
|
20
|
+
libnss3 libfreetype6-dev libharfbuzz-bin libharfbuzz-dev \
|
|
21
|
+
fonts-freefont-otf fonts-freefont-ttf \
|
|
22
|
+
fonts-noto-cjk fonts-noto-cjk-extra \
|
|
23
|
+
fonts-wqy-microhei fonts-wqy-zenhei \
|
|
24
|
+
xvfb; \
|
|
25
|
+
apt-get purge -y --auto-remove gnupg; \
|
|
26
|
+
apt-get clean; \
|
|
27
|
+
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /var/log/*
|
|
28
|
+
|
|
29
|
+
WORKDIR /usr/src/app
|
|
30
|
+
COPY ./package*.json ./
|
|
31
|
+
|
|
32
|
+
FROM base as builder
|
|
33
|
+
RUN npm ci && npm cache clean --force
|
|
34
|
+
COPY . ./
|
|
35
|
+
RUN npm run build
|
|
36
|
+
|
|
37
|
+
FROM base
|
|
38
|
+
ENV NODE_ENV production
|
|
39
|
+
RUN npm ci && npm cache clean --force
|
|
40
|
+
COPY --from=builder /usr/src/app/dist ./dist
|
|
41
|
+
|
|
42
|
+
CMD [ "npm", "start" ]
|
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Nanahira
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# yuzuthread
|
|
2
|
+
|
|
3
|
+
A lightweight, class-first `worker_threads` library for Node.js.
|
|
4
|
+
Define a class with decorators, run methods in a worker thread, and keep a mirrored instance on the main thread.
|
|
5
|
+
|
|
6
|
+
## Why
|
|
7
|
+
|
|
8
|
+
`yuzuthread` is built around a few practical goals:
|
|
9
|
+
|
|
10
|
+
- Organize worker logic with classes instead of manual message protocols.
|
|
11
|
+
- Keep method calls ergonomic and object-oriented.
|
|
12
|
+
- Automatically use shared memory for `typed-struct` classes.
|
|
13
|
+
- Stay non-intrusive: your class can still be used with plain `new`.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm i yuzuthread typed-struct
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`typed-struct` is required because `yuzuthread` declares it as a peer dependency.
|
|
22
|
+
|
|
23
|
+
## Define a Worker Class
|
|
24
|
+
|
|
25
|
+
Put each worker class in its own file and add `@DefineWorker()`.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
// counter.worker.ts
|
|
29
|
+
import { isMainThread } from 'node:worker_threads';
|
|
30
|
+
import { DefineWorker, WorkerMethod, WorkerCallback } from 'yuzuthread';
|
|
31
|
+
|
|
32
|
+
@DefineWorker()
|
|
33
|
+
export class CounterWorker {
|
|
34
|
+
count = 0;
|
|
35
|
+
|
|
36
|
+
@WorkerMethod()
|
|
37
|
+
async increment(step: number) {
|
|
38
|
+
this.count += step;
|
|
39
|
+
return { count: this.count, isMainThread };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@WorkerMethod()
|
|
43
|
+
add(a: number, b: number) {
|
|
44
|
+
return a + b;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@WorkerCallback()
|
|
48
|
+
onMainAdd(a: number, b: number) {
|
|
49
|
+
this.count += a + b;
|
|
50
|
+
return { count: this.count, isMainThread };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@WorkerMethod()
|
|
54
|
+
async callMainAdd(a: number, b: number) {
|
|
55
|
+
return this.onMainAdd(a, b);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Long-running Worker (`initWorker`)
|
|
61
|
+
|
|
62
|
+
`initWorker` creates a persistent worker instance.
|
|
63
|
+
Methods marked with `@WorkerMethod()` are proxied to the worker thread.
|
|
64
|
+
You still hold a main-thread instance of the same class.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { initWorker } from 'yuzuthread';
|
|
68
|
+
import { CounterWorker } from './counter.worker.js';
|
|
69
|
+
|
|
70
|
+
const counter = await initWorker(CounterWorker);
|
|
71
|
+
|
|
72
|
+
console.log(await counter.increment(2));
|
|
73
|
+
// -> { count: 2, isMainThread: false }
|
|
74
|
+
|
|
75
|
+
console.log(counter.count);
|
|
76
|
+
// -> 0 (main-thread instance state)
|
|
77
|
+
|
|
78
|
+
await counter.finalize();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## One-time Worker (`runInWorker`)
|
|
82
|
+
|
|
83
|
+
`runInWorker` is for fire-and-forget style work: create worker, run callback, finalize automatically.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { runInWorker } from 'yuzuthread';
|
|
87
|
+
import { CounterWorker } from './counter.worker.js';
|
|
88
|
+
|
|
89
|
+
const value = await runInWorker(
|
|
90
|
+
CounterWorker,
|
|
91
|
+
async (counter) => {
|
|
92
|
+
await counter.increment(2);
|
|
93
|
+
const result = await counter.increment(3);
|
|
94
|
+
return result.count;
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
console.log(value); // -> 5
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Reverse Calls with `WorkerCallback`
|
|
102
|
+
|
|
103
|
+
`@WorkerCallback()` is the reverse direction:
|
|
104
|
+
|
|
105
|
+
- Called on the main thread: runs locally like a normal method.
|
|
106
|
+
- Called inside the worker thread: forwarded to main thread, then result is sent back.
|
|
107
|
+
|
|
108
|
+
Use this when worker-side logic needs to call back into main-thread state or services.
|
|
109
|
+
|
|
110
|
+
## `typed-struct` Shared Memory
|
|
111
|
+
|
|
112
|
+
If a worker class inherits from a compiled `typed-struct` class, `yuzuthread` automatically:
|
|
113
|
+
|
|
114
|
+
- Detects the struct class and computes buffer size.
|
|
115
|
+
- Creates a `SharedArrayBuffer`.
|
|
116
|
+
- Injects the shared buffer into both main-thread and worker-thread instances.
|
|
117
|
+
|
|
118
|
+
So both sides operate on the same underlying memory.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { Struct } from 'typed-struct';
|
|
122
|
+
import { DefineWorker, WorkerMethod, initWorker } from 'yuzuthread';
|
|
123
|
+
|
|
124
|
+
const Base = new Struct('SharedStructBase').UInt8('value').compile();
|
|
125
|
+
|
|
126
|
+
@DefineWorker()
|
|
127
|
+
class SharedStructWorker extends Base {
|
|
128
|
+
@WorkerMethod()
|
|
129
|
+
setValue(value: number) {
|
|
130
|
+
this.value = value;
|
|
131
|
+
return this.value;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const instance = await initWorker(SharedStructWorker, [0x10]);
|
|
136
|
+
await instance.setValue(0x7f);
|
|
137
|
+
console.log(instance.value); // -> 0x7f
|
|
138
|
+
await instance.finalize();
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## API
|
|
142
|
+
|
|
143
|
+
- `DefineWorker(options?)`
|
|
144
|
+
- `options.filePath?`: worker file path override (optional, auto-inferred by default)
|
|
145
|
+
- `options.id?`: custom class registration ID (optional)
|
|
146
|
+
- `WorkerMethod()`
|
|
147
|
+
- marks a method to execute on worker thread
|
|
148
|
+
- `WorkerCallback()`
|
|
149
|
+
- marks a method to execute on main thread when called from worker
|
|
150
|
+
- `initWorker(cls, ...args)`
|
|
151
|
+
- creates a persistent worker and returns instance with `finalize(): Promise<void>`
|
|
152
|
+
- `runInWorker(cls, cb, ...args)`
|
|
153
|
+
- one-time worker execution with automatic finalize
|
|
154
|
+
|
|
155
|
+
## Notes
|
|
156
|
+
|
|
157
|
+
- Worker classes are still normal classes when instantiated directly via `new`.
|
|
158
|
+
- Only decorated methods go through the RPC channel.
|
|
159
|
+
- TypeScript decorators require `experimentalDecorators`.
|
package/index.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yuzuthread",
|
|
3
|
+
"description": "Decorator-driven class workers for Node.js worker_threads with typed-struct shared memory.",
|
|
4
|
+
"version": "1.0.1",
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.cjs",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"lint": "eslint --fix .",
|
|
17
|
+
"build": "node build.js",
|
|
18
|
+
"build:test": "npm run build && tsc -p tsconfig.test.json",
|
|
19
|
+
"build:cjs": "node build.js cjs",
|
|
20
|
+
"build:esm": "node build.js esm",
|
|
21
|
+
"build:types": "node build.js types",
|
|
22
|
+
"clean": "node build.js clean",
|
|
23
|
+
"test": "npm run build:test && jest --passWithNoTests",
|
|
24
|
+
"test:init-worker": "npm run build:test && jest --runInBand tests/init-worker.spec.ts",
|
|
25
|
+
"start": "node dist/index.cjs"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/purerosefallen/yuzuthread.git"
|
|
30
|
+
},
|
|
31
|
+
"author": "Nanahira <nanahira@momobako.com>",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"keywords": [
|
|
34
|
+
"worker_threads",
|
|
35
|
+
"worker",
|
|
36
|
+
"decorator",
|
|
37
|
+
"typescript",
|
|
38
|
+
"rpc",
|
|
39
|
+
"typed-struct"
|
|
40
|
+
],
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/purerosefallen/yuzuthread/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/purerosefallen/yuzuthread#readme",
|
|
45
|
+
"jest": {
|
|
46
|
+
"moduleFileExtensions": [
|
|
47
|
+
"js",
|
|
48
|
+
"json",
|
|
49
|
+
"ts"
|
|
50
|
+
],
|
|
51
|
+
"rootDir": "tests",
|
|
52
|
+
"testRegex": ".*\\.spec\\.ts$",
|
|
53
|
+
"transform": {
|
|
54
|
+
"^.+\\.ts$": "ts-jest"
|
|
55
|
+
},
|
|
56
|
+
"collectCoverageFrom": [
|
|
57
|
+
"**/*.(t|j)s"
|
|
58
|
+
],
|
|
59
|
+
"coverageDirectory": "../coverage",
|
|
60
|
+
"testEnvironment": "node"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/jest": "^30.0.0",
|
|
64
|
+
"@types/node": "^25.2.2",
|
|
65
|
+
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
66
|
+
"@typescript-eslint/parser": "^8.54.0",
|
|
67
|
+
"esbuild": "^0.27.3",
|
|
68
|
+
"esbuild-register": "^3.6.0",
|
|
69
|
+
"eslint": "^8.57.1",
|
|
70
|
+
"eslint-config-prettier": "^10.1.8",
|
|
71
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
72
|
+
"jest": "^29.7.0",
|
|
73
|
+
"prettier": "^3.8.1",
|
|
74
|
+
"ts-jest": "^29.4.6",
|
|
75
|
+
"typescript": "^5.9.3"
|
|
76
|
+
},
|
|
77
|
+
"peerDependencies": {
|
|
78
|
+
"typed-struct": "^2.7.1"
|
|
79
|
+
},
|
|
80
|
+
"dependencies": {
|
|
81
|
+
"nfkit": "^1.0.21",
|
|
82
|
+
"typed-reflector": "^1.0.14"
|
|
83
|
+
}
|
|
84
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"outDir": "dist",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"target": "es2021",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"emitDecoratorMetadata": true,
|
|
8
|
+
"experimentalDecorators": true,
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"sourceMap": true
|
|
11
|
+
},
|
|
12
|
+
"compileOnSave": true,
|
|
13
|
+
"allowJs": true,
|
|
14
|
+
"include": [
|
|
15
|
+
"*.ts",
|
|
16
|
+
"src/**/*.ts",
|
|
17
|
+
"test/**/*.ts",
|
|
18
|
+
"tests/**/*.ts"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "es2021",
|
|
5
|
+
"module": "commonjs",
|
|
6
|
+
"outDir": ".",
|
|
7
|
+
"rootDir": ".",
|
|
8
|
+
"declaration": false,
|
|
9
|
+
"sourceMap": false,
|
|
10
|
+
"removeComments": false,
|
|
11
|
+
"noEmit": false
|
|
12
|
+
},
|
|
13
|
+
"include": [
|
|
14
|
+
"tests/fixtures/**/*.ts"
|
|
15
|
+
],
|
|
16
|
+
"exclude": []
|
|
17
|
+
}
|