@cldmv/slothlet 1.0.1 → 2.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/README.md +862 -73
- package/dist/lib/engine/README.md +21 -0
- package/dist/lib/engine/slothlet_child.mjs +58 -0
- package/dist/lib/engine/slothlet_engine.mjs +371 -0
- package/dist/lib/engine/slothlet_esm.mjs +229 -0
- package/dist/lib/engine/slothlet_helpers.mjs +454 -0
- package/dist/lib/engine/slothlet_worker.mjs +148 -0
- package/dist/lib/helpers/resolve-from-caller.mjs +141 -0
- package/dist/lib/helpers/sanitize.mjs +78 -0
- package/dist/lib/modes/slothlet_eager.mjs +80 -0
- package/dist/lib/modes/slothlet_lazy.mjs +342 -0
- package/dist/lib/runtime/runtime.mjs +249 -0
- package/dist/slothlet.mjs +1092 -0
- package/index.cjs +81 -0
- package/index.mjs +76 -0
- package/package.json +136 -14
- package/types/dist/lib/engine/slothlet_child.d.mts +2 -0
- package/types/dist/lib/engine/slothlet_child.d.mts.map +1 -0
- package/types/dist/lib/engine/slothlet_engine.d.mts +31 -0
- package/types/dist/lib/engine/slothlet_engine.d.mts.map +1 -0
- package/types/dist/lib/engine/slothlet_esm.d.mts +19 -0
- package/types/dist/lib/engine/slothlet_esm.d.mts.map +1 -0
- package/types/dist/lib/engine/slothlet_helpers.d.mts +24 -0
- package/types/dist/lib/engine/slothlet_helpers.d.mts.map +1 -0
- package/types/dist/lib/engine/slothlet_worker.d.mts +2 -0
- package/types/dist/lib/engine/slothlet_worker.d.mts.map +1 -0
- package/types/dist/lib/helpers/resolve-from-caller.d.mts +149 -0
- package/types/dist/lib/helpers/resolve-from-caller.d.mts.map +1 -0
- package/types/dist/lib/helpers/sanitize.d.mts +138 -0
- package/types/dist/lib/helpers/sanitize.d.mts.map +1 -0
- package/types/dist/lib/modes/slothlet_eager.d.mts +66 -0
- package/types/dist/lib/modes/slothlet_eager.d.mts.map +1 -0
- package/types/dist/lib/modes/slothlet_lazy.d.mts +32 -0
- package/types/dist/lib/modes/slothlet_lazy.d.mts.map +1 -0
- package/types/dist/lib/runtime/runtime.d.mts +49 -0
- package/types/dist/lib/runtime/runtime.d.mts.map +1 -0
- package/types/dist/slothlet.d.mts +110 -0
- package/types/dist/slothlet.d.mts.map +1 -0
- package/types/index.d.mts +23 -0
- package/slothlet.mjs +0 -1218
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 CLDMV/Shinrai
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import vm from "node:vm";
|
|
19
|
+
import fs from "node:fs/promises";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
export function normalizeContext(ctx) {
|
|
29
|
+
if (ctx == null) return {};
|
|
30
|
+
if (isPlainObject(ctx)) return { ...ctx };
|
|
31
|
+
if (!Array.isArray(ctx)) {
|
|
32
|
+
const named = guessName(ctx);
|
|
33
|
+
return named ? { [named]: ctx, context: ctx } : { context: ctx };
|
|
34
|
+
}
|
|
35
|
+
const out = {};
|
|
36
|
+
for (const item of ctx) {
|
|
37
|
+
if (Array.isArray(item) && item.length === 2 && typeof item[0] === "string") {
|
|
38
|
+
out[item[0]] = item[1];
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (isPlainObject(item) && "name" in item && "value" in item) {
|
|
42
|
+
out[item.name] = item.value;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (typeof item === "function" && item.name) {
|
|
46
|
+
out[item.name] = item;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
throw new Error("Context array items must be [name,value], {name,value}, or a named function");
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function installGlobalsInCurrentRealm(contextMap) {
|
|
55
|
+
globalThis.context = Object.freeze({ ...contextMap });
|
|
56
|
+
for (const k of Object.keys(contextMap)) globalThis[k] = contextMap[k];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function extendSelfWithReference(self, reference) {
|
|
60
|
+
if (reference && typeof reference === "object") {
|
|
61
|
+
for (const k of Object.keys(reference)) {
|
|
62
|
+
if (!(k in self)) {
|
|
63
|
+
try {
|
|
64
|
+
self[k] = reference[k];
|
|
65
|
+
} catch {
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function installPortalForSelf() {
|
|
74
|
+
function get(o, p) {
|
|
75
|
+
let x = o;
|
|
76
|
+
for (const k of p) {
|
|
77
|
+
if (!x) {
|
|
78
|
+
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
x = x[k];
|
|
83
|
+
}
|
|
84
|
+
return x;
|
|
85
|
+
}
|
|
86
|
+
globalThis._portal = {
|
|
87
|
+
call(path, args) {
|
|
88
|
+
|
|
89
|
+
const obj = get(globalThis.self, path.slice(0, -1));
|
|
90
|
+
const fn = obj ? obj[path[path.length - 1]] : undefined;
|
|
91
|
+
|
|
92
|
+
if (typeof fn !== "function") {
|
|
93
|
+
|
|
94
|
+
throw new TypeError("Resolved value is not a function");
|
|
95
|
+
}
|
|
96
|
+
return fn.apply(obj, args);
|
|
97
|
+
},
|
|
98
|
+
batch(ops) {
|
|
99
|
+
return ops.map((op) => this.call(op.path, op.args || []));
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function asUrl(p) {
|
|
105
|
+
return p && p.startsWith && p.startsWith("file:") ? p : pathToFileURL(path.resolve(String(p))).href;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function isPlainObject(o) {
|
|
109
|
+
return !!o && typeof o === "object" && (o.constructor === Object || Object.getPrototypeOf(o) === null);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function guessName(v) {
|
|
113
|
+
if (typeof v === "function" && v.name) return v.name;
|
|
114
|
+
if (isPlainObject(v) && v.constructor && v.constructor.name && v.constructor.name !== "Object") {
|
|
115
|
+
const n = v.constructor.name;
|
|
116
|
+
return n[0].toLowerCase() + n.slice(1);
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
export const HAS_STM = typeof vm.SourceTextModule === "function";
|
|
126
|
+
|
|
127
|
+
export function makeNodeishContext() {
|
|
128
|
+
const sandbox = {
|
|
129
|
+
console,
|
|
130
|
+
process,
|
|
131
|
+
Buffer,
|
|
132
|
+
setTimeout,
|
|
133
|
+
clearTimeout,
|
|
134
|
+
setInterval,
|
|
135
|
+
clearInterval,
|
|
136
|
+
setImmediate,
|
|
137
|
+
clearImmediate,
|
|
138
|
+
queueMicrotask,
|
|
139
|
+
TextEncoder,
|
|
140
|
+
TextDecoder,
|
|
141
|
+
URL,
|
|
142
|
+
URLSearchParams,
|
|
143
|
+
AbortController,
|
|
144
|
+
AbortSignal,
|
|
145
|
+
performance,
|
|
146
|
+
Function
|
|
147
|
+
};
|
|
148
|
+
const context = vm.createContext(sandbox);
|
|
149
|
+
context.globalThis = context;
|
|
150
|
+
context.global = context;
|
|
151
|
+
return context;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
export async function loadEsmInVm2(context, fileUrl, mode = "auto") {
|
|
156
|
+
console.debug(`[loadEsmInVm] fileUrl: ${fileUrl}, mode: ${mode}`);
|
|
157
|
+
|
|
158
|
+
const code = await fs.readFile(fileURLToPath(fileUrl), "utf8");
|
|
159
|
+
let detectedMode = mode;
|
|
160
|
+
console.debug(`[loadEsmInVm] detectedMode: ${detectedMode} for fileUrl: ${fileUrl}`);
|
|
161
|
+
if (mode === "auto") {
|
|
162
|
+
const ext = path.extname(fileURLToPath(fileUrl)).toLowerCase();
|
|
163
|
+
if (ext === ".mjs") detectedMode = "mjs";
|
|
164
|
+
else if (ext === ".cjs") detectedMode = "cjs";
|
|
165
|
+
else if (ext === ".js") {
|
|
166
|
+
if (/\bimport\b/.test(code)) detectedMode = "mjs";
|
|
167
|
+
else if (/\brequire\b/.test(code)) detectedMode = "cjs";
|
|
168
|
+
else detectedMode = "cjs";
|
|
169
|
+
} else {
|
|
170
|
+
detectedMode = "cjs";
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (detectedMode === "cjs") {
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if (mode === "cjs" || (fileUrl.endsWith(".cjs") && mode === "auto")) {
|
|
179
|
+
console.debug(`[loadEsmInVm] Loading as CJS: ${fileUrl}`);
|
|
180
|
+
|
|
181
|
+
if (arguments.callee.caller && arguments.callee.caller.name === "loadEsmInVm") {
|
|
182
|
+
throw new Error("CJS modules are not supported for linker/dynamic import. Only ESM (.mjs) modules are allowed.");
|
|
183
|
+
}
|
|
184
|
+
console.debug(
|
|
185
|
+
`[loadEsmInVm] Returning CJS namespace for ${fileUrl}:`,
|
|
186
|
+
namespace,
|
|
187
|
+
"type:",
|
|
188
|
+
typeof namespace,
|
|
189
|
+
"constructor:",
|
|
190
|
+
namespace?.constructor?.name
|
|
191
|
+
);
|
|
192
|
+
console.debug(
|
|
193
|
+
`[loadEsmInVm] [RETURN]`,
|
|
194
|
+
{ namespace },
|
|
195
|
+
"type:",
|
|
196
|
+
typeof { namespace },
|
|
197
|
+
"constructor:",
|
|
198
|
+
{ namespace }?.constructor?.name
|
|
199
|
+
);
|
|
200
|
+
if (!context.require) {
|
|
201
|
+
context.require = require;
|
|
202
|
+
}
|
|
203
|
+
const script = new vm.Script(code, { filename: fileUrl });
|
|
204
|
+
script.runInContext(context);
|
|
205
|
+
const namespace = {};
|
|
206
|
+
for (const k of Object.keys(context)) {
|
|
207
|
+
if (k !== "globalThis" && k !== "global") {
|
|
208
|
+
namespace[k] = context[k];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
console.debug(`[loadEsmInVm] Returning CJS namespace for ${fileUrl}:`, namespace, "type:", typeof namespace);
|
|
212
|
+
return { namespace };
|
|
213
|
+
} else {
|
|
214
|
+
console.error(`[loadEsmInVm] CJS not allowed for linker/dynamic import: ${fileUrl}`);
|
|
215
|
+
throw new Error("CJS modules are not supported in linker/dynamic import. Only ESM (.mjs) modules are allowed.");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (detectedMode === "mjs") {
|
|
219
|
+
console.debug(`[loadEsmInVm] Loading as ESM: ${fileUrl}`);
|
|
220
|
+
if (HAS_STM) {
|
|
221
|
+
const mod = new vm.SourceTextModule(code, {
|
|
222
|
+
context,
|
|
223
|
+
identifier: fileUrl,
|
|
224
|
+
initializeImportMeta(m) {
|
|
225
|
+
m.url = fileUrl;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
await mod.evaluate();
|
|
274
|
+
console.debug("[loadEsmInVm] returning top-level SourceTextModule:", mod, "status:", mod.status);
|
|
275
|
+
console.debug(`[loadEsmInVm] Returning SourceTextModule for ${fileUrl}:`, mod);
|
|
276
|
+
return mod;
|
|
277
|
+
} else {
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
throw new Error("ESM (import/export) not supported in VM context without vm.SourceTextModule. Custom parser not implemented yet.");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
console.error(`[loadEsmInVm] [RETURN] ERROR: Unknown module mode: ${detectedMode}`);
|
|
290
|
+
throw new Error(`Unknown module mode: ${detectedMode}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const MODULE_CACHE = Symbol.for("slothlet.vm.moduleCache");
|
|
294
|
+
export async function loadEsmInVm(context, fileUrl) {
|
|
295
|
+
if (!HAS_STM) {
|
|
296
|
+
throw new Error("vm.SourceTextModule not available; cannot load ESM inside vm context");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const cache = (context[MODULE_CACHE] ||= new Map());
|
|
300
|
+
if (cache.has(fileUrl)) return cache.get(fileUrl);
|
|
301
|
+
|
|
302
|
+
const code = await fs.readFile(fileURLToPath(fileUrl), "utf8");
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
const link = async (specifier, referencingModule) => {
|
|
306
|
+
const isRel = specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/") || specifier.startsWith("file:");
|
|
307
|
+
|
|
308
|
+
if (isRel) {
|
|
309
|
+
const childUrl = new URL(specifier, referencingModule.identifier).href;
|
|
310
|
+
return await loadEsmInVm(context, childUrl);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
const ns = await import(specifier);
|
|
315
|
+
const sm = new vm.SyntheticModule(
|
|
316
|
+
Object.keys(ns),
|
|
317
|
+
function init() {
|
|
318
|
+
for (const k of Object.keys(ns)) this.setExport(k, ns[k]);
|
|
319
|
+
},
|
|
320
|
+
{ context }
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
return sm;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
const importModuleDynamically = async (specifier, referencingModule) => {
|
|
328
|
+
const child = await link(specifier, referencingModule);
|
|
329
|
+
|
|
330
|
+
await child.evaluate();
|
|
331
|
+
return child;
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const mod = new vm.SourceTextModule(code, {
|
|
335
|
+
context,
|
|
336
|
+
identifier: fileUrl,
|
|
337
|
+
initializeImportMeta(meta) {
|
|
338
|
+
meta.url = fileUrl;
|
|
339
|
+
},
|
|
340
|
+
importModuleDynamically
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
cache.set(fileUrl, mod);
|
|
344
|
+
await mod.link(link);
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
return mod;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function installContextGlobalsVM(context, userContext) {
|
|
351
|
+
const map = normalizeContext(userContext);
|
|
352
|
+
context.__ctx = map;
|
|
353
|
+
vm.runInContext(
|
|
354
|
+
`
|
|
355
|
+
globalThis.context = Object.freeze({ ...__ctx });
|
|
356
|
+
for (const k of Object.keys(__ctx)) { globalThis[k] = __ctx[k]; }
|
|
357
|
+
`,
|
|
358
|
+
context,
|
|
359
|
+
{ filename: "slothlet_helpers-installContextGlobalsVM.mjs" }
|
|
360
|
+
);
|
|
361
|
+
delete context.__ctx;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export async function bootSlothletVM(context, entryUrl, loadConfig, ctxRef) {
|
|
365
|
+
const mod = await loadEsmInVm(context, entryUrl);
|
|
366
|
+
await mod.evaluate();
|
|
367
|
+
console.log(mod.namespace);
|
|
368
|
+
console.log("mod.namespace?.slothlet", mod.namespace?.slothlet);
|
|
369
|
+
console.log("mod.namespace?.default", mod.namespace?.default);
|
|
370
|
+
console.log(entryUrl);
|
|
371
|
+
let sloth = mod.namespace?.slothlet;
|
|
372
|
+
if (!sloth && mod.namespace?.default) {
|
|
373
|
+
|
|
374
|
+
if (mod.namespace.default.slothlet) {
|
|
375
|
+
sloth = mod.namespace.default.slothlet;
|
|
376
|
+
} else {
|
|
377
|
+
|
|
378
|
+
sloth = mod.namespace.default;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (!sloth) throw new Error("Entry did not export `slothlet`");
|
|
382
|
+
context.__slothlet = sloth;
|
|
383
|
+
context.__loadConfig = loadConfig;
|
|
384
|
+
context.__ctxRef = ctxRef;
|
|
385
|
+
vm.runInContext(
|
|
386
|
+
`
|
|
387
|
+
globalThis.slothletReady = global.slothletReady = new Promise(async (resolve) => {
|
|
388
|
+
globalThis.slothlet = global.slothlet = __slothlet;
|
|
389
|
+
const ret = await globalThis.slothlet.load(__loadConfig, __ctxRef);
|
|
390
|
+
globalThis.self = global.self = ret;
|
|
391
|
+
const ref = __ctxRef?.reference;
|
|
392
|
+
if (ref && typeof ref === 'object') {
|
|
393
|
+
for (const k of Object.keys(ref)) if (!(k in globalThis.self)) {
|
|
394
|
+
try { globalThis.self[k] = global.self[k] = ref[k]; } catch {}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function get(o,p){ return p.reduce((x,k)=>x[k], o); }
|
|
398
|
+
globalThis._portal = global._portal = {
|
|
399
|
+
call(path, args){
|
|
400
|
+
const obj = get(globalThis.self || global.self, path.slice(0,-1));
|
|
401
|
+
const fn = obj[path[path.length-1]];
|
|
402
|
+
return fn.apply(obj, args);
|
|
403
|
+
},
|
|
404
|
+
batch(ops){ return ops.map(op => this.call(op.path, op.args || [])); }
|
|
405
|
+
};
|
|
406
|
+
resolve();
|
|
407
|
+
});
|
|
408
|
+
`,
|
|
409
|
+
context,
|
|
410
|
+
{ filename: "slothlet_helpers-bootSlothletVM.mjs" }
|
|
411
|
+
);
|
|
412
|
+
delete context.__slothlet;
|
|
413
|
+
delete context.__loadConfig;
|
|
414
|
+
delete context.__ctxRef;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
export function marshalArgsReplaceFunctions(value, registerCb) {
|
|
420
|
+
function walk(v) {
|
|
421
|
+
if (typeof v === "function") {
|
|
422
|
+
const id = registerCb(v);
|
|
423
|
+
return { __cb: id };
|
|
424
|
+
}
|
|
425
|
+
if (!v || typeof v !== "object") return v;
|
|
426
|
+
if (Array.isArray(v)) return v.map(walk);
|
|
427
|
+
const out = {};
|
|
428
|
+
for (const k of Object.keys(v)) out[k] = walk(v[k]);
|
|
429
|
+
return out;
|
|
430
|
+
}
|
|
431
|
+
return walk(value);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export function reviveArgsReplaceTokens(value, invokeCb) {
|
|
435
|
+
function walk(v) {
|
|
436
|
+
if (v && typeof v === "object" && typeof v.__cb === "number") {
|
|
437
|
+
const id = v.__cb;
|
|
438
|
+
return (...args) => invokeCb(id, args);
|
|
439
|
+
}
|
|
440
|
+
if (!v || typeof v !== "object") return v;
|
|
441
|
+
if (Array.isArray(v)) return v.map(walk);
|
|
442
|
+
const out = {};
|
|
443
|
+
for (const k of Object.keys(v)) out[k] = walk(v[k]);
|
|
444
|
+
return out;
|
|
445
|
+
}
|
|
446
|
+
return walk(value);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export function containsFunction(value) {
|
|
450
|
+
if (typeof value === "function") return true;
|
|
451
|
+
if (!value || typeof value !== "object") return false;
|
|
452
|
+
if (Array.isArray(value)) return value.some(containsFunction);
|
|
453
|
+
return Object.values(value).some(containsFunction);
|
|
454
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 CLDMV/Shinrai
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
19
|
+
import {
|
|
20
|
+
installGlobalsInCurrentRealm,
|
|
21
|
+
extendSelfWithReference,
|
|
22
|
+
installPortalForSelf,
|
|
23
|
+
reviveArgsReplaceTokens
|
|
24
|
+
} from "./slothlet_helpers.mjs";
|
|
25
|
+
|
|
26
|
+
const { entry, loadConfig, contextMap, reference } = workerData;
|
|
27
|
+
|
|
28
|
+
(async () => {
|
|
29
|
+
|
|
30
|
+
installGlobalsInCurrentRealm(contextMap);
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
const { slothlet } = await import(entry);
|
|
34
|
+
if (!slothlet) throw new Error("Entry did not export `slothlet`");
|
|
35
|
+
|
|
36
|
+
const api = await slothlet.load(loadConfig, { context: contextMap, reference });
|
|
37
|
+
|
|
38
|
+
Object.defineProperty(api, "__dispose__", {
|
|
39
|
+
value: async () => {
|
|
40
|
+
parentPort.postMessage({ t: "dispose" });
|
|
41
|
+
setImmediate(() => process.exit(0));
|
|
42
|
+
},
|
|
43
|
+
writable: false,
|
|
44
|
+
enumerable: false,
|
|
45
|
+
configurable: false
|
|
46
|
+
});
|
|
47
|
+
globalThis.self = api;
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
extendSelfWithReference(globalThis.self, reference);
|
|
51
|
+
installPortalForSelf();
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
const callbackResults = new Map();
|
|
56
|
+
parentPort.on("message", async (msg) => {
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if (msg && msg.t === "callbackResult" && typeof msg.cb === "number") {
|
|
60
|
+
|
|
61
|
+
const resolver = callbackResults.get(msg.cb);
|
|
62
|
+
if (resolver) {
|
|
63
|
+
callbackResults.delete(msg.cb);
|
|
64
|
+
if (msg.ok) resolver.resolve(msg.result);
|
|
65
|
+
else resolver.reject(new Error(msg.error));
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
|
|
71
|
+
function invokeCb(cbId, args) {
|
|
72
|
+
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
callbackResults.set(cbId, { resolve, reject });
|
|
75
|
+
parentPort.postMessage({ t: "callback", cb: cbId, args });
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (msg.t === "call") {
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if (Array.isArray(msg.path) && msg.path.length === 1 && msg.path[0] === "describe") {
|
|
82
|
+
function serialize(x) {
|
|
83
|
+
if (typeof x === "function") {
|
|
84
|
+
const props = {};
|
|
85
|
+
for (const k of Object.keys(x)) props[k] = serialize(x[k]);
|
|
86
|
+
return { __fn: x.name || "", props };
|
|
87
|
+
}
|
|
88
|
+
if (x && typeof x === "object") {
|
|
89
|
+
if (Array.isArray(x)) return x.map(serialize);
|
|
90
|
+
const out = {};
|
|
91
|
+
for (const [k, v] of Object.entries(x)) out[k] = serialize(v);
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
return x;
|
|
95
|
+
}
|
|
96
|
+
parentPort.postMessage({ id: msg.id, ok: 1, result: serialize(globalThis.self) });
|
|
97
|
+
return;
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
const revivedArgs = reviveArgsReplaceTokens(msg.args || [], invokeCb);
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
let target = globalThis.self;
|
|
106
|
+
if (Array.isArray(msg.path)) {
|
|
107
|
+
for (const seg of msg.path) {
|
|
108
|
+
if (target == null) break;
|
|
109
|
+
target = target[seg];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
let result;
|
|
113
|
+
if (typeof target === "function") {
|
|
114
|
+
|
|
115
|
+
result = await target(...revivedArgs);
|
|
116
|
+
} else if (target && typeof target === "object" && typeof target.default === "function") {
|
|
117
|
+
|
|
118
|
+
result = await target.default(...revivedArgs);
|
|
119
|
+
} else {
|
|
120
|
+
throw new TypeError("Resolved value is not a function");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
parentPort.postMessage({ id: msg.id, ok: 1, result });
|
|
124
|
+
} else if (msg.t === "batch") {
|
|
125
|
+
|
|
126
|
+
const revivedOps = Array.isArray(msg.ops)
|
|
127
|
+
? msg.ops.map((op) => ({ ...op, args: reviveArgsReplaceTokens(op.args || [], invokeCb) }))
|
|
128
|
+
: msg.ops;
|
|
129
|
+
|
|
130
|
+
const result = await globalThis._portal.batch(revivedOps);
|
|
131
|
+
|
|
132
|
+
parentPort.postMessage({ id: msg.id, ok: 1, result });
|
|
133
|
+
} else if (msg.t === "dispose") {
|
|
134
|
+
|
|
135
|
+
parentPort.postMessage({ id: msg.id, ok: 1, result: true });
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
} catch (e) {
|
|
143
|
+
parentPort.postMessage({ id: msg.id, ok: 0, error: String(e?.stack || e) });
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
})().catch((e) => {
|
|
147
|
+
parentPort.postMessage({ id: 0, ok: 0, error: String(e?.stack || e) });
|
|
148
|
+
});
|