satoru-render 1.0.11 → 1.0.13

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/dist/workers.js CHANGED
@@ -9,8 +9,18 @@ export * from "./log-level.js";
9
9
  * Defaults to the bundled workers.js in the same directory.
10
10
  * @param params.maxParallel Maximum number of parallel workers
11
11
  */
12
+ /**
13
+ * Create a Satoru worker proxy using worker-lib.
14
+ * @param params Initialization parameters
15
+ * @param params.worker Optional: Path to the worker file, a URL, or a factory function.
16
+ * Defaults to the bundled workers.js in the same directory.
17
+ * @param params.maxParallel Maximum number of parallel workers (default: 4)
18
+ * @param params.timeoutMs Optional: Default timeout in milliseconds for each render job.
19
+ * If a render job times out, the pool is reset to prevent hung workers.
20
+ * @param params.onWorkerLog Optional: Global callback to intercept logs emitted by the worker threads.
21
+ */
12
22
  export const createSatoruWorker = (params) => {
13
- const { worker, maxParallel = 4 } = params ?? {};
23
+ const { worker, maxParallel = 4, timeoutMs, onWorkerLog } = params ?? {};
14
24
  const factory = () => {
15
25
  let w;
16
26
  if (worker) {
@@ -29,20 +39,109 @@ export const createSatoruWorker = (params) => {
29
39
  return w;
30
40
  };
31
41
  const workerInstance = createWorker(factory, maxParallel);
42
+ let totalPendingJobs = 0;
43
+ let completedJobs = 0;
44
+ let failedJobs = 0;
45
+ let totalJobTimeMs = 0;
46
+ /**
47
+ * Retrieve operational stats of the worker pool.
48
+ */
49
+ const getStats = () => ({
50
+ workerCount: maxParallel,
51
+ activeJobs: Math.min(totalPendingJobs, maxParallel),
52
+ queuedJobs: Math.max(0, totalPendingJobs - maxParallel),
53
+ completedJobs,
54
+ failedJobs,
55
+ avgJobTimeMs: completedJobs > 0 ? totalJobTimeMs / completedJobs : 0,
56
+ });
57
+ /**
58
+ * Reset the worker pool by terminating all running workers and recreating them.
59
+ * Useful to clear hung workers or reset statistics.
60
+ */
61
+ const reset = () => {
62
+ workerInstance.setLimit(0);
63
+ workerInstance.setLimit(maxParallel);
64
+ totalPendingJobs = 0;
65
+ completedJobs = 0;
66
+ failedJobs = 0;
67
+ totalJobTimeMs = 0;
68
+ };
32
69
  const proxy = new Proxy(workerInstance, {
33
70
  get(target, prop, receiver) {
71
+ if (prop === "getStats") {
72
+ return getStats;
73
+ }
74
+ if (prop === "reset") {
75
+ return reset;
76
+ }
34
77
  if (prop === "render") {
35
78
  return async (options) => {
36
- return await target.execute("render", options);
79
+ totalPendingJobs++;
80
+ const startTime = Date.now();
81
+ const jobTimeoutMs = options.limits?.timeoutMs ?? timeoutMs;
82
+ // Merge the user's specific onLog callback with the global onWorkerLog callback
83
+ const originalOnLog = options.onLog;
84
+ const mergedOnLog = (level, message) => {
85
+ if (originalOnLog) {
86
+ originalOnLog(level, message);
87
+ }
88
+ if (onWorkerLog) {
89
+ onWorkerLog(level, message);
90
+ }
91
+ };
92
+ const mergedOptions = {
93
+ ...options,
94
+ onLog: mergedOnLog,
95
+ };
96
+ let timeoutId;
97
+ let timeoutPromise;
98
+ if (jobTimeoutMs !== undefined && jobTimeoutMs > 0) {
99
+ timeoutPromise = new Promise((_, reject) => {
100
+ timeoutId = setTimeout(() => {
101
+ // If a job times out, terminate and recreate workers to recover from a potential hang
102
+ reset();
103
+ reject(new Error(`Render timed out after ${jobTimeoutMs}ms`));
104
+ }, jobTimeoutMs);
105
+ });
106
+ }
107
+ try {
108
+ const executePromise = target.execute("render", mergedOptions);
109
+ const result = await (timeoutPromise
110
+ ? Promise.race([executePromise, timeoutPromise])
111
+ : executePromise);
112
+ completedJobs++;
113
+ totalJobTimeMs += Date.now() - startTime;
114
+ return result;
115
+ }
116
+ catch (e) {
117
+ failedJobs++;
118
+ throw e;
119
+ }
120
+ finally {
121
+ if (timeoutId) {
122
+ clearTimeout(timeoutId);
123
+ }
124
+ totalPendingJobs--;
125
+ }
37
126
  };
38
127
  }
39
128
  if (prop in target) {
40
129
  return Reflect.get(target, prop, receiver);
41
130
  }
42
- return (...args) => target.execute(prop, ...args);
131
+ return async (...args) => {
132
+ totalPendingJobs++;
133
+ try {
134
+ return await target.execute(prop, ...args);
135
+ }
136
+ finally {
137
+ totalPendingJobs--;
138
+ }
139
+ };
43
140
  },
44
141
  });
45
142
  return proxy;
46
143
  };
47
- const { close, render, launchWorker, setLimit, waitAll, waitReady } = createSatoruWorker({ maxParallel: 1 });
48
- export { close, render, launchWorker, setLimit, waitAll, waitReady };
144
+ const defaultWorker = createSatoruWorker({ maxParallel: 1 });
145
+ export const { close, render, launchWorker, setLimit, waitAll, waitReady } = defaultWorker;
146
+ export const reset = () => defaultWorker.reset();
147
+ export const getStats = () => defaultWorker.getStats();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "satoru-render",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "High-fidelity HTML/CSS to SVG/PNG/PDF converter running in WebAssembly",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -8,10 +8,6 @@
8
8
  "bin": {
9
9
  "satoru-render": "./dist/cli.js"
10
10
  },
11
- "scripts": {
12
- "build": "tsc -b && rolldown -c rolldown.config.js",
13
- "deploy": "npm publish"
14
- },
15
11
  "exports": {
16
12
  ".": {
17
13
  "workerd": {
@@ -64,6 +60,10 @@
64
60
  "types": "./dist/tailwind.d.ts",
65
61
  "import": "./dist/tailwind.js"
66
62
  },
63
+ "./resources": {
64
+ "types": "./dist/resources.d.ts",
65
+ "import": "./dist/resources.js"
66
+ },
67
67
  "./jsdom": {
68
68
  "types": "./dist/jsdom.d.ts",
69
69
  "import": "./dist/jsdom.js"
@@ -86,6 +86,9 @@
86
86
  "tailwind": [
87
87
  "./dist/tailwind.d.ts"
88
88
  ],
89
+ "resources": [
90
+ "./dist/resources.d.ts"
91
+ ],
89
92
  "jsdom": [
90
93
  "./dist/jsdom.d.ts"
91
94
  ],
@@ -97,7 +100,8 @@
97
100
  "files": [
98
101
  "dist",
99
102
  "README.md",
100
- "LICENSE"
103
+ "LICENSE",
104
+ "CHANGELOG.md"
101
105
  ],
102
106
  "repository": {
103
107
  "type": "git",
@@ -164,5 +168,9 @@
164
168
  "@unocss/preset-wind4": {
165
169
  "optional": true
166
170
  }
171
+ },
172
+ "scripts": {
173
+ "build": "tsc -b && rolldown -c rolldown.config.js",
174
+ "deploy": "npm publish"
167
175
  }
168
- }
176
+ }