elit 3.5.6 → 3.5.8
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/Cargo.toml +1 -1
- package/README.md +1 -1
- package/desktop/build.rs +83 -0
- package/desktop/icon.rs +106 -0
- package/desktop/lib.rs +2 -0
- package/desktop/main.rs +235 -0
- package/desktop/native_main.rs +128 -0
- package/desktop/native_renderer/action_widgets.rs +184 -0
- package/desktop/native_renderer/app_models.rs +171 -0
- package/desktop/native_renderer/app_runtime.rs +140 -0
- package/desktop/native_renderer/container_rendering.rs +610 -0
- package/desktop/native_renderer/content_widgets.rs +634 -0
- package/desktop/native_renderer/css_models.rs +371 -0
- package/desktop/native_renderer/embedded_surfaces.rs +414 -0
- package/desktop/native_renderer/form_controls.rs +516 -0
- package/desktop/native_renderer/interaction_dispatch.rs +89 -0
- package/desktop/native_renderer/runtime_support.rs +135 -0
- package/desktop/native_renderer/utilities.rs +495 -0
- package/desktop/native_renderer/vector_drawing.rs +491 -0
- package/desktop/native_renderer.rs +4122 -0
- package/desktop/runtime/external.rs +422 -0
- package/desktop/runtime/mod.rs +67 -0
- package/desktop/runtime/quickjs.rs +106 -0
- package/desktop/window.rs +383 -0
- package/dist/build.d.ts +1 -1
- package/dist/cli.cjs +16 -2
- package/dist/cli.mjs +16 -2
- package/dist/config.d.ts +1 -1
- package/dist/coverage.d.ts +1 -1
- package/dist/desktop-auto-render.cjs +2370 -0
- package/dist/desktop-auto-render.d.ts +13 -0
- package/dist/desktop-auto-render.js +2341 -0
- package/dist/desktop-auto-render.mjs +2344 -0
- package/dist/render-context.cjs +118 -0
- package/dist/render-context.d.ts +39 -0
- package/dist/render-context.js +77 -0
- package/dist/render-context.mjs +87 -0
- package/dist/{server-CNgDUgSZ.d.ts → server-FCdUqabc.d.ts} +1 -1
- package/dist/server.d.ts +1 -1
- package/package.json +26 -3
- package/dist/build.d.mts +0 -20
- package/dist/chokidar.d.mts +0 -134
- package/dist/cli.d.mts +0 -81
- package/dist/config.d.mts +0 -254
- package/dist/coverage.d.mts +0 -85
- package/dist/database.d.mts +0 -52
- package/dist/desktop.d.mts +0 -68
- package/dist/dom.d.mts +0 -87
- package/dist/el.d.mts +0 -208
- package/dist/fs.d.mts +0 -255
- package/dist/hmr.d.mts +0 -38
- package/dist/http.d.mts +0 -169
- package/dist/https.d.mts +0 -108
- package/dist/index.d.mts +0 -13
- package/dist/mime-types.d.mts +0 -48
- package/dist/native.d.mts +0 -136
- package/dist/path.d.mts +0 -163
- package/dist/router.d.mts +0 -49
- package/dist/runtime.d.mts +0 -97
- package/dist/server-D0Dp4R5z.d.mts +0 -449
- package/dist/server.d.mts +0 -7
- package/dist/state.d.mts +0 -117
- package/dist/style.d.mts +0 -232
- package/dist/test-reporter.d.mts +0 -77
- package/dist/test-runtime.d.mts +0 -122
- package/dist/test.d.mts +0 -39
- package/dist/types.d.mts +0 -586
- package/dist/universal.d.mts +0 -21
- package/dist/ws.d.mts +0 -200
- package/dist/wss.d.mts +0 -108
- package/src/build.ts +0 -362
- package/src/chokidar.ts +0 -427
- package/src/cli.ts +0 -1162
- package/src/config.ts +0 -509
- package/src/coverage.ts +0 -1479
- package/src/database.ts +0 -1410
- package/src/desktop-auto-render.ts +0 -317
- package/src/desktop-cli.ts +0 -1533
- package/src/desktop.ts +0 -99
- package/src/dev-build.ts +0 -340
- package/src/dom.ts +0 -901
- package/src/el.ts +0 -183
- package/src/fs.ts +0 -609
- package/src/hmr.ts +0 -149
- package/src/http.ts +0 -856
- package/src/https.ts +0 -411
- package/src/index.ts +0 -16
- package/src/mime-types.ts +0 -222
- package/src/mobile-cli.ts +0 -2313
- package/src/native-background.ts +0 -444
- package/src/native-border.ts +0 -343
- package/src/native-canvas.ts +0 -260
- package/src/native-cli.ts +0 -414
- package/src/native-color.ts +0 -904
- package/src/native-estimation.ts +0 -194
- package/src/native-grid.ts +0 -590
- package/src/native-interaction.ts +0 -1289
- package/src/native-layout.ts +0 -568
- package/src/native-link.ts +0 -76
- package/src/native-render-support.ts +0 -361
- package/src/native-spacing.ts +0 -231
- package/src/native-state.ts +0 -318
- package/src/native-strings.ts +0 -46
- package/src/native-transform.ts +0 -120
- package/src/native-types.ts +0 -439
- package/src/native-typography.ts +0 -254
- package/src/native-units.ts +0 -441
- package/src/native-vector.ts +0 -910
- package/src/native.ts +0 -5606
- package/src/path.ts +0 -493
- package/src/pm-cli.ts +0 -2498
- package/src/preview-build.ts +0 -294
- package/src/render-context.ts +0 -138
- package/src/router.ts +0 -260
- package/src/runtime.ts +0 -97
- package/src/server.ts +0 -2294
- package/src/state.ts +0 -556
- package/src/style.ts +0 -1790
- package/src/test-globals.d.ts +0 -184
- package/src/test-reporter.ts +0 -609
- package/src/test-runtime.ts +0 -1359
- package/src/test.ts +0 -368
- package/src/types.ts +0 -381
- package/src/universal.ts +0 -81
- package/src/wapk-cli.ts +0 -3213
- package/src/workspace-package.ts +0 -102
- package/src/ws.ts +0 -648
- package/src/wss.ts +0 -241
package/src/database.ts
DELETED
|
@@ -1,1410 +0,0 @@
|
|
|
1
|
-
import vm from "node:vm";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import * as nodeModule from 'node:module';
|
|
5
|
-
|
|
6
|
-
interface VMOptions {
|
|
7
|
-
language?: 'ts' | 'js';
|
|
8
|
-
registerModules?: { [key: string]: any };
|
|
9
|
-
dir?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
type VMModuleLoader = 'ts' | 'tsx' | 'js' | 'jsx';
|
|
13
|
-
|
|
14
|
-
interface VMTranspileOptions {
|
|
15
|
-
filename?: string;
|
|
16
|
-
format?: 'cjs';
|
|
17
|
-
loader?: VMModuleLoader;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface VMTransformResult {
|
|
21
|
-
code: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
type StripTypeScriptTypes = (
|
|
25
|
-
code: string,
|
|
26
|
-
options?: {
|
|
27
|
-
mode?: 'strip' | 'transform';
|
|
28
|
-
sourceMap?: boolean;
|
|
29
|
-
sourceUrl?: string;
|
|
30
|
-
},
|
|
31
|
-
) => string;
|
|
32
|
-
|
|
33
|
-
const stripTypeScriptTypes = typeof (nodeModule as { stripTypeScriptTypes?: unknown }).stripTypeScriptTypes === 'function'
|
|
34
|
-
? ((nodeModule as { stripTypeScriptTypes: StripTypeScriptTypes }).stripTypeScriptTypes)
|
|
35
|
-
: undefined;
|
|
36
|
-
|
|
37
|
-
let cachedEsbuildTransformSync:
|
|
38
|
-
| ((code: string, options: { loader?: VMModuleLoader; format?: 'cjs' }) => { code: string })
|
|
39
|
-
| null
|
|
40
|
-
| undefined;
|
|
41
|
-
|
|
42
|
-
function getEsbuildTransformSync() {
|
|
43
|
-
if (cachedEsbuildTransformSync !== undefined) {
|
|
44
|
-
return cachedEsbuildTransformSync;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (typeof nodeModule.createRequire !== 'function') {
|
|
48
|
-
cachedEsbuildTransformSync = null;
|
|
49
|
-
return cachedEsbuildTransformSync;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const requireFromApp = nodeModule.createRequire(path.join(process.cwd(), 'package.json'));
|
|
54
|
-
const esbuildModule = requireFromApp('esbuild') as {
|
|
55
|
-
transformSync?: (code: string, options: { loader?: VMModuleLoader; format?: 'cjs' }) => { code: string };
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
cachedEsbuildTransformSync = typeof esbuildModule?.transformSync === 'function'
|
|
59
|
-
? esbuildModule.transformSync.bind(esbuildModule)
|
|
60
|
-
: null;
|
|
61
|
-
} catch {
|
|
62
|
-
cachedEsbuildTransformSync = null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return cachedEsbuildTransformSync;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function parseModuleBindings(specifiers: string): Array<{ imported: string; local: string }> {
|
|
69
|
-
return specifiers
|
|
70
|
-
.split(',')
|
|
71
|
-
.map((entry) => entry.trim())
|
|
72
|
-
.filter((entry) => entry.length > 0)
|
|
73
|
-
.map((entry) => {
|
|
74
|
-
const [imported, local] = entry.split(/\s+as\s+/);
|
|
75
|
-
return {
|
|
76
|
-
imported: (imported || '').trim(),
|
|
77
|
-
local: (local || imported || '').trim(),
|
|
78
|
-
};
|
|
79
|
-
})
|
|
80
|
-
.filter((entry) => entry.imported.length > 0 && entry.local.length > 0);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function formatNamedImportBindings(specifiers: string): string {
|
|
84
|
-
return parseModuleBindings(specifiers)
|
|
85
|
-
.map(({ imported, local }) => imported === local ? imported : `${imported}: ${local}`)
|
|
86
|
-
.join(', ');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function formatNamedExportAssignments(specifiers: string): string {
|
|
90
|
-
return parseModuleBindings(specifiers)
|
|
91
|
-
.map(({ imported, local }) => `module.exports.${local} = ${imported};`)
|
|
92
|
-
.join('\n');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function stripTypescriptSource(source: string, filename: string): string {
|
|
96
|
-
if (!stripTypeScriptTypes) {
|
|
97
|
-
throw new Error('TypeScript database execution requires Node.js 22+ or the esbuild package.');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const originalEmitWarning = process.emitWarning;
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
process.emitWarning = (((warning: string | Error, ...args: any[]) => {
|
|
104
|
-
if (typeof warning === 'string' && warning.includes('stripTypeScriptTypes')) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return (originalEmitWarning as any).call(process, warning, ...args);
|
|
109
|
-
}) as typeof process.emitWarning);
|
|
110
|
-
|
|
111
|
-
return stripTypeScriptTypes(source, {
|
|
112
|
-
mode: 'transform',
|
|
113
|
-
sourceUrl: filename,
|
|
114
|
-
});
|
|
115
|
-
} finally {
|
|
116
|
-
process.emitWarning = originalEmitWarning;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function isSimpleIdentifier(value: string): boolean {
|
|
121
|
-
return /^[A-Za-z_$][\w$]*$/.test(value);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function stripOptionalLineTerminator(value: string): string {
|
|
125
|
-
const trimmed = value.trim();
|
|
126
|
-
return trimmed.endsWith(';')
|
|
127
|
-
? trimmed.slice(0, -1).trimEnd()
|
|
128
|
-
: trimmed;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function parseQuotedModulePath(value: string): string | null {
|
|
132
|
-
const trimmed = value.trim();
|
|
133
|
-
if (trimmed.length < 2) {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const quote = trimmed[0];
|
|
138
|
-
if ((quote !== '"' && quote !== "'") || trimmed[trimmed.length - 1] !== quote) {
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return trimmed.slice(1, -1);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function getLineIndentation(line: string): string {
|
|
146
|
-
const match = line.match(/^\s*/);
|
|
147
|
-
return match?.[0] ?? '';
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function rewriteImportLine(
|
|
151
|
-
line: string,
|
|
152
|
-
nextImportBinding: () => string,
|
|
153
|
-
resolveDefaultImport: (bindingName: string) => string,
|
|
154
|
-
): string | null {
|
|
155
|
-
const indentation = getLineIndentation(line);
|
|
156
|
-
const trimmed = stripOptionalLineTerminator(line);
|
|
157
|
-
if (!trimmed.startsWith('import ')) {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const importBody = trimmed.slice('import '.length).trim();
|
|
162
|
-
const sideEffectModulePath = parseQuotedModulePath(importBody);
|
|
163
|
-
if (sideEffectModulePath !== null) {
|
|
164
|
-
return `${indentation}require(${JSON.stringify(sideEffectModulePath)});`;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const fromIndex = importBody.lastIndexOf(' from ');
|
|
168
|
-
if (fromIndex === -1) {
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const clause = importBody.slice(0, fromIndex).trim();
|
|
173
|
-
const modulePath = parseQuotedModulePath(importBody.slice(fromIndex + 6));
|
|
174
|
-
if (!clause || modulePath === null) {
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const buildDefaultImport = (defaultName: string): { bindingName: string; code: string } | null => {
|
|
179
|
-
if (!isSimpleIdentifier(defaultName)) {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const bindingName = nextImportBinding();
|
|
184
|
-
return {
|
|
185
|
-
bindingName,
|
|
186
|
-
code: [
|
|
187
|
-
`${indentation}const ${bindingName} = require(${JSON.stringify(modulePath)});`,
|
|
188
|
-
`${indentation}const ${defaultName} = ${resolveDefaultImport(bindingName)};`,
|
|
189
|
-
].join('\n'),
|
|
190
|
-
};
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
if (clause.startsWith('* as ')) {
|
|
194
|
-
const namespaceName = clause.slice(5).trim();
|
|
195
|
-
return isSimpleIdentifier(namespaceName)
|
|
196
|
-
? `${indentation}const ${namespaceName} = require(${JSON.stringify(modulePath)});`
|
|
197
|
-
: null;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (clause.startsWith('{') && clause.endsWith('}')) {
|
|
201
|
-
const namedBindings = clause.slice(1, -1).trim();
|
|
202
|
-
return namedBindings.length > 0
|
|
203
|
-
? `${indentation}const { ${formatNamedImportBindings(namedBindings)} } = require(${JSON.stringify(modulePath)});`
|
|
204
|
-
: null;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const commaIndex = clause.indexOf(',');
|
|
208
|
-
if (commaIndex !== -1) {
|
|
209
|
-
const defaultName = clause.slice(0, commaIndex).trim();
|
|
210
|
-
const remainder = clause.slice(commaIndex + 1).trim();
|
|
211
|
-
const defaultImport = buildDefaultImport(defaultName);
|
|
212
|
-
if (!defaultImport) {
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (remainder.startsWith('* as ')) {
|
|
217
|
-
const namespaceName = remainder.slice(5).trim();
|
|
218
|
-
if (!isSimpleIdentifier(namespaceName)) {
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return `${defaultImport.code}\n${indentation}const ${namespaceName} = ${defaultImport.bindingName};`;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (remainder.startsWith('{') && remainder.endsWith('}')) {
|
|
226
|
-
const namedBindings = remainder.slice(1, -1).trim();
|
|
227
|
-
if (!namedBindings) {
|
|
228
|
-
return null;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return `${defaultImport.code}\n${indentation}const { ${formatNamedImportBindings(namedBindings)} } = ${defaultImport.bindingName};`;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return buildDefaultImport(clause)?.code ?? null;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function rewriteExportLine(
|
|
241
|
-
line: string,
|
|
242
|
-
namedExports: Set<string>,
|
|
243
|
-
markDefaultExport: () => void,
|
|
244
|
-
): string | null {
|
|
245
|
-
const trimmed = stripOptionalLineTerminator(line);
|
|
246
|
-
if (!trimmed.startsWith('export ')) {
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const indentation = getLineIndentation(line);
|
|
251
|
-
|
|
252
|
-
if (trimmed.startsWith('export default ')) {
|
|
253
|
-
markDefaultExport();
|
|
254
|
-
return `${indentation}module.exports = ${trimmed.slice('export default '.length)}`;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const valueDeclarationMatch = /^export\s+(const|let|var)\s+([A-Za-z_$][\w$]*)\b/.exec(trimmed);
|
|
258
|
-
if (valueDeclarationMatch) {
|
|
259
|
-
namedExports.add(valueDeclarationMatch[2]);
|
|
260
|
-
return `${indentation}${trimmed.slice('export '.length)}`;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const asyncFunctionMatch = /^export\s+async\s+function\s+([A-Za-z_$][\w$]*)\b/.exec(trimmed);
|
|
264
|
-
if (asyncFunctionMatch) {
|
|
265
|
-
namedExports.add(asyncFunctionMatch[1]);
|
|
266
|
-
return `${indentation}${trimmed.slice('export '.length)}`;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const functionMatch = /^export\s+function\s+([A-Za-z_$][\w$]*)\b/.exec(trimmed);
|
|
270
|
-
if (functionMatch) {
|
|
271
|
-
namedExports.add(functionMatch[1]);
|
|
272
|
-
return `${indentation}${trimmed.slice('export '.length)}`;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const classMatch = /^export\s+class\s+([A-Za-z_$][\w$]*)\b/.exec(trimmed);
|
|
276
|
-
if (classMatch) {
|
|
277
|
-
namedExports.add(classMatch[1]);
|
|
278
|
-
return `${indentation}${trimmed.slice('export '.length)}`;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (trimmed.startsWith('export {') && trimmed.endsWith('}')) {
|
|
282
|
-
const specifiers = trimmed.slice('export {'.length, -1).trim();
|
|
283
|
-
return specifiers.length > 0
|
|
284
|
-
? `${indentation}${formatNamedExportAssignments(specifiers)}`
|
|
285
|
-
: null;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function rewriteModuleSyntaxToCommonJs(source: string): string {
|
|
292
|
-
let importCounter = 0;
|
|
293
|
-
let hasDefaultExport = false;
|
|
294
|
-
const namedExports = new Set<string>();
|
|
295
|
-
|
|
296
|
-
const nextImportBinding = () => `__vm_import_${importCounter++}`;
|
|
297
|
-
const resolveDefaultImport = (bindingName: string) => `${bindingName} && Object.prototype.hasOwnProperty.call(${bindingName}, "default") ? ${bindingName}.default : ${bindingName}`;
|
|
298
|
-
const code = source
|
|
299
|
-
.split(/\r?\n/)
|
|
300
|
-
.map((line) => rewriteImportLine(line, nextImportBinding, resolveDefaultImport)
|
|
301
|
-
?? rewriteExportLine(line, namedExports, () => {
|
|
302
|
-
hasDefaultExport = true;
|
|
303
|
-
})
|
|
304
|
-
?? line)
|
|
305
|
-
.join('\n');
|
|
306
|
-
|
|
307
|
-
const exportFooter = [...namedExports].map((name) => `module.exports.${name} = ${name};`);
|
|
308
|
-
if (hasDefaultExport) {
|
|
309
|
-
exportFooter.push('module.exports.default = module.exports;');
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return exportFooter.length > 0
|
|
313
|
-
? `${code.trimEnd()}\n${exportFooter.join('\n')}\n`
|
|
314
|
-
: code;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function transpileVmModule(source: string, options: VMTranspileOptions = {}): VMTransformResult {
|
|
318
|
-
const loader = options.loader || 'js';
|
|
319
|
-
const filename = options.filename || `virtual.${loader}`;
|
|
320
|
-
|
|
321
|
-
if (loader === 'tsx' || loader === 'jsx') {
|
|
322
|
-
const esbuildTransformSync = getEsbuildTransformSync();
|
|
323
|
-
if (!esbuildTransformSync) {
|
|
324
|
-
throw new Error(`JSX database execution requires the esbuild package (${filename}).`);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return esbuildTransformSync(source, {
|
|
328
|
-
loader,
|
|
329
|
-
format: options.format,
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (loader === 'ts') {
|
|
334
|
-
try {
|
|
335
|
-
return {
|
|
336
|
-
code: rewriteModuleSyntaxToCommonJs(stripTypescriptSource(source, filename)),
|
|
337
|
-
};
|
|
338
|
-
} catch (error) {
|
|
339
|
-
const esbuildTransformSync = getEsbuildTransformSync();
|
|
340
|
-
if (!esbuildTransformSync) {
|
|
341
|
-
throw error;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
return esbuildTransformSync(source, {
|
|
345
|
-
loader,
|
|
346
|
-
format: options.format,
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return {
|
|
352
|
-
code: rewriteModuleSyntaxToCommonJs(source),
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
class VM {
|
|
357
|
-
private transpiler: (code: string, options?: VMTranspileOptions) => VMTransformResult;
|
|
358
|
-
private ctx: vm.Context;
|
|
359
|
-
private registerModules: { [key: string]: any };
|
|
360
|
-
private DATABASE_DIR: string;
|
|
361
|
-
private SCRIPTDB_DIR: string;
|
|
362
|
-
private pkgScriptDB: { dependencies?: Record<string, string> } = {};
|
|
363
|
-
private language: 'ts' | 'js';
|
|
364
|
-
private _registerModules: { [key: string]: any };
|
|
365
|
-
private options: VMOptions;
|
|
366
|
-
constructor(options?: VMOptions) {
|
|
367
|
-
this.options = options || {};
|
|
368
|
-
// Set directories based on options or defaults
|
|
369
|
-
this.DATABASE_DIR = options?.dir || path.join(process.cwd(), 'databases');
|
|
370
|
-
this.SCRIPTDB_DIR = process.cwd();
|
|
371
|
-
|
|
372
|
-
// Ensure directories exist
|
|
373
|
-
if (!fs.existsSync(this.DATABASE_DIR)) {
|
|
374
|
-
fs.mkdirSync(this.DATABASE_DIR, { recursive: true });
|
|
375
|
-
}
|
|
376
|
-
if (!fs.existsSync(this.SCRIPTDB_DIR)) {
|
|
377
|
-
fs.mkdirSync(this.SCRIPTDB_DIR, { recursive: true });
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Load scriptdb workspace package.json if it exists
|
|
381
|
-
const pkgPath = path.join(this.SCRIPTDB_DIR, 'package.json');
|
|
382
|
-
if (fs.existsSync(pkgPath)) {
|
|
383
|
-
this.pkgScriptDB = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
384
|
-
}
|
|
385
|
-
this.language = options?.language || 'ts';
|
|
386
|
-
this.transpiler = transpileVmModule;
|
|
387
|
-
|
|
388
|
-
this.registerModules = options?.registerModules || {};
|
|
389
|
-
this._registerModules = { ...this.registerModules };
|
|
390
|
-
|
|
391
|
-
// Add require function to initial context for fallback path
|
|
392
|
-
this._registerModules.require = ((moduleId: string) => this.createRequire(moduleId)).bind(this);
|
|
393
|
-
|
|
394
|
-
this.ctx = vm.createContext(this._registerModules);
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
register(context: { [key: string]: any }) {
|
|
400
|
-
this.registerModules = { ...this.registerModules, ...context };
|
|
401
|
-
this._registerModules = { ...this._registerModules, ...context };
|
|
402
|
-
// Always ensure our custom require function is present (with @db alias support)
|
|
403
|
-
// Store the original require if it exists in context
|
|
404
|
-
const originalRequire = context.require;
|
|
405
|
-
this._registerModules.require = ((moduleId: string) => {
|
|
406
|
-
// Try custom require first (handles @db aliases and database files)
|
|
407
|
-
try {
|
|
408
|
-
return this.createRequire(moduleId);
|
|
409
|
-
} catch (e) {
|
|
410
|
-
// Fall back to original require for node_modules
|
|
411
|
-
if (originalRequire && !moduleId.startsWith('@db/') && !moduleId.startsWith('./') && !moduleId.startsWith('../')) {
|
|
412
|
-
return originalRequire(moduleId);
|
|
413
|
-
}
|
|
414
|
-
throw e;
|
|
415
|
-
}
|
|
416
|
-
}).bind(this);
|
|
417
|
-
// Update context with all modules including require
|
|
418
|
-
this.ctx = vm.createContext(this._registerModules);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
private createRequire(moduleId: string): any {
|
|
422
|
-
// Validate moduleId
|
|
423
|
-
if (!moduleId) {
|
|
424
|
-
console.error('[createRequire] moduleId is undefined');
|
|
425
|
-
return {};
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
console.log('[createRequire] Loading module:', moduleId, 'from DATABASE_DIR:', this.DATABASE_DIR);
|
|
429
|
-
|
|
430
|
-
// Handle @db/ path alias
|
|
431
|
-
if (moduleId.startsWith('@db/')) {
|
|
432
|
-
const relativePath = moduleId.substring(4); // Remove '@db/'
|
|
433
|
-
moduleId = './' + relativePath;
|
|
434
|
-
console.log('[createRequire] Resolved @db/ alias to:', moduleId);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// Handle relative paths (e.g., './users')
|
|
438
|
-
if (moduleId.startsWith('./') || moduleId.startsWith('../')) {
|
|
439
|
-
const dbDir = this.DATABASE_DIR || process.cwd();
|
|
440
|
-
const fullPath = path.join(dbDir, moduleId);
|
|
441
|
-
|
|
442
|
-
console.log('[createRequire] Full path:', fullPath);
|
|
443
|
-
|
|
444
|
-
// Try to find the file with an extension
|
|
445
|
-
let actualPath: string | undefined = fullPath;
|
|
446
|
-
if (fs.existsSync(fullPath)) {
|
|
447
|
-
actualPath = fullPath;
|
|
448
|
-
} else {
|
|
449
|
-
const extensions = ['.ts', '.tsx', '.mts', '.cts', '.js', '.mjs', '.cjs'];
|
|
450
|
-
for (const ext of extensions) {
|
|
451
|
-
if (fs.existsSync(fullPath + ext)) {
|
|
452
|
-
actualPath = fullPath + ext;
|
|
453
|
-
break;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
console.log('[createRequire] Actual path:', actualPath);
|
|
459
|
-
|
|
460
|
-
if (!actualPath || !fs.existsSync(actualPath)) {
|
|
461
|
-
console.log('[createRequire] File not found, throwing error');
|
|
462
|
-
throw new Error(`Module '${moduleId}' not found at ${fullPath}`);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// For ES module and TypeScript files, transpile the content into CommonJS for vm execution.
|
|
466
|
-
if (actualPath.endsWith('.ts') || actualPath.endsWith('.tsx') || actualPath.endsWith('.mts') || actualPath.endsWith('.cts') || actualPath.endsWith('.js') || actualPath.endsWith('.mjs')) {
|
|
467
|
-
const content = fs.readFileSync(actualPath, 'utf8');
|
|
468
|
-
const loader: VMModuleLoader = actualPath.endsWith('.ts') || actualPath.endsWith('.mts') || actualPath.endsWith('.cts')
|
|
469
|
-
? 'ts'
|
|
470
|
-
: actualPath.endsWith('.tsx')
|
|
471
|
-
? 'tsx'
|
|
472
|
-
: 'js';
|
|
473
|
-
const js = this.transpiler(content, {
|
|
474
|
-
loader,
|
|
475
|
-
format: 'cjs',
|
|
476
|
-
filename: actualPath,
|
|
477
|
-
}).code;
|
|
478
|
-
|
|
479
|
-
// Create a wrapper object to capture the final exports
|
|
480
|
-
const moduleWrapper = { exports: {} };
|
|
481
|
-
const moduleContext = vm.createContext({
|
|
482
|
-
...this._registerModules,
|
|
483
|
-
module: moduleWrapper,
|
|
484
|
-
exports: moduleWrapper.exports,
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
vm.runInContext(js, moduleContext, { filename: actualPath });
|
|
488
|
-
|
|
489
|
-
console.log('[createRequire] Returning exports:', moduleWrapper.exports);
|
|
490
|
-
return moduleWrapper.exports;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// For CommonJS files, use standard require.
|
|
494
|
-
const result = require(actualPath);
|
|
495
|
-
console.log('[createRequire] Returning (JS):', result);
|
|
496
|
-
return result;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// For node_modules, use standard require
|
|
500
|
-
return require(moduleId);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
resolvePath(fileList: any[], query: string) {
|
|
504
|
-
const aliases = { '@db': this.DATABASE_DIR };
|
|
505
|
-
|
|
506
|
-
let resolvedPath = query;
|
|
507
|
-
for (const [alias, target] of Object.entries(aliases)) {
|
|
508
|
-
if (resolvedPath.startsWith(alias + '/')) {
|
|
509
|
-
resolvedPath = resolvedPath.replace(alias, target);
|
|
510
|
-
break;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// Normalize path separators for cross-platform compatibility
|
|
515
|
-
resolvedPath = path.normalize(resolvedPath);
|
|
516
|
-
|
|
517
|
-
return fileList.find(file => {
|
|
518
|
-
const normalizedFile = path.normalize(file);
|
|
519
|
-
const fileWithoutExt = normalizedFile.replace(/\.[^/.]+$/, "");
|
|
520
|
-
return normalizedFile === resolvedPath ||
|
|
521
|
-
fileWithoutExt === resolvedPath ||
|
|
522
|
-
normalizedFile === resolvedPath + '.ts' ||
|
|
523
|
-
normalizedFile === resolvedPath + '.js';
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
async moduleLinker(specifier: any, referencingModule: any) {
|
|
528
|
-
console.log('[moduleLinker] Loading specifier:', specifier, 'from DATABASE_DIR:', this.DATABASE_DIR);
|
|
529
|
-
|
|
530
|
-
// Try database files first
|
|
531
|
-
const dbFiles = fs.readdirSync(this.DATABASE_DIR)
|
|
532
|
-
.filter(f => f.endsWith(".ts"))
|
|
533
|
-
.map(f => path.join(this.DATABASE_DIR, f));
|
|
534
|
-
|
|
535
|
-
console.log('[moduleLinker] Database files:', dbFiles);
|
|
536
|
-
|
|
537
|
-
const dbResult = this.resolvePath(dbFiles, specifier);
|
|
538
|
-
console.log('[moduleLinker] Resolved path:', dbResult);
|
|
539
|
-
|
|
540
|
-
if (dbResult) {
|
|
541
|
-
try {
|
|
542
|
-
const actualModule = await import(dbResult);
|
|
543
|
-
const exportNames = Object.keys(actualModule);
|
|
544
|
-
return new vm.SyntheticModule(
|
|
545
|
-
exportNames,
|
|
546
|
-
function () {
|
|
547
|
-
exportNames.forEach(key => {
|
|
548
|
-
this.setExport(key, actualModule[key]);
|
|
549
|
-
});
|
|
550
|
-
},
|
|
551
|
-
{ identifier: specifier, context: referencingModule.context }
|
|
552
|
-
);
|
|
553
|
-
} catch (err) {
|
|
554
|
-
console.error(`Failed to load database module ${specifier}:`, err);
|
|
555
|
-
throw err;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// Try workspace packages
|
|
560
|
-
const allowedPackages = Object.keys(this.pkgScriptDB.dependencies || {});
|
|
561
|
-
if (allowedPackages.includes(specifier)) {
|
|
562
|
-
try {
|
|
563
|
-
// Import from scriptdb workspace node_modules
|
|
564
|
-
const modulePath = path.join(this.SCRIPTDB_DIR, 'node_modules', specifier);
|
|
565
|
-
const actualModule = await import(modulePath);
|
|
566
|
-
const exportNames = Object.keys(actualModule);
|
|
567
|
-
return new vm.SyntheticModule(
|
|
568
|
-
exportNames,
|
|
569
|
-
function () {
|
|
570
|
-
exportNames.forEach(key => {
|
|
571
|
-
this.setExport(key, actualModule[key]);
|
|
572
|
-
});
|
|
573
|
-
},
|
|
574
|
-
{ identifier: specifier, context: referencingModule.context }
|
|
575
|
-
);
|
|
576
|
-
} catch (err) {
|
|
577
|
-
console.error(`Failed to load workspace module ${specifier}:`, err);
|
|
578
|
-
throw err;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
throw new Error(`Module ${specifier} is not allowed or not found.`);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
async run(code: string) {
|
|
586
|
-
const logs: any[] = [];
|
|
587
|
-
|
|
588
|
-
const customConsole = ['log', 'error', 'warn', 'info', 'debug', 'trace'].reduce((acc: any, type: any) => {
|
|
589
|
-
acc[type] = (...args: any[]) => logs.push({ type, args });
|
|
590
|
-
return acc;
|
|
591
|
-
}, {});
|
|
592
|
-
|
|
593
|
-
this.register({
|
|
594
|
-
console: customConsole
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
const systemModules = await SystemModuleResolver(this.options);
|
|
598
|
-
this.register(systemModules);
|
|
599
|
-
|
|
600
|
-
const js = this.transpiler(code, {
|
|
601
|
-
loader: this.language,
|
|
602
|
-
format: 'cjs',
|
|
603
|
-
filename: path.join(this.SCRIPTDB_DIR, `virtual-entry.${this.language}`),
|
|
604
|
-
}).code;
|
|
605
|
-
console.log('[run] Transpiled code:', js);
|
|
606
|
-
|
|
607
|
-
// Use SourceTextModule when the runtime provides it so module loading errors surface directly.
|
|
608
|
-
const SourceTextModule = (vm as any).SourceTextModule;
|
|
609
|
-
console.log('[run] SourceTextModule available:', typeof SourceTextModule === 'function');
|
|
610
|
-
if (typeof SourceTextModule === 'function') {
|
|
611
|
-
const mod = new SourceTextModule(js, { context: this.ctx, identifier: path.join(this.SCRIPTDB_DIR, 'virtual-entry.js') });
|
|
612
|
-
await mod.link(this.moduleLinker.bind(this));
|
|
613
|
-
await mod.evaluate();
|
|
614
|
-
|
|
615
|
-
return {
|
|
616
|
-
namespace: mod.namespace,
|
|
617
|
-
logs: logs
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// Fallback: Pre-process imports and use vm.runInContext
|
|
622
|
-
let processedCode = js;
|
|
623
|
-
|
|
624
|
-
console.log('[run] Original transpiled code:', processedCode);
|
|
625
|
-
|
|
626
|
-
// esbuild converts: import { users } from './users'
|
|
627
|
-
// to: var import_users = require("./users");
|
|
628
|
-
// and uses: import_users.users
|
|
629
|
-
// But our module exports: exports.users = []
|
|
630
|
-
// So we need to convert import_users.users -> import_users
|
|
631
|
-
|
|
632
|
-
// First, convert static imports to require calls
|
|
633
|
-
processedCode = processedCode.replace(
|
|
634
|
-
/var\s+(\w+)\s+=\s+require\((['"])([^'"]+)\2\);/g,
|
|
635
|
-
(_match: string, varName: string, quote: string, modulePath: string) => {
|
|
636
|
-
return `const ${varName} = require(${quote}${modulePath}${quote});`;
|
|
637
|
-
}
|
|
638
|
-
);
|
|
639
|
-
|
|
640
|
-
// Convert any remaining static imports to require calls
|
|
641
|
-
processedCode = processedCode.replace(
|
|
642
|
-
/import\s+\{([^}]+)\}\s+from\s+(['"])([^'"]+)\2/g,
|
|
643
|
-
(_match: string, imports: string, quote: string, modulePath: string) => {
|
|
644
|
-
return `const { ${imports} } = require(${quote}${modulePath}${quote});`;
|
|
645
|
-
}
|
|
646
|
-
);
|
|
647
|
-
|
|
648
|
-
processedCode = processedCode.replace(
|
|
649
|
-
/import\s+(\w+)\s+from\s+(['"])([^'"]+)\2/g,
|
|
650
|
-
(_match: string, name: string, quote: string, modulePath: string) => {
|
|
651
|
-
return `const ${name} = require(${quote}${modulePath}${quote});`;
|
|
652
|
-
}
|
|
653
|
-
);
|
|
654
|
-
|
|
655
|
-
// Convert dynamic import() to require()
|
|
656
|
-
processedCode = processedCode.replace(/import\(([^)]+)\)/g, 'require($1)');
|
|
657
|
-
|
|
658
|
-
console.log('[run] Processed code:', processedCode);
|
|
659
|
-
|
|
660
|
-
console.log('[run] Context has require:', typeof this._registerModules.require);
|
|
661
|
-
console.log('[run] DATABASE_DIR:', this.DATABASE_DIR);
|
|
662
|
-
|
|
663
|
-
try {
|
|
664
|
-
const moduleWrapper = { exports: {} as any };
|
|
665
|
-
const initialExports = moduleWrapper.exports;
|
|
666
|
-
const originalModule = this._registerModules.module;
|
|
667
|
-
const originalExports = this._registerModules.exports;
|
|
668
|
-
|
|
669
|
-
this._registerModules.module = moduleWrapper;
|
|
670
|
-
this._registerModules.exports = moduleWrapper.exports;
|
|
671
|
-
this.ctx = vm.createContext(this._registerModules);
|
|
672
|
-
|
|
673
|
-
let result: any;
|
|
674
|
-
try {
|
|
675
|
-
result = vm.runInContext(processedCode, this.ctx, {
|
|
676
|
-
filename: path.join(this.SCRIPTDB_DIR, 'virtual-entry.js')
|
|
677
|
-
});
|
|
678
|
-
} finally {
|
|
679
|
-
if (originalModule) {
|
|
680
|
-
this._registerModules.module = originalModule;
|
|
681
|
-
} else {
|
|
682
|
-
delete this._registerModules.module;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
if (originalExports) {
|
|
686
|
-
this._registerModules.exports = originalExports;
|
|
687
|
-
} else {
|
|
688
|
-
delete this._registerModules.exports;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
this.ctx = vm.createContext(this._registerModules);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
const hasExplicitExports = moduleWrapper.exports !== initialExports
|
|
695
|
-
|| (typeof initialExports === 'object' && initialExports !== null && Object.keys(initialExports).length > 0);
|
|
696
|
-
|
|
697
|
-
return {
|
|
698
|
-
namespace: hasExplicitExports ? moduleWrapper.exports : result,
|
|
699
|
-
logs: logs
|
|
700
|
-
};
|
|
701
|
-
} catch (e) {
|
|
702
|
-
console.log('[run] Error executing code:', e);
|
|
703
|
-
throw e;
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
function create(dbName: string, code: string | Function, options?: VMOptions): void {
|
|
709
|
-
const DIR = options?.dir || path.join(process.cwd(), 'databases');
|
|
710
|
-
const dbPath = path.join(DIR, `${dbName}.ts`);
|
|
711
|
-
// Prepare the export line
|
|
712
|
-
fs.appendFileSync(dbPath, code.toString(), 'utf8');
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
function read(dbName: string, options?: VMOptions): string {
|
|
716
|
-
const DIR = options?.dir || path.join(process.cwd(), 'databases');
|
|
717
|
-
const dbPath = path.join(DIR, `${dbName}.ts`);
|
|
718
|
-
|
|
719
|
-
if (!fs.existsSync(dbPath)) {
|
|
720
|
-
throw new Error(`Database '${dbName}' not found`);
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
return fs.readFileSync(dbPath, 'utf8');
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
function remove(dbName: string, fnName: string, options?: VMOptions) {
|
|
727
|
-
const DIR = options?.dir || path.join(process.cwd(), 'databases');
|
|
728
|
-
const dbPath = path.join(DIR, `${dbName}.ts`);
|
|
729
|
-
if (!fs.existsSync(dbPath)) return false;
|
|
730
|
-
|
|
731
|
-
// if no functionName provided -> remove the whole file (after backup)
|
|
732
|
-
if (!fnName) {
|
|
733
|
-
const bak = `${dbPath}.bak`;
|
|
734
|
-
try {
|
|
735
|
-
fs.copyFileSync(dbPath, bak);
|
|
736
|
-
} catch (e) {
|
|
737
|
-
// ignore backup errors
|
|
738
|
-
}
|
|
739
|
-
try {
|
|
740
|
-
fs.unlinkSync(dbPath);
|
|
741
|
-
return "Removed successfully";
|
|
742
|
-
} catch (e) {
|
|
743
|
-
return "Removed failed";
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// create a backup before editing the file in-place
|
|
748
|
-
const bak = `${dbPath}.bak`;
|
|
749
|
-
try {
|
|
750
|
-
fs.copyFileSync(dbPath, bak);
|
|
751
|
-
} catch (e) {
|
|
752
|
-
// ignore backup errors but continue carefully
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
let src = fs.readFileSync(dbPath, "utf8");
|
|
756
|
-
const escaped = fnName.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
757
|
-
|
|
758
|
-
// regex to find a declaration of the named symbol (function, class, or var/const/let assignment)
|
|
759
|
-
const startRe = new RegExp(
|
|
760
|
-
`function\\s+${escaped}\\s*\\(|\\bclass\\s+${escaped}\\b|\\b(?:const|let|var)\\s+${escaped}\\s*=\\s*(?:function\\b|class\\b|\\(|\\{|\\[)`,
|
|
761
|
-
"m"
|
|
762
|
-
);
|
|
763
|
-
|
|
764
|
-
const startMatch = src.match(startRe);
|
|
765
|
-
|
|
766
|
-
if (startMatch) {
|
|
767
|
-
const startIdx = startMatch.index;
|
|
768
|
-
|
|
769
|
-
// find the first meaningful character after startIdx: {, [, or ; or newline
|
|
770
|
-
const len = src.length;
|
|
771
|
-
const idxCurly = src.indexOf("{", startIdx);
|
|
772
|
-
const idxBracket = src.indexOf("[", startIdx);
|
|
773
|
-
let braceOpen = -1;
|
|
774
|
-
if (idxCurly === -1) braceOpen = idxBracket;
|
|
775
|
-
else if (idxBracket === -1) braceOpen = idxCurly;
|
|
776
|
-
else braceOpen = Math.min(idxCurly, idxBracket);
|
|
777
|
-
|
|
778
|
-
if (braceOpen !== -1) {
|
|
779
|
-
const openingChar = src[braceOpen];
|
|
780
|
-
const closingChar = openingChar === "[" ? "]" : "}";
|
|
781
|
-
let i = braceOpen + 1;
|
|
782
|
-
let depth = 1;
|
|
783
|
-
while (i < len && depth > 0) {
|
|
784
|
-
const ch = src[i];
|
|
785
|
-
if (ch === openingChar) depth++;
|
|
786
|
-
else if (ch === closingChar) depth--;
|
|
787
|
-
i++;
|
|
788
|
-
}
|
|
789
|
-
let braceClose = i;
|
|
790
|
-
let endIdx = braceClose;
|
|
791
|
-
if (src.slice(braceClose, braceClose + 1) === ";")
|
|
792
|
-
endIdx = braceClose + 1;
|
|
793
|
-
|
|
794
|
-
const before = src.slice(0, startIdx);
|
|
795
|
-
const after = src.slice(endIdx);
|
|
796
|
-
src = before + after;
|
|
797
|
-
} else {
|
|
798
|
-
// fallback: remove until next semicolon or a blank line
|
|
799
|
-
const semi = src.indexOf(";", startIdx);
|
|
800
|
-
let endIdx = semi !== -1 ? semi + 1 : src.indexOf("\n\n", startIdx);
|
|
801
|
-
if (endIdx === -1) endIdx = len;
|
|
802
|
-
src = src.slice(0, startIdx) + src.slice(endIdx);
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
// remove any export const <name>: any = <name>; lines
|
|
807
|
-
const exportRe = new RegExp(
|
|
808
|
-
`export\\s+const\\s+${escaped}\\s*:\\s*any\\s*=\\s*${escaped}\\s*;?`,
|
|
809
|
-
"g"
|
|
810
|
-
);
|
|
811
|
-
src = src.replace(exportRe, "");
|
|
812
|
-
|
|
813
|
-
// tidy up multiple blank lines
|
|
814
|
-
src = src.replace(/\n{3,}/g, "\n\n");
|
|
815
|
-
|
|
816
|
-
fs.writeFileSync(dbPath, src, "utf8");
|
|
817
|
-
|
|
818
|
-
return `Removed ${fnName} from database ${dbName}.`;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
function rename(oldName: string, newName: string, options?: VMOptions): string {
|
|
822
|
-
const DIR = options?.dir || path.join(process.cwd(), 'databases');
|
|
823
|
-
const oldPath = path.join(DIR, `${oldName}.ts`);
|
|
824
|
-
const newPath = path.join(DIR, `${newName}.ts`);
|
|
825
|
-
|
|
826
|
-
// Check if the source file exists
|
|
827
|
-
if (!fs.existsSync(oldPath)) {
|
|
828
|
-
return `Error: File '${oldName}.ts' does not exist in the database`;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
// Check if the destination file already exists
|
|
832
|
-
if (fs.existsSync(newPath)) {
|
|
833
|
-
return `Error: File '${newName}.ts' already exists in the database`;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
try {
|
|
837
|
-
// Rename the file
|
|
838
|
-
fs.renameSync(oldPath, newPath);
|
|
839
|
-
return `Successfully renamed '${oldName}.ts' to '${newName}.ts'`;
|
|
840
|
-
} catch (error) {
|
|
841
|
-
return `Error renaming file: ${error instanceof Error ? error.message : String(error)}`;
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
type DeclarationKind = "valueDecl" | "functionDecl" | "classDecl";
|
|
846
|
-
type UpdateValue = unknown;
|
|
847
|
-
|
|
848
|
-
interface DeclarationMatch {
|
|
849
|
-
kind: DeclarationKind;
|
|
850
|
-
start: number;
|
|
851
|
-
end: number;
|
|
852
|
-
exported: boolean;
|
|
853
|
-
prefixEnd?: number;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
function escapeRegExp(value: string): string {
|
|
857
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
function findMatchingBlockEnd(source: string, openIndex: number): number {
|
|
861
|
-
let depth = 0;
|
|
862
|
-
let stringChar: string | null = null;
|
|
863
|
-
|
|
864
|
-
for (let index = openIndex; index < source.length; index += 1) {
|
|
865
|
-
const char = source[index];
|
|
866
|
-
const nextChar = source[index + 1];
|
|
867
|
-
|
|
868
|
-
if (stringChar) {
|
|
869
|
-
if (char === "\\") {
|
|
870
|
-
index += 1;
|
|
871
|
-
continue;
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
if (char === stringChar) {
|
|
875
|
-
stringChar = null;
|
|
876
|
-
}
|
|
877
|
-
continue;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
if (char === "/" && nextChar === "/") {
|
|
881
|
-
index += 2;
|
|
882
|
-
while (index < source.length && source[index] !== "\n") {
|
|
883
|
-
index += 1;
|
|
884
|
-
}
|
|
885
|
-
continue;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
if (char === "/" && nextChar === "*") {
|
|
889
|
-
index += 2;
|
|
890
|
-
while (index < source.length && !(source[index] === "*" && source[index + 1] === "/")) {
|
|
891
|
-
index += 1;
|
|
892
|
-
}
|
|
893
|
-
index += 1;
|
|
894
|
-
continue;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
if (char === '"' || char === "'" || char === "`") {
|
|
898
|
-
stringChar = char;
|
|
899
|
-
continue;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
if (char === "{") {
|
|
903
|
-
depth += 1;
|
|
904
|
-
} else if (char === "}") {
|
|
905
|
-
depth -= 1;
|
|
906
|
-
if (depth === 0) {
|
|
907
|
-
return index;
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
return source.length - 1;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
function findInitializerEnd(source: string, startIndex: number): number {
|
|
916
|
-
let braceDepth = 0;
|
|
917
|
-
let bracketDepth = 0;
|
|
918
|
-
let parenDepth = 0;
|
|
919
|
-
let stringChar: string | null = null;
|
|
920
|
-
|
|
921
|
-
for (let index = startIndex; index < source.length; index += 1) {
|
|
922
|
-
const char = source[index];
|
|
923
|
-
const nextChar = source[index + 1];
|
|
924
|
-
|
|
925
|
-
if (stringChar) {
|
|
926
|
-
if (char === "\\") {
|
|
927
|
-
index += 1;
|
|
928
|
-
continue;
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
if (char === stringChar) {
|
|
932
|
-
stringChar = null;
|
|
933
|
-
}
|
|
934
|
-
continue;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
if (char === "/" && nextChar === "/") {
|
|
938
|
-
index += 2;
|
|
939
|
-
while (index < source.length && source[index] !== "\n") {
|
|
940
|
-
index += 1;
|
|
941
|
-
}
|
|
942
|
-
continue;
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
if (char === "/" && nextChar === "*") {
|
|
946
|
-
index += 2;
|
|
947
|
-
while (index < source.length && !(source[index] === "*" && source[index + 1] === "/")) {
|
|
948
|
-
index += 1;
|
|
949
|
-
}
|
|
950
|
-
index += 1;
|
|
951
|
-
continue;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
if (char === '"' || char === "'" || char === "`") {
|
|
955
|
-
stringChar = char;
|
|
956
|
-
continue;
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
if (char === "{") {
|
|
960
|
-
braceDepth += 1;
|
|
961
|
-
} else if (char === "}") {
|
|
962
|
-
braceDepth = Math.max(0, braceDepth - 1);
|
|
963
|
-
} else if (char === "[") {
|
|
964
|
-
bracketDepth += 1;
|
|
965
|
-
} else if (char === "]") {
|
|
966
|
-
bracketDepth = Math.max(0, bracketDepth - 1);
|
|
967
|
-
} else if (char === "(") {
|
|
968
|
-
parenDepth += 1;
|
|
969
|
-
} else if (char === ")") {
|
|
970
|
-
parenDepth = Math.max(0, parenDepth - 1);
|
|
971
|
-
} else if (char === ";" && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
|
|
972
|
-
return index;
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
return source.length;
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
function looksLikeDeclarationSnippet(source: string): boolean {
|
|
980
|
-
const trimmed = source.trim();
|
|
981
|
-
return /^(?:export\s+)?(?:async\s+function\b|function\b|class\b|(?:const|let|var)\b)/.test(trimmed);
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
function replaceExistingBindingValue(source: string, bindingName: string, serializedValue: string): string | null {
|
|
985
|
-
const escapedName = escapeRegExp(bindingName);
|
|
986
|
-
const declarationRegex = new RegExp(`(?:export\\s+)?(?:const|let|var)\\s+${escapedName}(?:\\s*:\\s*[^=;]+)?\\s*=`, "m");
|
|
987
|
-
const declarationMatch = declarationRegex.exec(source);
|
|
988
|
-
|
|
989
|
-
if (!declarationMatch || declarationMatch.index === undefined) {
|
|
990
|
-
return null;
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
const equalsIndex = source.indexOf("=", declarationMatch.index);
|
|
994
|
-
if (equalsIndex === -1) {
|
|
995
|
-
return null;
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
const initializerEnd = findInitializerEnd(source, equalsIndex + 1);
|
|
999
|
-
const suffix = initializerEnd < source.length
|
|
1000
|
-
? source.slice(initializerEnd)
|
|
1001
|
-
: ";";
|
|
1002
|
-
|
|
1003
|
-
return `${source.slice(0, equalsIndex + 1)} ${serializedValue}${suffix}`;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
function buildDatabaseModuleSource(dbName: string, code: unknown, dbPath: string): string {
|
|
1007
|
-
if (typeof code === "string") {
|
|
1008
|
-
return code;
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
if (typeof code === "function") {
|
|
1012
|
-
return code.toString();
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
const serializedValue = valueToCode(code, 0);
|
|
1016
|
-
|
|
1017
|
-
if (fs.existsSync(dbPath) && isIdentifier(dbName)) {
|
|
1018
|
-
const existingSource = fs.readFileSync(dbPath, "utf8");
|
|
1019
|
-
const updatedSource = replaceExistingBindingValue(existingSource, dbName, serializedValue);
|
|
1020
|
-
|
|
1021
|
-
if (updatedSource) {
|
|
1022
|
-
return updatedSource;
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
if (isIdentifier(dbName)) {
|
|
1027
|
-
return `const ${dbName} = ${serializedValue};\n\nexport { ${dbName} };\nexport default ${dbName};\n`;
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
return `const value = ${serializedValue};\n\nexport default value;\n`;
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
function toInitializerSource(code: UpdateValue): string {
|
|
1034
|
-
if (typeof code === "function") {
|
|
1035
|
-
return code.toString().trim();
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
if (typeof code === "string") {
|
|
1039
|
-
const trimmed = code.trim();
|
|
1040
|
-
if (
|
|
1041
|
-
looksLikeDeclarationSnippet(trimmed) ||
|
|
1042
|
-
/=>/.test(trimmed) ||
|
|
1043
|
-
/^(?:\{|\[|\(|"|'|`|\d|-\d|true\b|false\b|null\b|undefined\b|new\b|await\b)/.test(trimmed)
|
|
1044
|
-
) {
|
|
1045
|
-
return trimmed;
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
return valueToCode(code, 0);
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
return valueToCode(code, 0);
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
function shouldUseDeclarationSource(code: UpdateValue): code is string | Function {
|
|
1055
|
-
return typeof code === "function" || (typeof code === "string" && looksLikeDeclarationSnippet(code));
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
function normalizeFunctionDeclaration(name: string, code: string): string {
|
|
1059
|
-
const trimmed = code.trim().replace(/^export\s+/, "");
|
|
1060
|
-
|
|
1061
|
-
if (/^async\s+function\s+[A-Za-z_$][\w$]*/.test(trimmed)) {
|
|
1062
|
-
return trimmed.replace(/^async\s+function\s+[A-Za-z_$][\w$]*/, `async function ${name}`);
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
if (/^async\s+function\s*\(/.test(trimmed)) {
|
|
1066
|
-
return trimmed.replace(/^async\s+function\s*\(/, `async function ${name}(`);
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
if (/^function\s+[A-Za-z_$][\w$]*/.test(trimmed)) {
|
|
1070
|
-
return trimmed.replace(/^function\s+[A-Za-z_$][\w$]*/, `function ${name}`);
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
if (/^function\s*\(/.test(trimmed)) {
|
|
1074
|
-
return trimmed.replace(/^function\s*\(/, `function ${name}(`);
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
return `function ${name}() {\n${trimmed}\n}`;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
function normalizeClassDeclaration(name: string, code: string): string {
|
|
1081
|
-
const trimmed = code.trim().replace(/^export\s+/, "");
|
|
1082
|
-
|
|
1083
|
-
if (/^class\s+[A-Za-z_$][\w$]*/.test(trimmed)) {
|
|
1084
|
-
return trimmed.replace(/^class\s+[A-Za-z_$][\w$]*/, `class ${name}`);
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
if (/^class(?:\s+extends\b|\s*\{)/.test(trimmed)) {
|
|
1088
|
-
return trimmed.replace(/^class/, `class ${name}`);
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
return `class ${name} ${trimmed}`;
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
function findDeclaration(source: string, name: string): DeclarationMatch | null {
|
|
1095
|
-
const escaped = escapeRegExp(name);
|
|
1096
|
-
const matches: DeclarationMatch[] = [];
|
|
1097
|
-
|
|
1098
|
-
const valueRegex = new RegExp(`(?:export\\s+)?(?:const|let|var)\\s+${escaped}(?:\\s*:\\s*[^=;]+)?\\s*=`, "m");
|
|
1099
|
-
const valueMatch = valueRegex.exec(source);
|
|
1100
|
-
if (valueMatch && valueMatch.index !== undefined) {
|
|
1101
|
-
const equalsIndex = source.indexOf("=", valueMatch.index);
|
|
1102
|
-
if (equalsIndex !== -1) {
|
|
1103
|
-
const initializerEnd = findInitializerEnd(source, equalsIndex + 1);
|
|
1104
|
-
const end = initializerEnd < source.length && source[initializerEnd] === ";"
|
|
1105
|
-
? initializerEnd + 1
|
|
1106
|
-
: initializerEnd;
|
|
1107
|
-
matches.push({
|
|
1108
|
-
kind: "valueDecl",
|
|
1109
|
-
start: valueMatch.index,
|
|
1110
|
-
end,
|
|
1111
|
-
exported: /^\s*export\b/.test(valueMatch[0]),
|
|
1112
|
-
prefixEnd: equalsIndex + 1,
|
|
1113
|
-
});
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
const functionRegex = new RegExp(`(?:export\\s+)?(?:async\\s+)?function\\s+${escaped}\\s*\\(`, "m");
|
|
1118
|
-
const functionMatch = functionRegex.exec(source);
|
|
1119
|
-
if (functionMatch && functionMatch.index !== undefined) {
|
|
1120
|
-
const braceOpen = source.indexOf("{", functionMatch.index);
|
|
1121
|
-
if (braceOpen !== -1) {
|
|
1122
|
-
const braceClose = findMatchingBlockEnd(source, braceOpen);
|
|
1123
|
-
const end = braceClose + 1 < source.length && source[braceClose + 1] === ";"
|
|
1124
|
-
? braceClose + 2
|
|
1125
|
-
: braceClose + 1;
|
|
1126
|
-
matches.push({
|
|
1127
|
-
kind: "functionDecl",
|
|
1128
|
-
start: functionMatch.index,
|
|
1129
|
-
end,
|
|
1130
|
-
exported: /^\s*export\b/.test(functionMatch[0]),
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
const classRegex = new RegExp(`(?:export\\s+)?class\\s+${escaped}(?=\\s|\\{)`, "m");
|
|
1136
|
-
const classMatch = classRegex.exec(source);
|
|
1137
|
-
if (classMatch && classMatch.index !== undefined) {
|
|
1138
|
-
const braceOpen = source.indexOf("{", classMatch.index);
|
|
1139
|
-
if (braceOpen !== -1) {
|
|
1140
|
-
const braceClose = findMatchingBlockEnd(source, braceOpen);
|
|
1141
|
-
const end = braceClose + 1 < source.length && source[braceClose + 1] === ";"
|
|
1142
|
-
? braceClose + 2
|
|
1143
|
-
: braceClose + 1;
|
|
1144
|
-
matches.push({
|
|
1145
|
-
kind: "classDecl",
|
|
1146
|
-
start: classMatch.index,
|
|
1147
|
-
end,
|
|
1148
|
-
exported: /^\s*export\b/.test(classMatch[0]),
|
|
1149
|
-
});
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
if (matches.length === 0) {
|
|
1154
|
-
return null;
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
matches.sort((left, right) => left.start - right.start);
|
|
1158
|
-
return matches[0];
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
function createStructuredReplacement(kind: Extract<DeclarationKind, "functionDecl" | "classDecl">, name: string, code: UpdateValue): string {
|
|
1162
|
-
if (!shouldUseDeclarationSource(code)) {
|
|
1163
|
-
return `const ${name} = ${toInitializerSource(code)};`;
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
const source = code.toString();
|
|
1167
|
-
return kind === "functionDecl"
|
|
1168
|
-
? normalizeFunctionDeclaration(name, source)
|
|
1169
|
-
: normalizeClassDeclaration(name, source);
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
function createDeclarationSnippet(name: string, code: UpdateValue): string {
|
|
1173
|
-
if (typeof code === "function") {
|
|
1174
|
-
const fnSource = code.toString().trim();
|
|
1175
|
-
if (/^(?:async\s+)?function\b/.test(fnSource)) {
|
|
1176
|
-
return `export ${normalizeFunctionDeclaration(name, fnSource)}`;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
if (/^class\b/.test(fnSource)) {
|
|
1180
|
-
return `export ${normalizeClassDeclaration(name, fnSource)}`;
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
return `export const ${name} = ${fnSource};`;
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
if (typeof code === "string") {
|
|
1187
|
-
const trimmed = code.trim();
|
|
1188
|
-
if (looksLikeDeclarationSnippet(trimmed)) {
|
|
1189
|
-
return trimmed;
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
return `export const ${name} = ${toInitializerSource(code)};`;
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
function save(dbName: string, code: unknown, options?: VMOptions): void {
|
|
1197
|
-
const DIR = options?.dir || path.join(process.cwd(), 'databases');
|
|
1198
|
-
const dbPath = path.join(DIR, `${dbName}.ts`);
|
|
1199
|
-
|
|
1200
|
-
const fileContent = buildDatabaseModuleSource(dbName, code, dbPath);
|
|
1201
|
-
fs.writeFileSync(dbPath, fileContent, 'utf8');
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
function update(dbName: string, fnName: string, code: UpdateValue, options?: VMOptions): string {
|
|
1205
|
-
const DIR = options?.dir || path.join(process.cwd(), 'databases');
|
|
1206
|
-
const dbPath = path.join(DIR, `${dbName}.ts`);
|
|
1207
|
-
|
|
1208
|
-
if (!fs.existsSync(dbPath)) {
|
|
1209
|
-
try {
|
|
1210
|
-
fs.writeFileSync(dbPath, '', 'utf8');
|
|
1211
|
-
} catch {
|
|
1212
|
-
return `Failed to create dbPath file: ${dbPath}`;
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
let src = fs.readFileSync(dbPath, 'utf8');
|
|
1217
|
-
const originalSrc = src;
|
|
1218
|
-
|
|
1219
|
-
const declaration = findDeclaration(src, fnName);
|
|
1220
|
-
|
|
1221
|
-
if (declaration) {
|
|
1222
|
-
if (declaration.kind === 'valueDecl' && declaration.prefixEnd !== undefined) {
|
|
1223
|
-
const initializer = toInitializerSource(code);
|
|
1224
|
-
src =
|
|
1225
|
-
src.slice(0, declaration.start) +
|
|
1226
|
-
src.slice(declaration.start, declaration.prefixEnd) +
|
|
1227
|
-
` ${initializer};` +
|
|
1228
|
-
src.slice(declaration.end);
|
|
1229
|
-
} else if (declaration.kind === 'functionDecl') {
|
|
1230
|
-
const replacement = createStructuredReplacement('functionDecl', fnName, code);
|
|
1231
|
-
src =
|
|
1232
|
-
src.slice(0, declaration.start) +
|
|
1233
|
-
`${declaration.exported ? 'export ' : ''}${replacement.replace(/^export\s+/, '')}` +
|
|
1234
|
-
src.slice(declaration.end);
|
|
1235
|
-
} else {
|
|
1236
|
-
const replacement = createStructuredReplacement('classDecl', fnName, code);
|
|
1237
|
-
src =
|
|
1238
|
-
src.slice(0, declaration.start) +
|
|
1239
|
-
`${declaration.exported ? 'export ' : ''}${replacement.replace(/^export\s+/, '')}` +
|
|
1240
|
-
src.slice(declaration.end);
|
|
1241
|
-
}
|
|
1242
|
-
} else {
|
|
1243
|
-
const snippet = createDeclarationSnippet(fnName, code);
|
|
1244
|
-
const separator = src.trim().length > 0 ? '\n\n' : '';
|
|
1245
|
-
src = `${src.trimEnd()}${separator}${snippet}\n`;
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
fs.writeFileSync(dbPath, src, 'utf8');
|
|
1249
|
-
|
|
1250
|
-
if (src === originalSrc) {
|
|
1251
|
-
return `Saved ${fnName} to database ${dbName}.`;
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
return `Updated ${dbName} with ${fnName}.`;
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
function valueToCode(val: any, depth: number = 0): string {
|
|
1258
|
-
const indentUnit = " ";
|
|
1259
|
-
const indent = indentUnit.repeat(depth);
|
|
1260
|
-
const indentInner = indentUnit.repeat(depth + 1);
|
|
1261
|
-
|
|
1262
|
-
if (val === null) return "null";
|
|
1263
|
-
const t = typeof val;
|
|
1264
|
-
if (t === "string") return JSON.stringify(val);
|
|
1265
|
-
if (t === "number" || t === "boolean") return String(val);
|
|
1266
|
-
if (t === "function") return val.toString();
|
|
1267
|
-
if (Array.isArray(val)) {
|
|
1268
|
-
if (val.length === 0) return "[]";
|
|
1269
|
-
const items = val.map((v) => valueToCode(v, depth + 1));
|
|
1270
|
-
return (
|
|
1271
|
-
"[\n" +
|
|
1272
|
-
items.map((it) => indentInner + it).join(",\n") +
|
|
1273
|
-
"\n" +
|
|
1274
|
-
indent +
|
|
1275
|
-
"]"
|
|
1276
|
-
);
|
|
1277
|
-
}
|
|
1278
|
-
if (t === "object") {
|
|
1279
|
-
const keys = Object.keys(val);
|
|
1280
|
-
if (keys.length === 0) return "{}";
|
|
1281
|
-
const entries = keys.map((k) => {
|
|
1282
|
-
const keyPart = isIdentifier(k) ? k : JSON.stringify(k);
|
|
1283
|
-
const v = valueToCode(val[k], depth + 1);
|
|
1284
|
-
return indentInner + keyPart + ": " + v;
|
|
1285
|
-
});
|
|
1286
|
-
return "{\n" + entries.join(",\n") + "\n" + indent + "}";
|
|
1287
|
-
}
|
|
1288
|
-
return String(val);
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
function isIdentifier(key: any) {
|
|
1292
|
-
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key);
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
async function SystemModuleResolver(customOptions?: VMOptions) {
|
|
1296
|
-
|
|
1297
|
-
const moduleRegistry = new Map<string, any>();
|
|
1298
|
-
|
|
1299
|
-
// Wrap functions to automatically pass customOptions when called from within VM
|
|
1300
|
-
moduleRegistry.set("update", (dbName: string, fnName: string, code: unknown) =>
|
|
1301
|
-
update(dbName, fnName, code, customOptions));
|
|
1302
|
-
moduleRegistry.set("remove", (dbName: string, fnName: string) =>
|
|
1303
|
-
remove(dbName, fnName, customOptions));
|
|
1304
|
-
moduleRegistry.set("create", (dbName: string, code: string | Function) =>
|
|
1305
|
-
create(dbName, code, customOptions));
|
|
1306
|
-
moduleRegistry.set("save", (dbName: string, code: unknown) =>
|
|
1307
|
-
save(dbName, code, customOptions));
|
|
1308
|
-
moduleRegistry.set("read", (dbName: string) =>
|
|
1309
|
-
read(dbName, customOptions));
|
|
1310
|
-
|
|
1311
|
-
const context: Record<string, any> = {
|
|
1312
|
-
// Add require-like functionality
|
|
1313
|
-
require: (moduleName: string) => {
|
|
1314
|
-
const module = moduleRegistry.get(moduleName);
|
|
1315
|
-
if (!module) {
|
|
1316
|
-
throw new Error(`Module '${moduleName}' not found`);
|
|
1317
|
-
}
|
|
1318
|
-
// Return the default export if available, otherwise the module itself
|
|
1319
|
-
return module.default || module;
|
|
1320
|
-
},
|
|
1321
|
-
|
|
1322
|
-
// Add import functionality (simulated)
|
|
1323
|
-
import: async (moduleName: string) => {
|
|
1324
|
-
const module = moduleRegistry.get(moduleName);
|
|
1325
|
-
if (!module) {
|
|
1326
|
-
throw new Error(`Module '${moduleName}' not found`);
|
|
1327
|
-
}
|
|
1328
|
-
return {
|
|
1329
|
-
default: module.default || module
|
|
1330
|
-
};
|
|
1331
|
-
}
|
|
1332
|
-
};
|
|
1333
|
-
|
|
1334
|
-
for (const [name, moduleExports] of moduleRegistry) {
|
|
1335
|
-
context[name] = moduleExports.default || moduleExports;
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
return context;
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
export class Database {
|
|
1342
|
-
private vm: VM;
|
|
1343
|
-
private options: VMOptions;
|
|
1344
|
-
|
|
1345
|
-
constructor(options?: VMOptions) {
|
|
1346
|
-
this.options = {
|
|
1347
|
-
language: 'ts',
|
|
1348
|
-
registerModules: {},
|
|
1349
|
-
...options
|
|
1350
|
-
};
|
|
1351
|
-
this.vm = new VM(this.options);
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
register(context: { [key: string]: any }) {
|
|
1355
|
-
this.vm.register(context);
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
async execute(code: string) {
|
|
1359
|
-
return await this.vm.run(code);
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
// ===== Database Helper Methods =====
|
|
1363
|
-
|
|
1364
|
-
/**
|
|
1365
|
-
* Create a new database file with the given code
|
|
1366
|
-
*/
|
|
1367
|
-
create(dbName: string, code: string | Function): void {
|
|
1368
|
-
return create(dbName, code, this.options);
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
/**
|
|
1372
|
-
* Read the contents of a database file
|
|
1373
|
-
*/
|
|
1374
|
-
read(dbName: string): string {
|
|
1375
|
-
return read(dbName, this.options);
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
/**
|
|
1379
|
-
* Remove a function or the entire database file
|
|
1380
|
-
*/
|
|
1381
|
-
remove(dbName: string, fnName?: string): string | boolean {
|
|
1382
|
-
return remove(dbName, fnName || "", this.options);
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
/**
|
|
1386
|
-
* Rename a database file
|
|
1387
|
-
*/
|
|
1388
|
-
rename(oldName: string, newName: string): string {
|
|
1389
|
-
return rename(oldName, newName, this.options);
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
/**
|
|
1393
|
-
* Save code to a database file (overwrites existing content)
|
|
1394
|
-
*/
|
|
1395
|
-
save(dbName: string, code: unknown): void {
|
|
1396
|
-
return save(dbName, code, this.options);
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
/**
|
|
1400
|
-
* Update a function in a database file
|
|
1401
|
-
*/
|
|
1402
|
-
update(dbName: string, fnName: string, code: unknown): string {
|
|
1403
|
-
return update(dbName, fnName, code, this.options);
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
// Export Database class and keep helper functions for backward compatibility
|
|
1408
|
-
export { create, read, remove, rename, save, update };
|
|
1409
|
-
|
|
1410
|
-
|