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.
Files changed (128) hide show
  1. package/Cargo.toml +1 -1
  2. package/README.md +1 -1
  3. package/desktop/build.rs +83 -0
  4. package/desktop/icon.rs +106 -0
  5. package/desktop/lib.rs +2 -0
  6. package/desktop/main.rs +235 -0
  7. package/desktop/native_main.rs +128 -0
  8. package/desktop/native_renderer/action_widgets.rs +184 -0
  9. package/desktop/native_renderer/app_models.rs +171 -0
  10. package/desktop/native_renderer/app_runtime.rs +140 -0
  11. package/desktop/native_renderer/container_rendering.rs +610 -0
  12. package/desktop/native_renderer/content_widgets.rs +634 -0
  13. package/desktop/native_renderer/css_models.rs +371 -0
  14. package/desktop/native_renderer/embedded_surfaces.rs +414 -0
  15. package/desktop/native_renderer/form_controls.rs +516 -0
  16. package/desktop/native_renderer/interaction_dispatch.rs +89 -0
  17. package/desktop/native_renderer/runtime_support.rs +135 -0
  18. package/desktop/native_renderer/utilities.rs +495 -0
  19. package/desktop/native_renderer/vector_drawing.rs +491 -0
  20. package/desktop/native_renderer.rs +4122 -0
  21. package/desktop/runtime/external.rs +422 -0
  22. package/desktop/runtime/mod.rs +67 -0
  23. package/desktop/runtime/quickjs.rs +106 -0
  24. package/desktop/window.rs +383 -0
  25. package/dist/build.d.ts +1 -1
  26. package/dist/cli.cjs +16 -2
  27. package/dist/cli.mjs +16 -2
  28. package/dist/config.d.ts +1 -1
  29. package/dist/coverage.d.ts +1 -1
  30. package/dist/desktop-auto-render.cjs +2370 -0
  31. package/dist/desktop-auto-render.d.ts +13 -0
  32. package/dist/desktop-auto-render.js +2341 -0
  33. package/dist/desktop-auto-render.mjs +2344 -0
  34. package/dist/render-context.cjs +118 -0
  35. package/dist/render-context.d.ts +39 -0
  36. package/dist/render-context.js +77 -0
  37. package/dist/render-context.mjs +87 -0
  38. package/dist/{server-CNgDUgSZ.d.ts → server-FCdUqabc.d.ts} +1 -1
  39. package/dist/server.d.ts +1 -1
  40. package/package.json +26 -3
  41. package/dist/build.d.mts +0 -20
  42. package/dist/chokidar.d.mts +0 -134
  43. package/dist/cli.d.mts +0 -81
  44. package/dist/config.d.mts +0 -254
  45. package/dist/coverage.d.mts +0 -85
  46. package/dist/database.d.mts +0 -52
  47. package/dist/desktop.d.mts +0 -68
  48. package/dist/dom.d.mts +0 -87
  49. package/dist/el.d.mts +0 -208
  50. package/dist/fs.d.mts +0 -255
  51. package/dist/hmr.d.mts +0 -38
  52. package/dist/http.d.mts +0 -169
  53. package/dist/https.d.mts +0 -108
  54. package/dist/index.d.mts +0 -13
  55. package/dist/mime-types.d.mts +0 -48
  56. package/dist/native.d.mts +0 -136
  57. package/dist/path.d.mts +0 -163
  58. package/dist/router.d.mts +0 -49
  59. package/dist/runtime.d.mts +0 -97
  60. package/dist/server-D0Dp4R5z.d.mts +0 -449
  61. package/dist/server.d.mts +0 -7
  62. package/dist/state.d.mts +0 -117
  63. package/dist/style.d.mts +0 -232
  64. package/dist/test-reporter.d.mts +0 -77
  65. package/dist/test-runtime.d.mts +0 -122
  66. package/dist/test.d.mts +0 -39
  67. package/dist/types.d.mts +0 -586
  68. package/dist/universal.d.mts +0 -21
  69. package/dist/ws.d.mts +0 -200
  70. package/dist/wss.d.mts +0 -108
  71. package/src/build.ts +0 -362
  72. package/src/chokidar.ts +0 -427
  73. package/src/cli.ts +0 -1162
  74. package/src/config.ts +0 -509
  75. package/src/coverage.ts +0 -1479
  76. package/src/database.ts +0 -1410
  77. package/src/desktop-auto-render.ts +0 -317
  78. package/src/desktop-cli.ts +0 -1533
  79. package/src/desktop.ts +0 -99
  80. package/src/dev-build.ts +0 -340
  81. package/src/dom.ts +0 -901
  82. package/src/el.ts +0 -183
  83. package/src/fs.ts +0 -609
  84. package/src/hmr.ts +0 -149
  85. package/src/http.ts +0 -856
  86. package/src/https.ts +0 -411
  87. package/src/index.ts +0 -16
  88. package/src/mime-types.ts +0 -222
  89. package/src/mobile-cli.ts +0 -2313
  90. package/src/native-background.ts +0 -444
  91. package/src/native-border.ts +0 -343
  92. package/src/native-canvas.ts +0 -260
  93. package/src/native-cli.ts +0 -414
  94. package/src/native-color.ts +0 -904
  95. package/src/native-estimation.ts +0 -194
  96. package/src/native-grid.ts +0 -590
  97. package/src/native-interaction.ts +0 -1289
  98. package/src/native-layout.ts +0 -568
  99. package/src/native-link.ts +0 -76
  100. package/src/native-render-support.ts +0 -361
  101. package/src/native-spacing.ts +0 -231
  102. package/src/native-state.ts +0 -318
  103. package/src/native-strings.ts +0 -46
  104. package/src/native-transform.ts +0 -120
  105. package/src/native-types.ts +0 -439
  106. package/src/native-typography.ts +0 -254
  107. package/src/native-units.ts +0 -441
  108. package/src/native-vector.ts +0 -910
  109. package/src/native.ts +0 -5606
  110. package/src/path.ts +0 -493
  111. package/src/pm-cli.ts +0 -2498
  112. package/src/preview-build.ts +0 -294
  113. package/src/render-context.ts +0 -138
  114. package/src/router.ts +0 -260
  115. package/src/runtime.ts +0 -97
  116. package/src/server.ts +0 -2294
  117. package/src/state.ts +0 -556
  118. package/src/style.ts +0 -1790
  119. package/src/test-globals.d.ts +0 -184
  120. package/src/test-reporter.ts +0 -609
  121. package/src/test-runtime.ts +0 -1359
  122. package/src/test.ts +0 -368
  123. package/src/types.ts +0 -381
  124. package/src/universal.ts +0 -81
  125. package/src/wapk-cli.ts +0 -3213
  126. package/src/workspace-package.ts +0 -102
  127. package/src/ws.ts +0 -648
  128. 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
-