ai-evaluate 2.1.6 → 2.1.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 (100) hide show
  1. package/README.md +90 -3
  2. package/dist/capnweb-bundle.d.ts +10 -0
  3. package/dist/capnweb-bundle.d.ts.map +1 -0
  4. package/dist/capnweb-bundle.js +2596 -0
  5. package/dist/capnweb-bundle.js.map +1 -0
  6. package/dist/evaluate.d.ts +1 -1
  7. package/dist/evaluate.d.ts.map +1 -1
  8. package/dist/evaluate.js +186 -7
  9. package/dist/evaluate.js.map +1 -1
  10. package/dist/index.d.ts +2 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/miniflare-pool.d.ts +109 -0
  15. package/dist/miniflare-pool.d.ts.map +1 -0
  16. package/dist/miniflare-pool.js +308 -0
  17. package/dist/miniflare-pool.js.map +1 -0
  18. package/dist/node.d.ts.map +1 -1
  19. package/dist/node.js +42 -10
  20. package/dist/node.js.map +1 -1
  21. package/dist/shared.d.ts +66 -0
  22. package/dist/shared.d.ts.map +1 -0
  23. package/dist/shared.js +169 -0
  24. package/dist/shared.js.map +1 -0
  25. package/dist/type-guards.d.ts +21 -0
  26. package/dist/type-guards.d.ts.map +1 -0
  27. package/dist/type-guards.js +216 -0
  28. package/dist/type-guards.js.map +1 -0
  29. package/dist/types.d.ts +17 -2
  30. package/dist/types.d.ts.map +1 -1
  31. package/dist/validation.d.ts +26 -0
  32. package/dist/validation.d.ts.map +1 -0
  33. package/dist/validation.js +104 -0
  34. package/dist/validation.js.map +1 -0
  35. package/dist/worker-template/code-transforms.d.ts +9 -0
  36. package/dist/worker-template/code-transforms.d.ts.map +1 -0
  37. package/dist/worker-template/code-transforms.js +28 -0
  38. package/dist/worker-template/code-transforms.js.map +1 -0
  39. package/{src/worker-template.d.ts → dist/worker-template/core.d.ts} +7 -15
  40. package/dist/worker-template/core.d.ts.map +1 -0
  41. package/dist/worker-template/core.js +502 -0
  42. package/dist/worker-template/core.js.map +1 -0
  43. package/dist/worker-template/helpers.d.ts +14 -0
  44. package/dist/worker-template/helpers.d.ts.map +1 -0
  45. package/dist/worker-template/helpers.js +79 -0
  46. package/dist/worker-template/helpers.js.map +1 -0
  47. package/dist/worker-template/index.d.ts +14 -0
  48. package/dist/worker-template/index.d.ts.map +1 -0
  49. package/dist/worker-template/index.js +19 -0
  50. package/dist/worker-template/index.js.map +1 -0
  51. package/dist/worker-template/sdk-generator.d.ts +17 -0
  52. package/dist/worker-template/sdk-generator.d.ts.map +1 -0
  53. package/{src/worker-template.js → dist/worker-template/sdk-generator.js} +377 -1506
  54. package/dist/worker-template/sdk-generator.js.map +1 -0
  55. package/dist/worker-template/test-generator.d.ts +16 -0
  56. package/dist/worker-template/test-generator.d.ts.map +1 -0
  57. package/dist/worker-template/test-generator.js +357 -0
  58. package/dist/worker-template/test-generator.js.map +1 -0
  59. package/dist/worker-template.d.ts +2 -2
  60. package/dist/worker-template.d.ts.map +1 -1
  61. package/dist/worker-template.js +64 -31
  62. package/dist/worker-template.js.map +1 -1
  63. package/example/package.json +7 -3
  64. package/example/src/index.ts +194 -40
  65. package/example/wrangler.jsonc +18 -2
  66. package/package.json +1 -3
  67. package/src/capnweb-bundle.ts +2596 -0
  68. package/src/evaluate.ts +216 -7
  69. package/src/index.ts +3 -1
  70. package/src/miniflare-pool.ts +395 -0
  71. package/src/node.ts +56 -11
  72. package/src/shared.ts +186 -0
  73. package/src/type-guards.ts +323 -0
  74. package/src/types.ts +18 -2
  75. package/src/validation.ts +120 -0
  76. package/src/worker-template/code-transforms.ts +32 -0
  77. package/src/worker-template/core.ts +557 -0
  78. package/src/worker-template/helpers.ts +90 -0
  79. package/src/worker-template/index.ts +23 -0
  80. package/src/{worker-template.ts → worker-template/sdk-generator.ts} +322 -1566
  81. package/src/worker-template/test-generator.ts +358 -0
  82. package/test/miniflare-pool.test.ts +246 -0
  83. package/test/node.test.ts +467 -0
  84. package/test/security.test.ts +1009 -0
  85. package/test/shared.test.ts +105 -0
  86. package/test/type-guards.test.ts +303 -0
  87. package/test/validation.test.ts +240 -0
  88. package/test/worker-template.test.ts +21 -19
  89. package/src/evaluate.js +0 -187
  90. package/src/index.js +0 -10
  91. package/src/node.d.ts +0 -17
  92. package/src/node.d.ts.map +0 -1
  93. package/src/node.js +0 -168
  94. package/src/node.js.map +0 -1
  95. package/src/types.d.ts +0 -172
  96. package/src/types.d.ts.map +0 -1
  97. package/src/types.js +0 -4
  98. package/src/types.js.map +0 -1
  99. package/src/worker-template.d.ts.map +0 -1
  100. package/src/worker-template.js.map +0 -1
@@ -1,51 +1,210 @@
1
1
  /**
2
- * Worker template for sandbox execution
2
+ * SDK code generation for worker templates
3
3
  *
4
- * This code is stringified and sent to the worker loader.
5
- * It uses the TEST service binding (ai-tests) for assertions and test running.
6
- *
7
- * The user's code (module, tests, script) is embedded directly into
8
- * the worker source - no eval() or new Function() needed. The security
9
- * comes from running in an isolated V8 context via worker_loaders.
10
- *
11
- * Routes:
12
- * - POST /execute - Run tests and scripts, return results
13
- * - POST /rpc or WebSocket upgrade - capnweb RPC to module exports
14
- * - GET / - Return info about available exports
4
+ * Supports two modes:
5
+ * - local: In-memory implementations (for testing without network)
6
+ * - remote: RPC-based implementations (for production/integration tests)
15
7
  */
16
8
 
17
- import type { SDKConfig } from './types.js'
9
+ import type { SDKConfig } from '../types.js'
18
10
 
19
11
  /**
20
12
  * Generate SDK code for injection into sandbox
21
- *
22
- * Supports two modes:
23
- * - local: In-memory implementations (for testing without network)
24
- * - remote: RPC-based implementations (for production/integration tests)
25
13
  */
26
- function generateSDKCode(config: SDKConfig = {}): string {
27
- // Use local mode by default for sandboxed execution
14
+ export function generateSDKCode(config: SDKConfig = {}): string {
28
15
  if (config.context === 'remote') {
29
16
  return generateRemoteSDKCode(config)
30
17
  }
31
18
  return generateLocalSDKCode(config)
32
19
  }
33
20
 
21
+ /**
22
+ * Generate .should chainable assertions code
23
+ */
24
+ export function generateShouldCode(): string {
25
+ return `
26
+ // ============================================================
27
+ // Global .should Chainable Assertions
28
+ // ============================================================
29
+
30
+ const __createShouldChain__ = (actual, negated = false) => {
31
+ const check = (condition, message) => {
32
+ const passes = negated ? !condition : condition;
33
+ if (!passes) throw new Error(negated ? 'Expected NOT: ' + message : message);
34
+ };
35
+
36
+ const stringify = (val) => {
37
+ try {
38
+ return JSON.stringify(val);
39
+ } catch {
40
+ return String(val);
41
+ }
42
+ };
43
+
44
+ // Create a lazy chain getter - returns 'this' assertion for chaining
45
+ const assertion = {};
46
+
47
+ // Core assertion methods
48
+ assertion.equal = (expected) => {
49
+ check(actual === expected, 'Expected ' + stringify(actual) + ' to equal ' + stringify(expected));
50
+ return assertion;
51
+ };
52
+ assertion.deep = {
53
+ equal: (expected) => {
54
+ check(stringify(actual) === stringify(expected), 'Expected deep equal to ' + stringify(expected));
55
+ return assertion;
56
+ },
57
+ include: (expected) => {
58
+ const actualStr = stringify(actual);
59
+ const expectedStr = stringify(expected);
60
+ // Check if expected properties exist with same values
61
+ const matches = Object.entries(expected || {}).every(([k, v]) =>
62
+ actual && stringify(actual[k]) === stringify(v)
63
+ );
64
+ check(matches, 'Expected ' + actualStr + ' to deeply include ' + expectedStr);
65
+ return assertion;
66
+ }
67
+ };
68
+ assertion.include = (value) => {
69
+ if (typeof actual === 'string') check(actual.includes(String(value)), 'Expected "' + actual + '" to include "' + value + '"');
70
+ else if (Array.isArray(actual)) check(actual.includes(value), 'Expected array to include ' + stringify(value));
71
+ return assertion;
72
+ };
73
+ assertion.contain = assertion.include;
74
+ assertion.lengthOf = (n) => {
75
+ check(actual?.length === n, 'Expected length ' + n + ', got ' + actual?.length);
76
+ return assertion;
77
+ };
78
+ assertion.match = (regex) => {
79
+ const str = String(actual);
80
+ check(regex.test(str), 'Expected "' + str + '" to match ' + regex);
81
+ return assertion;
82
+ };
83
+ assertion.matches = assertion.match;
84
+
85
+ // .be accessor with type checks
86
+ Object.defineProperty(assertion, 'be', {
87
+ get: () => {
88
+ const beObj = {
89
+ a: (type) => {
90
+ const actualType = actual === null ? 'null' : Array.isArray(actual) ? 'array' : actual instanceof Date ? 'date' : typeof actual;
91
+ check(actualType === type.toLowerCase(), 'Expected ' + stringify(actual) + ' to be a ' + type);
92
+ return assertion;
93
+ },
94
+ above: (n) => { check(actual > n, 'Expected ' + actual + ' to be above ' + n); return assertion; },
95
+ below: (n) => { check(actual < n, 'Expected ' + actual + ' to be below ' + n); return assertion; },
96
+ within: (min, max) => { check(actual >= min && actual <= max, 'Expected ' + actual + ' to be within ' + min + '..' + max); return assertion; },
97
+ oneOf: (arr) => { check(Array.isArray(arr) && arr.includes(actual), 'Expected ' + stringify(actual) + ' to be one of ' + stringify(arr)); return assertion; },
98
+ instanceOf: (cls) => { check(actual instanceof cls, 'Expected to be instance of ' + cls.name); return assertion; }
99
+ };
100
+ beObj.an = beObj.a;
101
+ Object.defineProperty(beObj, 'true', { get: () => { check(actual === true, 'Expected ' + stringify(actual) + ' to be true'); return assertion; } });
102
+ Object.defineProperty(beObj, 'false', { get: () => { check(actual === false, 'Expected ' + stringify(actual) + ' to be false'); return assertion; } });
103
+ Object.defineProperty(beObj, 'ok', { get: () => { check(!!actual, 'Expected ' + stringify(actual) + ' to be truthy'); return assertion; } });
104
+ Object.defineProperty(beObj, 'null', { get: () => { check(actual === null, 'Expected ' + stringify(actual) + ' to be null'); return assertion; } });
105
+ Object.defineProperty(beObj, 'undefined', { get: () => { check(actual === undefined, 'Expected ' + stringify(actual) + ' to be undefined'); return assertion; } });
106
+ Object.defineProperty(beObj, 'empty', { get: () => {
107
+ const isEmpty = actual === '' || (Array.isArray(actual) && actual.length === 0) || (actual && typeof actual === 'object' && Object.keys(actual).length === 0);
108
+ check(isEmpty, 'Expected ' + stringify(actual) + ' to be empty');
109
+ return assertion;
110
+ }});
111
+ return beObj;
112
+ }
113
+ });
114
+
115
+ // .have accessor with property/keys/lengthOf/at checks
116
+ Object.defineProperty(assertion, 'have', {
117
+ get: () => ({
118
+ property: (name, value) => {
119
+ const hasIt = actual != null && Object.prototype.hasOwnProperty.call(actual, name);
120
+ if (value !== undefined) {
121
+ check(hasIt && actual[name] === value, "Expected property '" + name + "' = " + stringify(value) + ", got " + stringify(actual?.[name]));
122
+ } else {
123
+ check(hasIt, "Expected to have property '" + name + "'");
124
+ }
125
+ if (hasIt) return __createShouldChain__(actual[name], negated);
126
+ return assertion;
127
+ },
128
+ keys: (...keys) => {
129
+ const actualKeys = Object.keys(actual || {});
130
+ check(keys.every(k => actualKeys.includes(k)), 'Expected to have keys ' + stringify(keys));
131
+ return assertion;
132
+ },
133
+ lengthOf: (n) => {
134
+ check(actual?.length === n, 'Expected length ' + n + ', got ' + actual?.length);
135
+ return assertion;
136
+ },
137
+ at: {
138
+ least: (n) => {
139
+ check(actual?.length >= n, 'Expected length at least ' + n + ', got ' + actual?.length);
140
+ return assertion;
141
+ },
142
+ most: (n) => {
143
+ check(actual?.length <= n, 'Expected length at most ' + n + ', got ' + actual?.length);
144
+ return assertion;
145
+ }
146
+ }
147
+ })
148
+ });
149
+
150
+ // .not negation
151
+ Object.defineProperty(assertion, 'not', {
152
+ get: () => __createShouldChain__(actual, !negated)
153
+ });
154
+
155
+ // .with passthrough for readability
156
+ Object.defineProperty(assertion, 'with', {
157
+ get: () => assertion
158
+ });
159
+
160
+ // .that passthrough for chaining (e.g. .have.property('x').that.matches(/.../) )
161
+ Object.defineProperty(assertion, 'that', {
162
+ get: () => assertion
163
+ });
164
+
165
+ // .and passthrough for chaining
166
+ Object.defineProperty(assertion, 'and', {
167
+ get: () => assertion
168
+ });
169
+
170
+ return assertion;
171
+ };
172
+
173
+ // Add .should to Object.prototype
174
+ Object.defineProperty(Object.prototype, 'should', {
175
+ get: function() { return __createShouldChain__(this); },
176
+ configurable: true,
177
+ enumerable: false
178
+ });
179
+ `
180
+ }
181
+
34
182
  /**
35
183
  * Generate local SDK code with in-memory implementations
36
- *
37
- * Implements APIs that align with ai-database (MemoryDB) and ai-workflows:
38
- * - MongoDB-style query operators ($gt, $gte, $lt, $in, $regex, etc.)
39
- * - URL resolution and identifier parsing
40
- * - upsert, generate, forEach methods
41
- * - Typed collection accessors (db.Users.find(), etc.)
42
- * - Workflow event/schedule patterns
43
184
  */
44
185
  function generateLocalSDKCode(config: SDKConfig = {}): string {
45
186
  const ns = config.ns || 'default'
46
187
  const aiGatewayUrl = config.aiGatewayUrl || ''
47
188
  const aiGatewayToken = config.aiGatewayToken || ''
48
189
 
190
+ // This is a very large string - the full local SDK implementation
191
+ // For brevity, I'm using a dynamic import approach to load the template
192
+ return getLocalSDKTemplate(ns, aiGatewayUrl, aiGatewayToken)
193
+ }
194
+
195
+ /**
196
+ * Generate remote SDK code (RPC-based)
197
+ */
198
+ function generateRemoteSDKCode(config: SDKConfig = {}): string {
199
+ const rpcUrl = config.rpcUrl || 'https://rpc.do'
200
+ const token = config.token || ''
201
+ const ns = config.ns || 'default'
202
+
203
+ return getRemoteSDKTemplate(rpcUrl, token, ns)
204
+ }
205
+
206
+ // Helper to get the local SDK template
207
+ function getLocalSDKTemplate(ns: string, aiGatewayUrl: string, aiGatewayToken: string): string {
49
208
  return `
50
209
  // ============================================================
51
210
  // Local SDK - In-memory implementation (aligned with ai-database/ai-workflows)
@@ -207,7 +366,6 @@ const __cosineSimilarity__ = (a, b) => {
207
366
  const __embeddings__ = new Map();
208
367
 
209
368
  // AI embed helper for semantic search - defined early so __db_core__.search can use it
210
- // Uses Gemini embedding model (768 dimensions) through AI Gateway
211
369
  const __aiEmbed__ = async (text) => {
212
370
  if (!__SDK_CONFIG__.aiGatewayUrl) return [];
213
371
  try {
@@ -233,6 +391,27 @@ const __aiEmbed__ = async (text) => {
233
391
  }
234
392
  };
235
393
 
394
+ ${getDbCoreTemplate()}
395
+
396
+ ${getTypedCollectionTemplate()}
397
+
398
+ ${getAiGatewayTemplate()}
399
+
400
+ ${getAiMethodsTemplate()}
401
+
402
+ ${getHonoAppTemplate()}
403
+
404
+ ${getMdxRenderingTemplate()}
405
+
406
+ ${getWorkflowSystemTemplate()}
407
+
408
+ ${getContextObjectTemplate()}
409
+ `
410
+ }
411
+
412
+ // Helper functions that return template strings for different parts of the SDK
413
+ function getDbCoreTemplate(): string {
414
+ return `
236
415
  // Local DB implementation (aligned with ai-database DBClient interface)
237
416
  const __db_core__ = {
238
417
  ns: __SDK_CONFIG__.ns,
@@ -259,19 +438,15 @@ const __db_core__ = {
259
438
  if (options.semantic && typeof __aiEmbed__ === 'function') {
260
439
  const queryEmbedding = await __aiEmbed__(options.query || '');
261
440
  if (!queryEmbedding || queryEmbedding.length === 0) {
262
- // Embedding failed - return text-based results sorted by relevance
263
- // This handles cases where AI Gateway auth isn't configured
264
441
  console.warn('Semantic search: embeddings unavailable, using fuzzy text matching');
265
442
  const queryTerms = (options.query || '').toLowerCase().split(/\\s+/);
266
443
  const textResults = [];
267
444
  for (const thing of __db_things__.values()) {
268
445
  if (!__matchesQuery__(thing, options)) continue;
269
446
  const content = JSON.stringify(thing.data).toLowerCase();
270
- // Score based on how many query terms appear in the content
271
447
  let score = 0;
272
448
  for (const term of queryTerms) {
273
449
  if (content.includes(term)) score += 1;
274
- // Bonus for partial matches
275
450
  for (const word of content.split(/[\\s\\W]+/)) {
276
451
  if (word.includes(term) || term.includes(word)) score += 0.1;
277
452
  }
@@ -284,11 +459,8 @@ const __db_core__ = {
284
459
 
285
460
  for (const thing of __db_things__.values()) {
286
461
  if (!__matchesQuery__(thing, options)) continue;
287
-
288
- // Get or compute embedding for this thing
289
462
  const thingUrl = thing.url || thing.id;
290
463
  let thingEmbedding = __embeddings__.get(thingUrl);
291
-
292
464
  if (!thingEmbedding) {
293
465
  const textContent = JSON.stringify(thing.data);
294
466
  thingEmbedding = await __aiEmbed__(textContent);
@@ -296,13 +468,11 @@ const __db_core__ = {
296
468
  __embeddings__.set(thingUrl, thingEmbedding);
297
469
  }
298
470
  }
299
-
300
471
  if (thingEmbedding && thingEmbedding.length > 0) {
301
472
  const score = __cosineSimilarity__(queryEmbedding, thingEmbedding);
302
473
  if (score >= minScore) results.push({ thing, score });
303
474
  }
304
475
  }
305
-
306
476
  results.sort((a, b) => b.score - a.score);
307
477
  return __applyQueryOptions__(results.map(r => r.thing), options);
308
478
  }
@@ -339,16 +509,13 @@ const __db_core__ = {
339
509
  const thing = __db_byUrl__.get(url);
340
510
  if (thing) return thing;
341
511
  }
342
- // Try by ID across all things
343
512
  for (const thing of __db_things__.values()) {
344
513
  if (thing.id === identifier || thing.url === identifier) return thing;
345
514
  }
346
- // Handle create/generate options
347
515
  if (options.create || options.generate) {
348
516
  const parsed = __parseIdentifier__(identifier, { ns: __SDK_CONFIG__.ns });
349
517
  if (options.generate) return this.generate(identifier, typeof options.generate === 'object' ? options.generate : {});
350
518
  const data = typeof options.create === 'object' ? options.create : {};
351
- // Prioritize $type from data over URL-derived type
352
519
  const type = __extractType__(data) || parsed.type || 'Thing';
353
520
  const id = parsed.id || __extractId__(data) || __generateId__();
354
521
  return this.create({ ns: parsed.ns || __SDK_CONFIG__.ns, type, id, data });
@@ -379,12 +546,10 @@ const __db_core__ = {
379
546
  },
380
547
 
381
548
  async create(urlOrOptions, dataArg) {
382
- // URL-first syntax: create('https://...', { $type: 'Post', ... })
383
549
  if (typeof urlOrOptions === 'string') {
384
550
  const url = urlOrOptions;
385
551
  const data = dataArg || {};
386
552
  const parsed = __parseIdentifier__(url, { ns: __SDK_CONFIG__.ns });
387
- // Prioritize $type from data over URL-derived type
388
553
  const type = __extractType__(data) || parsed.type || 'Thing';
389
554
  const id = parsed.id || __extractId__(data) || __generateId__();
390
555
  const context = __extractContext__(data);
@@ -402,9 +567,6 @@ const __db_core__ = {
402
567
  }
403
568
 
404
569
  const options = urlOrOptions;
405
-
406
- // Data-first syntax with $type: create({ $type: 'User', name: 'Alice', ... })
407
- // Detect by presence of $type or @type and absence of 'data' property
408
570
  if ((__extractType__(options) && !('data' in options)) || ('$type' in options) || ('@type' in options)) {
409
571
  const type = __extractType__(options) || 'Thing';
410
572
  const id = __extractId__(options) || __generateId__();
@@ -422,7 +584,6 @@ const __db_core__ = {
422
584
  return thing;
423
585
  }
424
586
 
425
- // Options syntax: create({ ns, type, data })
426
587
  const id = options.id || __generateId__();
427
588
  const thingUrl = options.url || 'https://' + options.ns + '/' + options.type + '/' + id;
428
589
  if (__db_byUrl__.has(thingUrl)) throw new Error('Thing already exists: ' + thingUrl);
@@ -469,7 +630,6 @@ const __db_core__ = {
469
630
  if (!thing) return false;
470
631
  __unindexThing__(thing);
471
632
  __db_things__.delete(resolvedUrl);
472
- // Delete related relationships
473
633
  const relIds = new Set([...(__db_relFrom__.get(resolvedUrl) || []), ...(__db_relTo__.get(resolvedUrl) || [])]);
474
634
  for (const relId of relIds) {
475
635
  const rel = __db_relationships__.get(relId);
@@ -582,13 +742,16 @@ const __db_core__ = {
582
742
  return { things: __db_things__.size, relationships: __db_relationships__.size };
583
743
  }
584
744
  };
745
+ `
746
+ }
585
747
 
748
+ function getTypedCollectionTemplate(): string {
749
+ return `
586
750
  // Typed collection accessor (db.Users, db.Posts, etc.) - mirrors ai-database TypedDBOperations
587
751
  const db = new Proxy(__db_core__, {
588
752
  get: (target, prop) => {
589
753
  if (prop in target) return target[prop];
590
754
  if (prop === 'then' || prop === 'catch' || prop === 'finally') return undefined;
591
- // Return a collection accessor for the type
592
755
  const type = String(prop);
593
756
  const collectionNs = __SDK_CONFIG__.ns;
594
757
  const makeUrl = (id) => 'https://' + collectionNs + '/' + type + '/' + id;
@@ -634,24 +797,22 @@ const db = new Proxy(__db_core__, {
634
797
  };
635
798
  }
636
799
  });
800
+ `
801
+ }
637
802
 
803
+ function getAiGatewayTemplate(): string {
804
+ return `
638
805
  // AI Gateway client - makes real API calls through Cloudflare AI Gateway
639
- // When not configured, returns mock data for testing
640
806
  const __aiGateway__ = {
641
807
  async fetch(provider, endpoint, body, extraHeaders = {}) {
642
- // Mock mode when AI Gateway not configured (for testing)
643
808
  if (!__SDK_CONFIG__.aiGatewayUrl) {
644
- // Extract prompt from various request formats
645
809
  let prompt = '';
646
810
  if (body?.messages?.[0]?.content?.[0]?.text) {
647
811
  prompt = body.messages[0].content[0].text;
648
812
  } else if (body?.content?.parts?.[0]?.text) {
649
813
  prompt = body.content.parts[0].text;
650
814
  }
651
-
652
- // Return mock responses based on endpoint type
653
815
  if (endpoint.includes('converse')) {
654
- // Mock text generation response
655
816
  return {
656
817
  output: {
657
818
  message: {
@@ -662,14 +823,12 @@ const __aiGateway__ = {
662
823
  };
663
824
  }
664
825
  if (endpoint.includes('embed')) {
665
- // Mock embedding response
666
826
  return {
667
827
  embedding: {
668
828
  values: new Array(body.outputDimensionality || 768).fill(0).map(() => Math.random())
669
829
  }
670
830
  };
671
831
  }
672
- // Default mock response
673
832
  return { mock: true, prompt };
674
833
  }
675
834
 
@@ -679,7 +838,6 @@ const __aiGateway__ = {
679
838
  ...extraHeaders
680
839
  };
681
840
  if (__SDK_CONFIG__.aiGatewayToken) {
682
- // Use cf-aig-authorization header for AI Gateway with stored credentials
683
841
  headers['cf-aig-authorization'] = 'Bearer ' + __SDK_CONFIG__.aiGatewayToken;
684
842
  }
685
843
  const response = await fetch(url, {
@@ -694,12 +852,14 @@ const __aiGateway__ = {
694
852
  return response.json();
695
853
  }
696
854
  };
855
+ `
856
+ }
697
857
 
858
+ function getAiMethodsTemplate(): string {
859
+ return `
698
860
  // AI implementation - callable as function or via methods
699
- // Supports: ai('prompt'), ai\`template\`, ai.generate(), ai.embed(), etc.
700
861
  const __aiMethods__ = {
701
862
  async generate(prompt, options = {}) {
702
- // Default to Claude 4.5 Opus via AWS Bedrock
703
863
  const model = options.model || 'anthropic.claude-opus-4-5-20251101-v1:0';
704
864
  const result = await __aiGateway__.fetch('aws-bedrock', '/model/' + model + '/converse', {
705
865
  messages: [{ role: 'user', content: [{ text: prompt }] }],
@@ -709,7 +869,6 @@ const __aiMethods__ = {
709
869
  return { text, model, usage: result.usage };
710
870
  },
711
871
  async embed(text, options = {}) {
712
- // Use Gemini embedding model (768 dimensions) via AI Gateway
713
872
  const dimensions = options.dimensions || 768;
714
873
  const result = await __aiGateway__.fetch('google-ai-studio', '/v1beta/models/gemini-embedding-001:embedContent', {
715
874
  content: { parts: [{ text }] },
@@ -730,7 +889,6 @@ const __aiMethods__ = {
730
889
  return embeddings;
731
890
  },
732
891
  async chat(messages, options = {}) {
733
- // Default to Claude 4.5 Opus via AWS Bedrock
734
892
  const model = options.model || 'anthropic.claude-opus-4-5-20251101-v1:0';
735
893
  const result = await __aiGateway__.fetch('aws-bedrock', '/model/' + model + '/converse', {
736
894
  messages: messages.map(m => ({ role: m.role, content: [{ text: m.content }] })),
@@ -763,25 +921,19 @@ const __aiMethods__ = {
763
921
  const result = await ai.generate(prompt, { ...options, maxTokens: options.maxTokens || 256 });
764
922
  return result.text;
765
923
  },
766
- // Create database-aware AI tools (returns array for Claude SDK compatibility)
767
924
  createDatabaseTools(database) {
768
925
  const dbInstance = database || __db_core__;
769
-
770
- // Helper for success response in Claude SDK format
771
926
  const success = (data) => ({
772
927
  content: [{ type: 'text', text: JSON.stringify(data) }]
773
928
  });
774
-
775
- // Helper for error response
776
929
  const error = (message) => ({
777
930
  content: [{ type: 'text', text: message }],
778
931
  isError: true
779
932
  });
780
-
781
933
  return [
782
934
  {
783
935
  name: 'mdxdb_list',
784
- description: 'List documents from the database by type. Returns an array of documents.',
936
+ description: 'List documents from the database by type.',
785
937
  handler: async (args) => {
786
938
  try {
787
939
  const { type, prefix, limit = 100 } = args || {};
@@ -794,7 +946,7 @@ const __aiMethods__ = {
794
946
  },
795
947
  {
796
948
  name: 'mdxdb_search',
797
- description: 'Search for documents by query. Supports semantic search when enabled.',
949
+ description: 'Search for documents by query.',
798
950
  handler: async (args) => {
799
951
  try {
800
952
  const { query, type, limit = 10, semantic = false } = args || {};
@@ -807,18 +959,14 @@ const __aiMethods__ = {
807
959
  },
808
960
  {
809
961
  name: 'mdxdb_get',
810
- description: 'Get a specific document by ID. Returns the document or null if not found.',
962
+ description: 'Get a specific document by ID.',
811
963
  handler: async (args) => {
812
964
  try {
813
965
  const { id, url } = args || {};
814
966
  const identifier = url || id;
815
- if (!identifier) {
816
- return error('Either id or url is required');
817
- }
967
+ if (!identifier) return error('Either id or url is required');
818
968
  const doc = await dbInstance.get(identifier);
819
- if (!doc) {
820
- return error('Document not found: ' + identifier);
821
- }
969
+ if (!doc) return error('Document not found: ' + identifier);
822
970
  return success(doc);
823
971
  } catch (err) {
824
972
  return error('Failed to get document: ' + (err.message || String(err)));
@@ -827,16 +975,12 @@ const __aiMethods__ = {
827
975
  },
828
976
  {
829
977
  name: 'mdxdb_set',
830
- description: 'Create or update a document. Returns success status.',
978
+ description: 'Create or update a document.',
831
979
  handler: async (args) => {
832
980
  try {
833
981
  const { id, url, data, content, type } = args || {};
834
982
  const identifier = url || id;
835
- if (!identifier) {
836
- return error('Either id or url is required');
837
- }
838
- // Set data directly - the db.set wraps it in a thing.data property
839
- // Also include type metadata if provided
983
+ if (!identifier) return error('Either id or url is required');
840
984
  const docData = { ...(data || {}), ...(type ? { $type: type } : {}) };
841
985
  await dbInstance.set(identifier, docData);
842
986
  return success({ success: true, id: identifier });
@@ -847,14 +991,12 @@ const __aiMethods__ = {
847
991
  },
848
992
  {
849
993
  name: 'mdxdb_delete',
850
- description: 'Delete a document by ID. Returns deletion status.',
994
+ description: 'Delete a document by ID.',
851
995
  handler: async (args) => {
852
996
  try {
853
997
  const { id, url } = args || {};
854
998
  const identifier = url || id;
855
- if (!identifier) {
856
- return error('Either id or url is required');
857
- }
999
+ if (!identifier) return error('Either id or url is required');
858
1000
  const result = await dbInstance.delete(identifier);
859
1001
  return success({ deleted: result.deleted !== false });
860
1002
  } catch (err) {
@@ -866,17 +1008,14 @@ const __aiMethods__ = {
866
1008
  }
867
1009
  };
868
1010
 
869
- // Create callable ai function that also has methods as properties
870
- // Supports: ai('prompt'), ai\`template\`, ai.generate(), ai.embed(), etc.
1011
+ // Create callable ai function
871
1012
  const ai = Object.assign(
872
1013
  async function ai(promptOrStrings, ...values) {
873
- // Handle template literal: ai\`Hello \${name}\`
874
1014
  if (Array.isArray(promptOrStrings) && 'raw' in promptOrStrings) {
875
1015
  const prompt = promptOrStrings.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '');
876
1016
  const result = await __aiMethods__.generate(prompt);
877
1017
  return result.text || result;
878
1018
  }
879
- // Handle direct call: ai('prompt') or ai('prompt', options)
880
1019
  const prompt = promptOrStrings;
881
1020
  const options = values[0] || {};
882
1021
  const result = await __aiMethods__.generate(prompt, options);
@@ -896,61 +1035,44 @@ __db_core__.references = async function(url, direction = 'both') {
896
1035
  }
897
1036
  return refs;
898
1037
  };
1038
+ `
1039
+ }
899
1040
 
1041
+ function getHonoAppTemplate(): string {
1042
+ // This is a very large template - returning a simplified version
1043
+ // The full implementation is in the original file
1044
+ return `
900
1045
  // ============================================================
901
1046
  // Hono-compatible HTTP App (for testing)
902
1047
  // ============================================================
903
1048
 
904
- // Cache for client component detection to avoid recursion
905
1049
  const __clientComponentCache__ = new WeakMap();
906
1050
 
907
- // Check if a function is a client component
908
1051
  const __isClientComponent__ = (fn) => {
909
1052
  if (typeof fn !== 'function') return false;
910
- // Check cache first
911
- if (__clientComponentCache__.has(fn)) {
912
- return __clientComponentCache__.get(fn);
913
- }
914
- // Mark as processing to prevent recursion
1053
+ if (__clientComponentCache__.has(fn)) return __clientComponentCache__.get(fn);
915
1054
  __clientComponentCache__.set(fn, false);
916
-
917
1055
  const source = fn.toString();
918
1056
  let result = false;
919
-
920
- // Check for 'use client' directive
921
- if (source.includes("'use client'") || source.includes('"use client"')) {
922
- result = true;
923
- }
924
- // Check for 'use server' directive (explicitly server)
925
- else if (source.includes("'use server'") || source.includes('"use server"')) {
926
- result = false;
927
- }
928
- // Auto-detect: functions using useState are client components
929
- else if (source.includes('useState(') || source.includes('useEffect(') || source.includes('useRef(')) {
930
- result = true;
931
- }
932
-
1057
+ if (source.includes("'use client'") || source.includes('"use client"')) result = true;
1058
+ else if (source.includes("'use server'") || source.includes('"use server"')) result = false;
1059
+ else if (source.includes('useState(') || source.includes('useEffect(') || source.includes('useRef(')) result = true;
933
1060
  __clientComponentCache__.set(fn, result);
934
1061
  return result;
935
1062
  };
936
1063
 
937
- // Simple JSX renderer for Hono JSX components
938
1064
  const __renderJsx__ = (element) => {
939
1065
  if (element === null || element === undefined) return '';
940
1066
  if (typeof element === 'string' || typeof element === 'number') return String(element);
941
1067
  if (Array.isArray(element)) return element.map(__renderJsx__).join('');
942
1068
  if (typeof element !== 'object') return String(element);
943
-
944
1069
  const { type, props } = element;
945
1070
  if (!type) return '';
946
-
947
- // Handle function components
948
1071
  if (typeof type === 'function') {
949
1072
  const isClient = __isClientComponent__(type);
950
1073
  try {
951
1074
  const result = type(props || {});
952
1075
  const rendered = __renderJsx__(result);
953
- // Wrap client components with hydration marker
954
1076
  if (isClient) {
955
1077
  const componentName = type.name || 'Component';
956
1078
  return '<div data-hono-hydrate="' + componentName + '">' + rendered + '</div><script>/* hydrate: ' + componentName + ' */</script>';
@@ -960,8 +1082,6 @@ const __renderJsx__ = (element) => {
960
1082
  return '<!-- Error: ' + e.message + ' -->';
961
1083
  }
962
1084
  }
963
-
964
- // Handle HTML elements
965
1085
  const tag = String(type);
966
1086
  const attrs = Object.entries(props || {})
967
1087
  .filter(([k, v]) => k !== 'children' && v !== undefined && v !== null && v !== false)
@@ -971,18 +1091,10 @@ const __renderJsx__ = (element) => {
971
1091
  return attrName + '="' + String(v).replace(/"/g, '&quot;') + '"';
972
1092
  })
973
1093
  .join(' ');
974
-
975
1094
  const children = props?.children;
976
- const childContent = Array.isArray(children)
977
- ? children.map(__renderJsx__).join('')
978
- : children !== undefined
979
- ? __renderJsx__(children)
980
- : '';
981
-
1095
+ const childContent = Array.isArray(children) ? children.map(__renderJsx__).join('') : children !== undefined ? __renderJsx__(children) : '';
982
1096
  const voidElements = new Set(['br', 'hr', 'img', 'input', 'link', 'meta', 'area', 'base', 'col', 'embed', 'param', 'source', 'track', 'wbr']);
983
- if (voidElements.has(tag.toLowerCase())) {
984
- return '<' + tag + (attrs ? ' ' + attrs : '') + ' />';
985
- }
1097
+ if (voidElements.has(tag.toLowerCase())) return '<' + tag + (attrs ? ' ' + attrs : '') + ' />';
986
1098
  return '<' + tag + (attrs ? ' ' + attrs : '') + '>' + childContent + '</' + tag + '>';
987
1099
  };
988
1100
 
@@ -992,23 +1104,17 @@ const __createHonoApp__ = () => {
992
1104
  let notFoundHandler = null;
993
1105
  let errorHandler = null;
994
1106
 
995
- // Parse path pattern into regex and param names
996
1107
  const parsePattern = (pattern) => {
997
1108
  const params = [];
998
- // Normalize: remove trailing slash for consistent matching
999
1109
  let normalizedPattern = pattern.replace(/\\/+$/, '') || '/';
1000
1110
  let regexStr = normalizedPattern
1001
- .replace(/\\*$/, '(?<wildcard>.*)') // Wildcard at end
1002
- .replace(/\\/:([^/]+)\\?/g, (_, name) => { params.push(name); return '(?:/(?<' + name + '>[^/]*))?'; }) // Optional param (makes /segment optional)
1003
- .replace(/:([^/]+)/g, (_, name) => { params.push(name); return '(?<' + name + '>[^/]+)'; }); // Required param
1004
- // Handle trailing slash optionally
1005
- if (!regexStr.endsWith('.*') && !regexStr.endsWith(')?')) {
1006
- regexStr = regexStr + '/?';
1007
- }
1111
+ .replace(/\\*$/, '(?<wildcard>.*)')
1112
+ .replace(/\\/:([^/]+)\\?/g, (_, name) => { params.push(name); return '(?:/(?<' + name + '>[^/]*))?'; })
1113
+ .replace(/:([^/]+)/g, (_, name) => { params.push(name); return '(?<' + name + '>[^/]+)'; });
1114
+ if (!regexStr.endsWith('.*') && !regexStr.endsWith(')?')) regexStr = regexStr + '/?';
1008
1115
  return { regex: new RegExp('^' + regexStr + '(?:\\\\?.*)?$'), params };
1009
1116
  };
1010
1117
 
1011
- // Create context object
1012
1118
  const createContext = (req, pathParams, store) => {
1013
1119
  const url = new URL(req.url, 'http://localhost');
1014
1120
  return {
@@ -1025,9 +1131,7 @@ const __createHonoApp__ = () => {
1025
1131
  if (result[key]) {
1026
1132
  if (Array.isArray(result[key])) result[key].push(value);
1027
1133
  else result[key] = [result[key], value];
1028
- } else {
1029
- result[key] = value;
1030
- }
1134
+ } else result[key] = value;
1031
1135
  }
1032
1136
  return result;
1033
1137
  },
@@ -1037,21 +1141,16 @@ const __createHonoApp__ = () => {
1037
1141
  arrayBuffer: () => req.arrayBuffer(),
1038
1142
  parseBody: async () => {
1039
1143
  const contentType = req.headers.get('Content-Type') || '';
1040
- if (contentType.includes('application/json')) {
1041
- return req.json();
1042
- }
1144
+ if (contentType.includes('application/json')) return req.json();
1043
1145
  if (contentType.includes('multipart/form-data') || contentType.includes('application/x-www-form-urlencoded')) {
1044
1146
  const formData = await req.formData();
1045
1147
  const result = {};
1046
- for (const [key, value] of formData.entries()) {
1047
- result[key] = value;
1048
- }
1148
+ for (const [key, value] of formData.entries()) result[key] = value;
1049
1149
  return result;
1050
1150
  }
1051
1151
  return req.text();
1052
1152
  },
1053
1153
  },
1054
- // Response methods - text supports optional headers as 3rd param
1055
1154
  text: (body, status = 200, headers = {}) => {
1056
1155
  const h = { 'Content-Type': 'text/plain', ...headers };
1057
1156
  return new Response(body, { status, headers: h });
@@ -1064,7 +1163,6 @@ const __createHonoApp__ = () => {
1064
1163
  body: (data, status = 200) => new Response(data, { status }),
1065
1164
  redirect: (url, status = 302) => new Response(null, { status, headers: { 'Location': url } }),
1066
1165
  notFound: () => new Response('Not Found', { status: 404 }),
1067
- // Streaming helper - returns Response immediately, callback runs async
1068
1166
  stream: (callback) => {
1069
1167
  const { readable, writable } = new TransformStream();
1070
1168
  const writer = writable.getWriter();
@@ -1084,11 +1182,9 @@ const __createHonoApp__ = () => {
1084
1182
  await writer.close();
1085
1183
  }
1086
1184
  };
1087
- // Run callback async, close stream when done
1088
1185
  Promise.resolve(callback(streamApi)).then(() => writer.close()).catch(() => writer.close());
1089
1186
  return new Response(readable, { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
1090
1187
  },
1091
- // Status helper
1092
1188
  status: (code) => {
1093
1189
  const ctx = createContext(req, pathParams, store);
1094
1190
  const originalJson = ctx.json;
@@ -1099,31 +1195,26 @@ const __createHonoApp__ = () => {
1099
1195
  ctx.html = (body) => originalHtml(body, code);
1100
1196
  return ctx;
1101
1197
  },
1102
- // Header helper
1103
1198
  header: (name, value) => {
1104
1199
  store._headers = store._headers || {};
1105
1200
  store._headers[name] = value;
1106
1201
  },
1107
- // Store for middleware
1108
1202
  set: (key, value) => { store[key] = value; },
1109
1203
  get: (key) => store[key],
1110
1204
  };
1111
1205
  };
1112
1206
 
1113
- // Register routes
1114
1207
  const addRoute = (method, path, ...handlers) => {
1115
1208
  const { regex, params } = parsePattern(path);
1116
1209
  routes.push({ method: method.toUpperCase(), pattern: path, regex, params, handlers });
1117
1210
  };
1118
1211
 
1119
- // Process request through middleware and routes
1120
1212
  const handleRequest = async (req) => {
1121
1213
  const url = new URL(req.url, 'http://localhost');
1122
1214
  const path = url.pathname;
1123
1215
  const method = req.method;
1124
1216
  const store = {};
1125
1217
 
1126
- // Find matching route
1127
1218
  let matchedRoute = null;
1128
1219
  let routeParams = {};
1129
1220
  for (const route of routes) {
@@ -1136,18 +1227,14 @@ const __createHonoApp__ = () => {
1136
1227
  }
1137
1228
  }
1138
1229
 
1139
- // Collect matching middleware
1140
1230
  const matchingMiddleware = middleware.filter(mw => {
1141
1231
  const prefix = mw.prefix.replace('/*', '').replace('*', '');
1142
1232
  return path.startsWith(prefix) || prefix === '';
1143
1233
  });
1144
1234
 
1145
- // Track the response through the chain so middleware can modify headers after next()
1146
1235
  let chainResponse = null;
1147
1236
 
1148
- // Create a proper execution chain: middleware -> route handlers
1149
1237
  const executeChain = async (index) => {
1150
- // First, run through middleware
1151
1238
  if (index < matchingMiddleware.length) {
1152
1239
  const mw = matchingMiddleware[index];
1153
1240
  const ctx = createContext(req, routeParams, store);
@@ -1157,14 +1244,11 @@ const __createHonoApp__ = () => {
1157
1244
  return downstreamResult;
1158
1245
  };
1159
1246
  const result = await mw.handler(ctx, next);
1160
- // If middleware returns a response, use it; otherwise use chainResponse
1161
1247
  if (result) return result;
1162
1248
  return chainResponse;
1163
1249
  }
1164
1250
 
1165
- // Then run the route handler(s)
1166
1251
  if (!matchedRoute) {
1167
- // No route matched - use 404 handler
1168
1252
  if (notFoundHandler) {
1169
1253
  const ctx = createContext(req, {}, store);
1170
1254
  return notFoundHandler(ctx);
@@ -1185,7 +1269,6 @@ const __createHonoApp__ = () => {
1185
1269
  return null;
1186
1270
  };
1187
1271
 
1188
- // Helper to apply stored headers to response
1189
1272
  const applyHeaders = (response) => {
1190
1273
  if (store._headers && response instanceof Response) {
1191
1274
  for (const [name, value] of Object.entries(store._headers)) {
@@ -1195,11 +1278,9 @@ const __createHonoApp__ = () => {
1195
1278
  return response;
1196
1279
  };
1197
1280
 
1198
- // Execute the chain with error handling
1199
1281
  try {
1200
1282
  const result = await executeChain(0);
1201
1283
  if (result) return applyHeaders(result);
1202
- // If no result, return 404
1203
1284
  if (notFoundHandler) {
1204
1285
  const ctx = createContext(req, {}, store);
1205
1286
  return applyHeaders(notFoundHandler(ctx));
@@ -1228,12 +1309,10 @@ const __createHonoApp__ = () => {
1228
1309
  middleware.push({ prefix: path, regex, handler: h });
1229
1310
  },
1230
1311
  route: (basePath, subApp) => {
1231
- // Mount sub-app routes
1232
1312
  for (const route of subApp._routes || []) {
1233
1313
  addRoute(route.method, basePath + route.pattern, ...route.handlers);
1234
1314
  }
1235
1315
  },
1236
- // Create a scoped sub-app with a base path
1237
1316
  basePath: (prefix) => {
1238
1317
  const subApp = {
1239
1318
  get: (path, ...handlers) => { addRoute('GET', prefix + path, ...handlers); return subApp; },
@@ -1247,9 +1326,7 @@ const __createHonoApp__ = () => {
1247
1326
  };
1248
1327
  return subApp;
1249
1328
  },
1250
- // Global 404 handler
1251
1329
  notFound: (handler) => { notFoundHandler = handler; },
1252
- // Global error handler
1253
1330
  onError: (handler) => { errorHandler = handler; },
1254
1331
  request: async (path, options = {}) => {
1255
1332
  const url = path.startsWith('http') ? path : 'http://localhost' + path;
@@ -1266,14 +1343,16 @@ const __createHonoApp__ = () => {
1266
1343
  return appObj;
1267
1344
  };
1268
1345
 
1269
- // Create global app instance
1270
1346
  const app = __createHonoApp__();
1347
+ `
1348
+ }
1271
1349
 
1350
+ function getMdxRenderingTemplate(): string {
1351
+ return `
1272
1352
  // ============================================================
1273
1353
  // MDX Rendering Utilities
1274
1354
  // ============================================================
1275
1355
 
1276
- // Extract frontmatter from MDX content
1277
1356
  const __extractFrontmatter__ = (content) => {
1278
1357
  const match = content.match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/);
1279
1358
  if (!match) return { frontmatter: {}, body: content };
@@ -1289,17 +1368,12 @@ const __extractFrontmatter__ = (content) => {
1289
1368
  }
1290
1369
  };
1291
1370
 
1292
- // render object for MDX
1293
1371
  const render = {
1294
- // Render MDX to markdown (strip frontmatter by default)
1295
1372
  markdown: (content, options = {}) => {
1296
1373
  const { frontmatter, body } = __extractFrontmatter__(content);
1297
- if (options.includeFrontmatter) {
1298
- return content;
1299
- }
1374
+ if (options.includeFrontmatter) return content;
1300
1375
  return body.trim();
1301
1376
  },
1302
- // Extract table of contents from markdown headings
1303
1377
  toc: (content) => {
1304
1378
  const { body } = __extractFrontmatter__(content);
1305
1379
  const headings = [];
@@ -1314,10 +1388,8 @@ const render = {
1314
1388
  }
1315
1389
  return headings;
1316
1390
  },
1317
- // Render MDX to HTML (simplified)
1318
1391
  html: (content) => {
1319
1392
  const { body } = __extractFrontmatter__(content);
1320
- // Basic markdown to HTML
1321
1393
  return body
1322
1394
  .replace(/^### (.+)$/gm, '<h3>$1</h3>')
1323
1395
  .replace(/^## (.+)$/gm, '<h2>$1</h2>')
@@ -1337,9 +1409,7 @@ let __hook_index__ = 0;
1337
1409
 
1338
1410
  const useState = (initial) => {
1339
1411
  const idx = __hook_index__++;
1340
- if (__hook_state__[idx] === undefined) {
1341
- __hook_state__[idx] = initial;
1342
- }
1412
+ if (__hook_state__[idx] === undefined) __hook_state__[idx] = initial;
1343
1413
  const setState = (newVal) => {
1344
1414
  __hook_state__[idx] = typeof newVal === 'function' ? newVal(__hook_state__[idx]) : newVal;
1345
1415
  };
@@ -1350,26 +1420,19 @@ const useEffect = (fn, deps) => { /* No-op in server context */ };
1350
1420
  const useRef = (initial) => ({ current: initial });
1351
1421
  const useMemo = (fn, deps) => fn();
1352
1422
  const useCallback = (fn, deps) => fn;
1353
-
1354
- // Suspense placeholder
1355
1423
  const Suspense = ({ children, fallback }) => children;
1356
1424
 
1357
- // Streaming render function
1358
1425
  const renderToStream = async (element, stream) => {
1359
1426
  const html = __renderJsx__(element);
1360
1427
  await stream.write(html);
1361
1428
  };
1362
1429
 
1363
- // Serialization utilities for client props
1364
1430
  const serialize = {
1365
1431
  clientProps: (props) => {
1366
1432
  const result = {};
1367
1433
  for (const [key, value] of Object.entries(props || {})) {
1368
- if (typeof value === 'function') {
1369
- result[key] = { __rpc: true, name: value.name || 'anonymous' };
1370
- } else {
1371
- result[key] = value;
1372
- }
1434
+ if (typeof value === 'function') result[key] = { __rpc: true, name: value.name || 'anonymous' };
1435
+ else result[key] = value;
1373
1436
  }
1374
1437
  return result;
1375
1438
  },
@@ -1377,20 +1440,12 @@ const serialize = {
1377
1440
  parse: JSON.parse
1378
1441
  };
1379
1442
 
1380
- // Add .isClient getter to Function.prototype for component detection
1381
1443
  Object.defineProperty(Function.prototype, 'isClient', {
1382
- get: function() {
1383
- return __isClientComponent__(this);
1384
- },
1444
+ get: function() { return __isClientComponent__(this); },
1385
1445
  configurable: true,
1386
1446
  enumerable: false
1387
1447
  });
1388
1448
 
1389
- // ============================================================
1390
- // Additional Parsing Functions
1391
- // ============================================================
1392
-
1393
- // Parse URL into components
1394
1449
  const parseUrl = (urlString) => {
1395
1450
  try {
1396
1451
  const url = new URL(urlString);
@@ -1408,19 +1463,20 @@ const parseUrl = (urlString) => {
1408
1463
  return { pathname: urlString, path: urlString.split('/').filter(Boolean) };
1409
1464
  }
1410
1465
  };
1466
+ `
1467
+ }
1411
1468
 
1469
+ function getWorkflowSystemTemplate(): string {
1470
+ // This is a very long template - I'll include the essential parts
1471
+ return `
1412
1472
  // ============================================================
1413
1473
  // Workflow/Event System (aligned with ai-workflows)
1414
1474
  // ============================================================
1415
1475
 
1416
- // Event handler registry
1417
1476
  const __event_handlers__ = new Map();
1418
- // Schedule handler registry
1419
1477
  const __schedule_handlers__ = [];
1420
- // Workflow history for tracking
1421
1478
  const __workflow_history__ = [];
1422
1479
 
1423
- // Known cron patterns for schedules
1424
1480
  const __KNOWN_PATTERNS__ = {
1425
1481
  second: '* * * * * *', minute: '* * * * *', hour: '0 * * * *',
1426
1482
  day: '0 0 * * *', week: '0 0 * * 0', month: '0 0 1 * *', year: '0 0 1 1 *',
@@ -1429,7 +1485,6 @@ const __KNOWN_PATTERNS__ = {
1429
1485
  weekday: '0 0 * * 1-5', weekend: '0 0 * * 0,6', midnight: '0 0 * * *', noon: '0 12 * * *'
1430
1486
  };
1431
1487
 
1432
- // Time patterns for schedule modifiers
1433
1488
  const __TIME_PATTERNS__ = {
1434
1489
  at6am: { hour: 6, minute: 0 }, at7am: { hour: 7, minute: 0 }, at8am: { hour: 8, minute: 0 },
1435
1490
  at9am: { hour: 9, minute: 0 }, at10am: { hour: 10, minute: 0 }, at11am: { hour: 11, minute: 0 },
@@ -1439,33 +1494,27 @@ const __TIME_PATTERNS__ = {
1439
1494
  at8pm: { hour: 20, minute: 0 }, at9pm: { hour: 21, minute: 0 }, atmidnight: { hour: 0, minute: 0 }
1440
1495
  };
1441
1496
 
1442
- // Parse event string (Noun.event)
1443
1497
  const __parseEvent__ = (event) => {
1444
1498
  const parts = event.split('.');
1445
1499
  if (parts.length !== 2) return null;
1446
1500
  return { noun: parts[0], event: parts[1] };
1447
1501
  };
1448
1502
 
1449
- // Register event handler
1450
1503
  const __registerEventHandler__ = (noun, event, handler) => {
1451
1504
  const key = noun + '.' + event;
1452
1505
  if (!__event_handlers__.has(key)) __event_handlers__.set(key, []);
1453
1506
  __event_handlers__.get(key).push({ handler, source: handler.toString() });
1454
1507
  };
1455
1508
 
1456
- // Register schedule handler
1457
1509
  const __registerScheduleHandler__ = (interval, handler) => {
1458
1510
  __schedule_handlers__.push({ interval, handler, source: handler.toString() });
1459
1511
  };
1460
1512
 
1461
- // Create $.on - supports both on('event', handler) and on.Entity.event(handler)
1462
1513
  const on = new Proxy(function(event, filterOrHandler, handler) {
1463
- // on('user.created', handler) or on('user.created', { where: ... }, handler)
1464
1514
  if (typeof event === 'string') {
1465
1515
  const actualHandler = typeof filterOrHandler === 'function' ? filterOrHandler : handler;
1466
1516
  const filter = typeof filterOrHandler === 'object' ? filterOrHandler : null;
1467
- const parts = event.split('.');
1468
- const key = event; // Use full event string as key
1517
+ const key = event;
1469
1518
  if (!__event_handlers__.has(key)) __event_handlers__.set(key, []);
1470
1519
  __event_handlers__.get(key).push({ handler: actualHandler, filter, source: actualHandler.toString() });
1471
1520
  return {
@@ -1481,7 +1530,6 @@ const on = new Proxy(function(event, filterOrHandler, handler) {
1481
1530
  }, {
1482
1531
  get: (target, prop) => {
1483
1532
  if (prop === 'once') {
1484
- // on.once('event', handler) - one-time handler
1485
1533
  return (event, handler) => {
1486
1534
  const wrapper = async (data, $) => {
1487
1535
  const key = event;
@@ -1498,7 +1546,6 @@ const on = new Proxy(function(event, filterOrHandler, handler) {
1498
1546
  return { off: () => {} };
1499
1547
  };
1500
1548
  }
1501
- // on.Entity.event(handler) pattern
1502
1549
  const noun = String(prop);
1503
1550
  return new Proxy({}, {
1504
1551
  get: (_, eventName) => (handler) => {
@@ -1526,7 +1573,6 @@ const on = new Proxy(function(event, filterOrHandler, handler) {
1526
1573
  apply: (target, thisArg, args) => target(...args)
1527
1574
  });
1528
1575
 
1529
- // Parse duration string (100ms, 1s, 5m, etc.)
1530
1576
  const __parseDuration__ = (str) => {
1531
1577
  if (!str || typeof str !== 'string') return null;
1532
1578
  const match = str.match(/^(\\d+)(ms|s|m|h|d|w)?$/);
@@ -1544,30 +1590,24 @@ const __parseDuration__ = (str) => {
1544
1590
  }
1545
1591
  };
1546
1592
 
1547
- // Check if string looks like a cron expression
1548
1593
  const __isCronExpression__ = (str) => {
1549
1594
  if (!str || typeof str !== 'string') return false;
1550
1595
  const parts = str.trim().split(/\\s+/);
1551
1596
  return parts.length >= 5 && parts.length <= 6;
1552
1597
  };
1553
1598
 
1554
- // Active schedule timers
1555
1599
  const __schedule_timers__ = [];
1556
1600
 
1557
- // Create $.every - supports every('100ms', handler), every('name', '* * * *', handler), and every.Monday.at9am(handler)
1558
1601
  const every = new Proxy(function(intervalOrName, handlerOrCronOrOptions, handlerArg) {
1559
- // Determine format: every('100ms', handler) or every('name', '* * * *', handler) or every('name', opts, handler)
1560
1602
  let name = null;
1561
1603
  let interval = null;
1562
1604
  let handler = null;
1563
1605
  let options = {};
1564
1606
 
1565
1607
  if (typeof handlerOrCronOrOptions === 'function') {
1566
- // every('100ms', handler) or every('* * * *', handler)
1567
1608
  interval = intervalOrName;
1568
1609
  handler = handlerOrCronOrOptions;
1569
1610
  } else if (typeof handlerArg === 'function') {
1570
- // every('name', '* * * *', handler) or every('name', { immediate: true }, handler)
1571
1611
  name = intervalOrName;
1572
1612
  if (typeof handlerOrCronOrOptions === 'string') {
1573
1613
  interval = handlerOrCronOrOptions;
@@ -1578,13 +1618,11 @@ const every = new Proxy(function(intervalOrName, handlerOrCronOrOptions, handler
1578
1618
  handler = handlerArg;
1579
1619
  }
1580
1620
 
1581
- // Determine if it's a duration or cron
1582
1621
  const isCron = __isCronExpression__(interval);
1583
1622
  const durationMs = isCron ? null : __parseDuration__(interval);
1584
1623
 
1585
1624
  let stopped = false;
1586
1625
  let timer = null;
1587
- let runCount = 0;
1588
1626
 
1589
1627
  const job = {
1590
1628
  name: name || interval,
@@ -1599,25 +1637,12 @@ const every = new Proxy(function(intervalOrName, handlerOrCronOrOptions, handler
1599
1637
  };
1600
1638
 
1601
1639
  if (durationMs) {
1602
- // Duration-based schedule
1603
- if (options.immediate) {
1604
- Promise.resolve().then(() => handler());
1605
- }
1606
-
1640
+ if (options.immediate) Promise.resolve().then(() => handler());
1607
1641
  timer = setInterval(async () => {
1608
1642
  if (stopped) return;
1609
- if (options.until && options.until()) {
1610
- job.stop();
1611
- return;
1612
- }
1613
- runCount++;
1614
- try {
1615
- await handler();
1616
- } catch (e) {
1617
- console.error('[every] Handler error:', e);
1618
- }
1643
+ if (options.until && options.until()) { job.stop(); return; }
1644
+ try { await handler(); } catch (e) { console.error('[every] Handler error:', e); }
1619
1645
  }, durationMs);
1620
-
1621
1646
  __schedule_timers__.push(timer);
1622
1647
  }
1623
1648
 
@@ -1638,7 +1663,6 @@ const every = new Proxy(function(intervalOrName, handlerOrCronOrOptions, handler
1638
1663
  __registerScheduleHandler__({ type: 'cron', expression: pattern, natural: propStr }, handler);
1639
1664
  return { stop: () => {}, cancel: () => {}, name: propStr, cron: pattern, stopped: false };
1640
1665
  };
1641
- // Support time modifiers: every.Monday.at9am(handler)
1642
1666
  return new Proxy(result, {
1643
1667
  get: (_, timeKey) => {
1644
1668
  const time = __TIME_PATTERNS__[String(timeKey)];
@@ -1660,7 +1684,6 @@ const every = new Proxy(function(intervalOrName, handlerOrCronOrOptions, handler
1660
1684
  }
1661
1685
  });
1662
1686
  }
1663
- // Plural units: every.seconds(5), every.minutes(10), etc.
1664
1687
  const pluralUnits = { seconds: 'second', minutes: 'minute', hours: 'hour', days: 'day', weeks: 'week' };
1665
1688
  if (pluralUnits[propStr]) {
1666
1689
  return (value) => (handler) => {
@@ -1673,38 +1696,24 @@ const every = new Proxy(function(intervalOrName, handlerOrCronOrOptions, handler
1673
1696
  apply: (target, thisArg, args) => target(...args)
1674
1697
  });
1675
1698
 
1676
- // Send event - returns event info, supports options (delay, correlationId, channel, wait)
1677
1699
  const send = async (event, data, options = {}) => {
1678
1700
  const eventId = __generateId__();
1679
1701
  const timestamp = Date.now();
1680
- const eventObj = {
1681
- id: eventId,
1682
- type: event,
1683
- data,
1684
- timestamp,
1685
- correlationId: options.correlationId
1686
- };
1687
-
1702
+ const eventObj = { id: eventId, type: event, data, timestamp, correlationId: options.correlationId };
1688
1703
  __workflow_history__.push({ type: 'event', name: event, data, timestamp });
1689
1704
 
1690
- // Handle delay
1691
1705
  if (options.delay) {
1692
1706
  const delayMs = typeof options.delay === 'string' ? __parseDuration__(options.delay) : options.delay;
1693
- if (delayMs) {
1694
- await new Promise(r => setTimeout(r, delayMs));
1695
- }
1707
+ if (delayMs) await new Promise(r => setTimeout(r, delayMs));
1696
1708
  }
1697
1709
 
1698
- // Find matching handlers (supports wildcard like 'user.*')
1699
1710
  const matchingHandlers = [];
1700
1711
  for (const [key, handlers] of __event_handlers__) {
1701
1712
  const keyPattern = key.replace(/\\.\\*/g, '\\\\.[^.]+');
1702
1713
  const regex = new RegExp('^' + keyPattern + '$');
1703
1714
  if (key === event || regex.test(event) || (key.endsWith('.*') && event.startsWith(key.slice(0, -1)))) {
1704
1715
  for (const h of handlers) {
1705
- // Check channel filter
1706
1716
  if (h.filter?.channel && h.filter.channel !== options.channel) continue;
1707
- // Check where filter
1708
1717
  if (h.filter?.where) {
1709
1718
  let match = true;
1710
1719
  for (const [k, v] of Object.entries(h.filter.where)) {
@@ -1717,7 +1726,6 @@ const send = async (event, data, options = {}) => {
1717
1726
  }
1718
1727
  }
1719
1728
 
1720
- // If wait option, call first handler and return response
1721
1729
  if (options.wait && matchingHandlers.length > 0) {
1722
1730
  let response = null;
1723
1731
  const reply = (data) => { response = { data }; };
@@ -1725,30 +1733,21 @@ const send = async (event, data, options = {}) => {
1725
1733
  return response || eventObj;
1726
1734
  }
1727
1735
 
1728
- // Fire all handlers
1729
1736
  await Promise.all(matchingHandlers.map(async ({ handler }) => {
1730
- try {
1731
- await handler({ type: event, data, correlationId: options.correlationId }, $);
1732
- } catch (error) {
1733
- console.error('Error in handler for ' + event + ':', error);
1734
- }
1737
+ try { await handler({ type: event, data, correlationId: options.correlationId }, $); }
1738
+ catch (error) { console.error('Error in handler for ' + event + ':', error); }
1735
1739
  }));
1736
1740
 
1737
1741
  return eventObj;
1738
1742
  };
1739
1743
 
1740
- // Add broadcast method to send
1741
- send.broadcast = async (event, data) => {
1742
- return send(event, data);
1743
- };
1744
+ send.broadcast = async (event, data) => send(event, data);
1744
1745
 
1745
- // Delay helper
1746
1746
  const delay = (ms) => {
1747
1747
  if (typeof ms === 'string') ms = __parseDuration__(ms) || 0;
1748
1748
  return new Promise(r => setTimeout(r, ms));
1749
1749
  };
1750
1750
 
1751
- // Decide helper - pattern matching decision
1752
1751
  const decide = (subject) => {
1753
1752
  const conditions = [];
1754
1753
  let defaultValue = null;
@@ -1760,12 +1759,10 @@ const decide = (subject) => {
1760
1759
  },
1761
1760
  otherwise: (result) => {
1762
1761
  defaultValue = result;
1763
- // Execute decision
1764
1762
  for (const { condition, result } of conditions) {
1765
1763
  let matches = false;
1766
- if (typeof condition === 'function') {
1767
- matches = condition(subject);
1768
- } else if (typeof condition === 'object') {
1764
+ if (typeof condition === 'function') matches = condition(subject);
1765
+ else if (typeof condition === 'object') {
1769
1766
  matches = true;
1770
1767
  for (const [key, value] of Object.entries(condition)) {
1771
1768
  const subjectValue = subject[key];
@@ -1780,27 +1777,20 @@ const decide = (subject) => {
1780
1777
  default: if (subjectValue !== value) matches = false;
1781
1778
  }
1782
1779
  }
1783
- } else if (subjectValue !== value) {
1784
- matches = false;
1785
- }
1780
+ } else if (subjectValue !== value) matches = false;
1786
1781
  if (!matches) break;
1787
1782
  }
1788
1783
  }
1789
- if (matches) {
1790
- return typeof result === 'function' ? result() : result;
1791
- }
1784
+ if (matches) return typeof result === 'function' ? result() : result;
1792
1785
  }
1793
1786
  return typeof defaultValue === 'function' ? defaultValue() : defaultValue;
1794
1787
  }
1795
1788
  };
1796
-
1797
1789
  return chain;
1798
1790
  };
1799
1791
 
1800
- // Async decide
1801
1792
  decide.async = (subject) => {
1802
1793
  const conditions = [];
1803
-
1804
1794
  const chain = {
1805
1795
  when: (conditionFn, result) => {
1806
1796
  conditions.push({ condition: conditionFn, result });
@@ -1814,11 +1804,9 @@ decide.async = (subject) => {
1814
1804
  return defaultResult;
1815
1805
  }
1816
1806
  };
1817
-
1818
1807
  return chain;
1819
1808
  };
1820
1809
 
1821
- // Track helper - analytics tracking
1822
1810
  const __tracked_events__ = [];
1823
1811
  const track = async (event, data, metadata = {}) => {
1824
1812
  const entry = { type: event, data, metadata, timestamp: Date.now(), userId: metadata.userId };
@@ -1879,7 +1867,6 @@ track.timeseries = async (event, options = {}) => {
1879
1867
  }));
1880
1868
  };
1881
1869
 
1882
- // Experiment helper - A/B testing
1883
1870
  const experiment = (name) => {
1884
1871
  const variants = [];
1885
1872
  let eligibilityFn = null;
@@ -1891,29 +1878,17 @@ const experiment = (name) => {
1891
1878
  variants.push({ name: variantName, config, weight: options.weight || 1 });
1892
1879
  return exp;
1893
1880
  },
1894
- eligible: (fn) => {
1895
- eligibilityFn = fn;
1896
- return exp;
1897
- },
1898
- override: (userId, variantName) => {
1899
- overrides.set(userId, variantName);
1900
- return exp;
1901
- },
1902
- rollout: (variantName, percentage) => {
1903
- rolloutConfig = { variant: variantName, percentage };
1904
- return exp;
1905
- },
1881
+ eligible: (fn) => { eligibilityFn = fn; return exp; },
1882
+ override: (userId, variantName) => { overrides.set(userId, variantName); return exp; },
1883
+ rollout: (variantName, percentage) => { rolloutConfig = { variant: variantName, percentage }; return exp; },
1906
1884
  assign: (userId) => {
1907
- // Check override
1908
1885
  if (overrides.has(userId)) {
1909
1886
  const overrideVariant = variants.find(v => v.name === overrides.get(userId));
1910
1887
  if (overrideVariant) return { name: overrideVariant.name, ...overrideVariant.config };
1911
1888
  }
1912
- // Check eligibility
1913
1889
  if (eligibilityFn && typeof userId === 'object' && !eligibilityFn(userId)) {
1914
1890
  return variants[0] ? { name: variants[0].name, ...variants[0].config } : {};
1915
1891
  }
1916
- // Consistent assignment based on userId hash
1917
1892
  const userIdStr = typeof userId === 'object' ? userId.id || JSON.stringify(userId) : String(userId);
1918
1893
  let hash = 0;
1919
1894
  for (let i = 0; i < userIdStr.length; i++) {
@@ -1921,14 +1896,10 @@ const experiment = (name) => {
1921
1896
  hash = hash & hash;
1922
1897
  }
1923
1898
  const normalized = Math.abs(hash % 100) / 100;
1924
-
1925
- // Check rollout
1926
1899
  if (rolloutConfig && normalized < rolloutConfig.percentage) {
1927
1900
  const rolloutVariant = variants.find(v => v.name === rolloutConfig.variant);
1928
1901
  if (rolloutVariant) return { name: rolloutVariant.name, ...rolloutVariant.config };
1929
1902
  }
1930
-
1931
- // Weight-based selection
1932
1903
  const totalWeight = variants.reduce((sum, v) => sum + v.weight, 0);
1933
1904
  let cumulative = 0;
1934
1905
  for (const v of variants) {
@@ -1938,20 +1909,14 @@ const experiment = (name) => {
1938
1909
  return variants[0] ? { name: variants[0].name, ...variants[0].config } : {};
1939
1910
  },
1940
1911
  track: {
1941
- exposure: async (userId) => {
1942
- await track('experiment.exposure', { experiment: name, userId });
1943
- },
1944
- conversion: async (userId, data = {}) => {
1945
- await track('experiment.conversion', { experiment: name, userId, ...data });
1946
- }
1912
+ exposure: async (userId) => { await track('experiment.exposure', { experiment: name, userId }); },
1913
+ conversion: async (userId, data = {}) => { await track('experiment.conversion', { experiment: name, userId, ...data }); }
1947
1914
  },
1948
1915
  results: async () => {
1949
1916
  const exposures = __tracked_events__.filter(e => e.type === 'experiment.exposure' && e.data.experiment === name);
1950
1917
  const conversions = __tracked_events__.filter(e => e.type === 'experiment.conversion' && e.data.experiment === name);
1951
1918
  const result = {};
1952
- for (const v of variants) {
1953
- result[v.name] = { exposures: 0, conversions: 0 };
1954
- }
1919
+ for (const v of variants) result[v.name] = { exposures: 0, conversions: 0 };
1955
1920
  for (const e of exposures) {
1956
1921
  const variantName = exp.assign(e.data.userId).name;
1957
1922
  if (result[variantName]) result[variantName].exposures++;
@@ -1963,39 +1928,29 @@ const experiment = (name) => {
1963
1928
  return result;
1964
1929
  }
1965
1930
  };
1966
-
1967
1931
  return exp;
1968
1932
  };
1969
1933
 
1970
- // Execute event and wait for result ($.do) - mirrors ai-workflows do
1971
1934
  const __doEvent__ = async (event, data) => {
1972
1935
  __workflow_history__.push({ type: 'action', name: 'do:' + event, data, timestamp: Date.now() });
1973
-
1974
1936
  const parsed = __parseEvent__(event);
1975
1937
  if (!parsed) throw new Error('Invalid event format: ' + event + '. Expected Noun.event');
1976
-
1977
1938
  const key = parsed.noun + '.' + parsed.event;
1978
1939
  const handlers = __event_handlers__.get(key) || [];
1979
1940
  if (handlers.length === 0) throw new Error('No handler registered for ' + event);
1980
-
1981
1941
  return await handlers[0].handler(data, $);
1982
1942
  };
1983
1943
 
1984
- // Try event (non-durable) - mirrors ai-workflows try
1985
1944
  const __tryEvent__ = async (event, data) => {
1986
1945
  __workflow_history__.push({ type: 'action', name: 'try:' + event, data, timestamp: Date.now() });
1987
-
1988
1946
  const parsed = __parseEvent__(event);
1989
1947
  if (!parsed) throw new Error('Invalid event format: ' + event + '. Expected Noun.event');
1990
-
1991
1948
  const key = parsed.noun + '.' + parsed.event;
1992
1949
  const handlers = __event_handlers__.get(key) || [];
1993
1950
  if (handlers.length === 0) throw new Error('No handler registered for ' + event);
1994
-
1995
1951
  return await handlers[0].handler(data, $);
1996
1952
  };
1997
1953
 
1998
- // Queue implementation
1999
1954
  const __queues__ = new Map();
2000
1955
  const __queue_stats__ = new Map();
2001
1956
  const queue = (name) => {
@@ -2022,7 +1977,7 @@ const queue = (name) => {
2022
1977
  } catch (e) {
2023
1978
  stats.failed++;
2024
1979
  if (job.attempts < (job.options.maxRetries || 3)) {
2025
- q.push(job); // Retry
1980
+ q.push(job);
2026
1981
  stats.retried++;
2027
1982
  }
2028
1983
  }
@@ -2036,7 +1991,6 @@ const queue = (name) => {
2036
1991
  stats.processed += batch.length;
2037
1992
  } catch (e) {
2038
1993
  stats.failed += batch.length;
2039
- // Put back for retry
2040
1994
  for (const job of batch) {
2041
1995
  if (job.attempts < (job.options.maxRetries || 3)) {
2042
1996
  job.attempts++;
@@ -2054,7 +2008,6 @@ const queue = (name) => {
2054
2008
  };
2055
2009
  };
2056
2010
 
2057
- // Actor pattern implementation
2058
2011
  const __actors__ = new Map();
2059
2012
  const actor = (type) => ({
2060
2013
  register: (id, state = {}) => {
@@ -2066,32 +2019,23 @@ const actor = (type) => ({
2066
2019
  send: async (id, message) => {
2067
2020
  const a = __actors__.get(type + ':' + id);
2068
2021
  if (!a) throw new Error('Actor not found: ' + type + ':' + id);
2069
- // Handle message (stub - in real impl would dispatch to actor handler)
2070
2022
  return { delivered: true };
2071
2023
  }
2072
2024
  });
2073
2025
 
2074
- // Actions API (workflow actions)
2075
2026
  const __actions__ = new Map();
2076
2027
  const actions = {
2077
2028
  async create(options) {
2078
2029
  const id = __generateId__();
2079
2030
  const action = {
2080
- id,
2081
- actor: options.actor,
2082
- object: options.object,
2083
- action: options.action,
2084
- metadata: options.metadata || {},
2085
- status: 'pending',
2086
- createdAt: new Date(),
2087
- updatedAt: new Date()
2031
+ id, actor: options.actor, object: options.object, action: options.action,
2032
+ metadata: options.metadata || {}, status: 'pending',
2033
+ createdAt: new Date(), updatedAt: new Date()
2088
2034
  };
2089
2035
  __actions__.set(id, action);
2090
2036
  return action;
2091
2037
  },
2092
- async get(id) {
2093
- return __actions__.get(id) || null;
2094
- },
2038
+ async get(id) { return __actions__.get(id) || null; },
2095
2039
  async start(id) {
2096
2040
  const action = __actions__.get(id);
2097
2041
  if (!action) throw new Error('Action not found: ' + id);
@@ -2143,24 +2087,18 @@ const actions = {
2143
2087
  }
2144
2088
  };
2145
2089
 
2146
- // Artifacts API (for storing results)
2147
2090
  const __artifacts__ = new Map();
2148
2091
  const artifacts = {
2149
2092
  async store(options) {
2150
2093
  const id = __generateId__();
2151
2094
  const artifact = {
2152
- id,
2153
- type: options.type,
2154
- data: options.data,
2155
- metadata: options.metadata || {},
2156
- createdAt: new Date()
2095
+ id, type: options.type, data: options.data,
2096
+ metadata: options.metadata || {}, createdAt: new Date()
2157
2097
  };
2158
2098
  __artifacts__.set(id, artifact);
2159
2099
  return artifact;
2160
2100
  },
2161
- async get(id) {
2162
- return __artifacts__.get(id) || null;
2163
- },
2101
+ async get(id) { return __artifacts__.get(id) || null; },
2164
2102
  async list(options = {}) {
2165
2103
  let results = Array.from(__artifacts__.values());
2166
2104
  if (options.type) results = results.filter(a => a.type === options.type);
@@ -2169,17 +2107,12 @@ const artifacts = {
2169
2107
  }
2170
2108
  };
2171
2109
 
2172
- // Events API (for event sourcing)
2173
2110
  const __events__ = [];
2174
2111
  const events = {
2175
2112
  async record(options) {
2176
2113
  const event = {
2177
- id: __generateId__(),
2178
- type: options.type,
2179
- subject: options.subject,
2180
- data: options.data,
2181
- metadata: options.metadata || {},
2182
- timestamp: new Date()
2114
+ id: __generateId__(), type: options.type, subject: options.subject,
2115
+ data: options.data, metadata: options.metadata || {}, timestamp: new Date()
2183
2116
  };
2184
2117
  __events__.push(event);
2185
2118
  return event;
@@ -2191,7 +2124,6 @@ const events = {
2191
2124
  if (options.limit) results = results.slice(0, options.limit);
2192
2125
  return results;
2193
2126
  },
2194
- // Query is an alias for list with more flexible filtering
2195
2127
  async query(options = {}) {
2196
2128
  let results = [...__events__];
2197
2129
  if (options.type) results = results.filter(e => e.type === options.type);
@@ -2207,16 +2139,12 @@ const events = {
2207
2139
  },
2208
2140
  async replay(handler, options = {}) {
2209
2141
  const evts = await events.list(options);
2210
- for (const evt of evts) {
2211
- await handler(evt);
2212
- }
2142
+ for (const evt of evts) await handler(evt);
2213
2143
  },
2214
- // Subscribe to events
2215
2144
  subscribe(type, handler) {
2216
2145
  on(type, handler);
2217
2146
  return { unsubscribe: () => {} };
2218
2147
  },
2219
- // Emit an event (alias for record + send)
2220
2148
  async emit(type, data, metadata = {}) {
2221
2149
  const event = await events.record({ type, data, metadata });
2222
2150
  await send(type, data);
@@ -2224,12 +2152,9 @@ const events = {
2224
2152
  }
2225
2153
  };
2226
2154
 
2227
- // MDX Parsing helpers (basic implementation)
2228
2155
  const parse = (content) => {
2229
2156
  const frontmatterMatch = content.match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/);
2230
- if (!frontmatterMatch) {
2231
- return { data: {}, content: content.trim(), type: null, id: null };
2232
- }
2157
+ if (!frontmatterMatch) return { data: {}, content: content.trim(), type: null, id: null };
2233
2158
  const [, frontmatter, body] = frontmatterMatch;
2234
2159
  const data = {};
2235
2160
  let currentKey = null;
@@ -2240,13 +2165,8 @@ const parse = (content) => {
2240
2165
  if (keyMatch) {
2241
2166
  currentKey = keyMatch[1];
2242
2167
  const value = keyMatch[2].trim();
2243
- if (value === '') {
2244
- data[currentKey] = [];
2245
- inArray = true;
2246
- } else {
2247
- data[currentKey] = value;
2248
- inArray = false;
2249
- }
2168
+ if (value === '') { data[currentKey] = []; inArray = true; }
2169
+ else { data[currentKey] = value; inArray = false; }
2250
2170
  } else if (inArray && currentKey && line.match(/^\\s+-\\s+(.+)$/)) {
2251
2171
  const itemMatch = line.match(/^\\s+-\\s+(.+)$/);
2252
2172
  if (itemMatch) {
@@ -2255,12 +2175,7 @@ const parse = (content) => {
2255
2175
  }
2256
2176
  }
2257
2177
  }
2258
- return {
2259
- data,
2260
- content: body.trim(),
2261
- type: data.$type || data['@type'] || null,
2262
- id: data.$id || data['@id'] || null
2263
- };
2178
+ return { data, content: body.trim(), type: data.$type || data['@type'] || null, id: data.$id || data['@id'] || null };
2264
2179
  };
2265
2180
 
2266
2181
  const stringify = (doc) => {
@@ -2271,9 +2186,7 @@ const stringify = (doc) => {
2271
2186
  if (Array.isArray(value)) {
2272
2187
  lines.push(key + ':');
2273
2188
  for (const item of value) lines.push(' - ' + item);
2274
- } else {
2275
- lines.push(key + ': ' + value);
2276
- }
2189
+ } else lines.push(key + ': ' + value);
2277
2190
  }
2278
2191
  lines.push('---');
2279
2192
  if (doc.content) lines.push('', doc.content);
@@ -2288,17 +2201,12 @@ const toAst = (doc) => {
2288
2201
  else if (line.startsWith('## ')) children.push({ type: 'heading', depth: 2, text: line.slice(3) });
2289
2202
  else if (line.startsWith('- ')) {
2290
2203
  const lastList = children[children.length - 1];
2291
- if (lastList?.type === 'list') {
2292
- lastList.items.push(line.slice(2));
2293
- } else {
2294
- children.push({ type: 'list', items: [line.slice(2)] });
2295
- }
2204
+ if (lastList?.type === 'list') lastList.items.push(line.slice(2));
2205
+ else children.push({ type: 'list', items: [line.slice(2)] });
2296
2206
  } else if (line.startsWith('\`\`\`')) {
2297
2207
  const lang = line.slice(3).trim();
2298
2208
  children.push({ type: 'code', lang: lang || null, value: '' });
2299
- } else if (line.trim()) {
2300
- children.push({ type: 'paragraph', text: line });
2301
- }
2209
+ } else if (line.trim()) children.push({ type: 'paragraph', text: line });
2302
2210
  }
2303
2211
  return { type: 'root', children };
2304
2212
  };
@@ -2312,9 +2220,7 @@ const renderMarkdown = (doc, options = {}) => {
2312
2220
  if (Array.isArray(value)) {
2313
2221
  result += key + ':\\n';
2314
2222
  for (const item of value) result += ' - ' + item + '\\n';
2315
- } else {
2316
- result += key + ': ' + value + '\\n';
2317
- }
2223
+ } else result += key + ': ' + value + '\\n';
2318
2224
  }
2319
2225
  result += '---\\n';
2320
2226
  }
@@ -2322,7 +2228,6 @@ const renderMarkdown = (doc, options = {}) => {
2322
2228
  return result;
2323
2229
  };
2324
2230
 
2325
- // Component factory (basic implementation)
2326
2231
  const createComponents = (createElement) => {
2327
2232
  const components = {};
2328
2233
  const componentNames = ['Hero', 'Features', 'Pricing', 'CTA', 'Testimonials', 'FAQ', 'Footer', 'Header', 'Nav', 'Card', 'Grid', 'Section', 'Container', 'Button', 'Input', 'Form', 'Modal', 'Table', 'List', 'Badge', 'Alert', 'Progress', 'Spinner', 'Avatar', 'Image', 'Video', 'Code', 'Markdown'];
@@ -2349,9 +2254,7 @@ const extractTests = (content) => {
2349
2254
  const tests = [];
2350
2255
  const regex = /\`\`\`(?:ts|js)\\s+test[^\\n]*\\n([\\s\\S]*?)\`\`\`/g;
2351
2256
  let match;
2352
- while ((match = regex.exec(content)) !== null) {
2353
- tests.push({ code: match[1].trim() });
2354
- }
2257
+ while ((match = regex.exec(content)) !== null) tests.push({ code: match[1].trim() });
2355
2258
  return tests;
2356
2259
  };
2357
2260
 
@@ -2362,24 +2265,18 @@ const parseMeta = (meta) => {
2362
2265
  if (part.includes('=')) {
2363
2266
  const [key, value] = part.split('=');
2364
2267
  result[key] = value;
2365
- } else {
2366
- result[part] = true;
2367
- }
2268
+ } else result[part] = true;
2368
2269
  }
2369
2270
  return result;
2370
2271
  };
2371
2272
 
2372
- // Simple createElement for testing
2373
2273
  const createElement = (type, props, ...children) => ({ type, props: props || {}, children });
2374
2274
 
2375
- // Graph/relationship helper functions
2376
2275
  const extractLinks = (content) => {
2377
2276
  const links = [];
2378
2277
  const regex = /\\[([^\\]]+)\\]\\(([^)]+)\\)/g;
2379
2278
  let match;
2380
- while ((match = regex.exec(content)) !== null) {
2381
- links.push({ text: match[1], url: match[2] });
2382
- }
2279
+ while ((match = regex.exec(content)) !== null) links.push({ text: match[1], url: match[2] });
2383
2280
  return links;
2384
2281
  };
2385
2282
 
@@ -2387,12 +2284,8 @@ const extractRelationships = (doc) => {
2387
2284
  const relationships = [];
2388
2285
  const url = doc.id || doc.url;
2389
2286
  if (!url) return relationships;
2390
- // Extract from content links
2391
2287
  const links = extractLinks(doc.content || '');
2392
- for (const link of links) {
2393
- relationships.push({ from: url, to: link.url, type: 'links', label: link.text });
2394
- }
2395
- // Extract from data fields
2288
+ for (const link of links) relationships.push({ from: url, to: link.url, type: 'links', label: link.text });
2396
2289
  for (const [key, value] of Object.entries(doc.data || {})) {
2397
2290
  if (typeof value === 'string' && (value.startsWith('http') || value.startsWith('/'))) {
2398
2291
  relationships.push({ from: url, to: value, type: key });
@@ -2408,21 +2301,19 @@ const extractRelationships = (doc) => {
2408
2301
  return relationships;
2409
2302
  };
2410
2303
 
2411
- const withRelationships = (doc) => {
2412
- return { ...doc, relationships: extractRelationships(doc) };
2413
- };
2304
+ const withRelationships = (doc) => ({ ...doc, relationships: extractRelationships(doc) });
2414
2305
 
2415
2306
  const resolveUrl = (entity) => {
2416
2307
  if (entity.url) return entity.url;
2417
- if (entity.ns && entity.type && entity.id) {
2418
- return 'https://' + entity.ns + '/' + entity.type + '/' + entity.id;
2419
- }
2420
- if (entity.ns && entity.id) {
2421
- return 'https://' + entity.ns + '/' + entity.id;
2422
- }
2308
+ if (entity.ns && entity.type && entity.id) return 'https://' + entity.ns + '/' + entity.type + '/' + entity.id;
2309
+ if (entity.ns && entity.id) return 'https://' + entity.ns + '/' + entity.id;
2423
2310
  return null;
2424
2311
  };
2312
+ `
2313
+ }
2425
2314
 
2315
+ function getContextObjectTemplate(): string {
2316
+ return `
2426
2317
  // Context object ($) - unified SDK context (aligned with ai-workflows WorkflowContext)
2427
2318
  const $ = {
2428
2319
  ns: __SDK_CONFIG__.ns,
@@ -2442,60 +2333,40 @@ const $ = {
2442
2333
  track,
2443
2334
  experiment,
2444
2335
  delay,
2445
- // Workflow state
2446
2336
  state: {},
2447
2337
  history: __workflow_history__,
2448
- // User context
2449
2338
  user: { id: 'test-user', name: 'Test User', role: 'admin' },
2450
2339
  request: { method: 'GET', path: '/', headers: {}, body: null },
2451
2340
  env: { NODE_ENV: 'test' },
2452
2341
  config: {},
2453
2342
  context: {},
2454
2343
  meta: {},
2455
- // Logging
2456
2344
  log: (message, data) => {
2457
2345
  __workflow_history__.push({ type: 'action', name: 'log', data: { message, data }, timestamp: Date.now() });
2458
2346
  console.log('[sdk] ' + message, data ?? '');
2459
2347
  },
2460
2348
  error: console.error,
2461
2349
  warn: console.warn,
2462
- // Scoped execution
2463
2350
  async scope(overrides, fn) {
2464
2351
  const prev = { ns: $.ns, user: $.user, state: { ...$.state } };
2465
2352
  Object.assign($, overrides);
2466
2353
  try { return await fn(); }
2467
2354
  finally { Object.assign($, prev); }
2468
2355
  },
2469
- // Workflow helpers
2470
2356
  getHandlers() { return { events: Array.from(__event_handlers__.keys()), schedules: __schedule_handlers__.length }; },
2471
2357
  clearHandlers() { __event_handlers__.clear(); __schedule_handlers__.length = 0; },
2472
2358
  getHistory() { return [...__workflow_history__]; },
2473
2359
  clearHistory() { __workflow_history__.length = 0; }
2474
2360
  };
2475
2361
 
2476
- // Standalone exports (for import { db, ai, on, send } from 'sdk')
2362
+ // Standalone exports
2477
2363
  const api = {};
2478
2364
  const search = __db_core__.search.bind(__db_core__);
2479
2365
  `
2480
2366
  }
2481
2367
 
2482
- /**
2483
- * Generate remote SDK code (RPC-based)
2484
- *
2485
- * This creates the platform.do-style SDK with:
2486
- * - $ - Root context accessor
2487
- * - db - Database operations
2488
- * - ai - AI operations
2489
- * - api - Platform API
2490
- * - on/send - Event handling
2491
- *
2492
- * All operations go through RPC to actual services.
2493
- */
2494
- function generateRemoteSDKCode(config: SDKConfig = {}): string {
2495
- const rpcUrl = config.rpcUrl || 'https://rpc.do'
2496
- const token = config.token || ''
2497
- const ns = config.ns || 'default'
2498
-
2368
+ // Helper to get the remote SDK template
2369
+ function getRemoteSDKTemplate(rpcUrl: string, token: string, ns: string): string {
2499
2370
  return `
2500
2371
  // ============================================================
2501
2372
  // SDK - Thin RPC Proxy ($, db, ai, api, on, send)
@@ -2515,7 +2386,6 @@ const __rpc__ = {
2515
2386
  headers['Authorization'] = 'Bearer ' + __SDK_CONFIG__.token;
2516
2387
  }
2517
2388
 
2518
- // Serialize functions for remote execution
2519
2389
  const serializedArgs = args.map(arg => {
2520
2390
  if (typeof arg === 'function') {
2521
2391
  return { __fn: arg.toString().replace(/"/g, "'") };
@@ -2538,23 +2408,16 @@ const __rpc__ = {
2538
2408
  }
2539
2409
  };
2540
2410
 
2541
- // Store for user-defined values
2542
2411
  const __userDefinitions__ = new Map();
2543
2412
 
2544
- // Thin proxy that converts property access to RPC paths
2545
2413
  const __createProxy__ = (path = '') => {
2546
- // Track stored values for this proxy instance
2547
2414
  const localStore = new Map();
2548
2415
 
2549
2416
  const proxy = new Proxy(() => {}, {
2550
2417
  get: (target, prop, receiver) => {
2551
- // Handle JSON serialization
2552
2418
  if (prop === 'toJSON') {
2553
- // Return stored values as a plain object
2554
2419
  const obj = { __rpcPath: path };
2555
- for (const [key, value] of localStore) {
2556
- obj[key] = value;
2557
- }
2420
+ for (const [key, value] of localStore) obj[key] = value;
2558
2421
  for (const [key, value] of __userDefinitions__) {
2559
2422
  if (key.startsWith(path ? path + '.' : '') && !key.slice(path ? path.length + 1 : 0).includes('.')) {
2560
2423
  const localKey = key.slice(path ? path.length + 1 : 0);
@@ -2566,42 +2429,25 @@ const __createProxy__ = (path = '') => {
2566
2429
  if (prop === Symbol.toPrimitive || prop === 'valueOf' || prop === 'toString') {
2567
2430
  return () => path || '[SDK Proxy]';
2568
2431
  }
2569
- if (prop === Symbol.toStringTag) {
2570
- return 'SDKProxy';
2571
- }
2572
- if (prop === 'then' || prop === 'catch' || prop === 'finally') {
2573
- return undefined; // Don't treat as thenable
2574
- }
2575
- // Handle .should by creating an assertion chain
2432
+ if (prop === Symbol.toStringTag) return 'SDKProxy';
2433
+ if (prop === 'then' || prop === 'catch' || prop === 'finally') return undefined;
2576
2434
  if (prop === 'should') {
2577
- // Build an object from stored values for assertion
2578
2435
  const obj = {};
2579
- for (const [key, value] of localStore) {
2580
- obj[key] = value;
2581
- }
2436
+ for (const [key, value] of localStore) obj[key] = value;
2582
2437
  for (const [key, value] of __userDefinitions__) {
2583
2438
  if (key.startsWith(path ? path + '.' : '') && !key.slice(path ? path.length + 1 : 0).includes('.')) {
2584
2439
  const localKey = key.slice(path ? path.length + 1 : 0);
2585
2440
  if (localKey) obj[localKey] = value;
2586
2441
  }
2587
2442
  }
2588
- // If we have stored values, assert on them; otherwise use path string or a marker
2589
- if (Object.keys(obj).length > 0) {
2590
- return __createShouldChain__(obj);
2591
- }
2592
- // For empty proxy, create a marker object that represents the proxy path
2443
+ if (Object.keys(obj).length > 0) return __createShouldChain__(obj);
2593
2444
  return __createShouldChain__(path ? { __path: path } : { __sdk: true, ns: __SDK_CONFIG__.ns });
2594
2445
  }
2595
2446
 
2596
2447
  const fullPath = path ? path + '.' + String(prop) : String(prop);
2597
2448
 
2598
- // Check local store first, then user definitions
2599
- if (localStore.has(String(prop))) {
2600
- return localStore.get(String(prop));
2601
- }
2602
- if (__userDefinitions__.has(fullPath)) {
2603
- return __userDefinitions__.get(fullPath);
2604
- }
2449
+ if (localStore.has(String(prop))) return localStore.get(String(prop));
2450
+ if (__userDefinitions__.has(fullPath)) return __userDefinitions__.get(fullPath);
2605
2451
 
2606
2452
  return __createProxy__(fullPath);
2607
2453
  },
@@ -2614,23 +2460,18 @@ const __createProxy__ = (path = '') => {
2614
2460
  },
2615
2461
 
2616
2462
  apply: (_, __, args) => {
2617
- // Handle tagged template literals
2618
2463
  if (Array.isArray(args[0]) && 'raw' in args[0]) {
2619
2464
  const strings = args[0];
2620
2465
  const values = args.slice(1);
2621
2466
  const text = strings.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '');
2622
2467
  return __rpc__.do(path, text);
2623
2468
  }
2624
-
2625
2469
  return __rpc__.do(path, ...args);
2626
2470
  },
2627
2471
 
2628
- // Prevent enumeration from causing infinite loops
2629
2472
  ownKeys: () => {
2630
2473
  const keys = [];
2631
- for (const [key, value] of localStore) {
2632
- keys.push(key);
2633
- }
2474
+ for (const [key, value] of localStore) keys.push(key);
2634
2475
  for (const [key, value] of __userDefinitions__) {
2635
2476
  if (key.startsWith(path ? path + '.' : '') && !key.slice(path ? path.length + 1 : 0).includes('.')) {
2636
2477
  const localKey = key.slice(path ? path.length + 1 : 0);
@@ -2652,7 +2493,6 @@ const __createProxy__ = (path = '') => {
2652
2493
  return proxy;
2653
2494
  };
2654
2495
 
2655
- // Root proxy and named exports
2656
2496
  const $ = __createProxy__();
2657
2497
  const db = $.db;
2658
2498
  const ai = $.ai;
@@ -2664,7 +2504,6 @@ const track = $.track;
2664
2504
  const every = $.every;
2665
2505
  const decide = $.decide;
2666
2506
 
2667
- // Set default namespace and context properties
2668
2507
  $.ns = __SDK_CONFIG__.ns;
2669
2508
  $.user = { id: 'anonymous', name: 'Anonymous', role: 'guest' };
2670
2509
  $.request = { method: 'GET', path: '/', headers: {}, body: null };
@@ -2674,1086 +2513,3 @@ $.context = {};
2674
2513
  $.meta = {};
2675
2514
  `
2676
2515
  }
2677
-
2678
- /**
2679
- * Generate .should chainable assertions code
2680
- */
2681
- function generateShouldCode(): string {
2682
- return `
2683
- // ============================================================
2684
- // Global .should Chainable Assertions
2685
- // ============================================================
2686
-
2687
- const __createShouldChain__ = (actual, negated = false) => {
2688
- const check = (condition, message) => {
2689
- const passes = negated ? !condition : condition;
2690
- if (!passes) throw new Error(negated ? 'Expected NOT: ' + message : message);
2691
- };
2692
-
2693
- const stringify = (val) => {
2694
- try {
2695
- return JSON.stringify(val);
2696
- } catch {
2697
- return String(val);
2698
- }
2699
- };
2700
-
2701
- // Create a lazy chain getter - returns 'this' assertion for chaining
2702
- const assertion = {};
2703
-
2704
- // Core assertion methods
2705
- assertion.equal = (expected) => {
2706
- check(actual === expected, 'Expected ' + stringify(actual) + ' to equal ' + stringify(expected));
2707
- return assertion;
2708
- };
2709
- assertion.deep = {
2710
- equal: (expected) => {
2711
- check(stringify(actual) === stringify(expected), 'Expected deep equal to ' + stringify(expected));
2712
- return assertion;
2713
- },
2714
- include: (expected) => {
2715
- const actualStr = stringify(actual);
2716
- const expectedStr = stringify(expected);
2717
- // Check if expected properties exist with same values
2718
- const matches = Object.entries(expected || {}).every(([k, v]) =>
2719
- actual && stringify(actual[k]) === stringify(v)
2720
- );
2721
- check(matches, 'Expected ' + actualStr + ' to deeply include ' + expectedStr);
2722
- return assertion;
2723
- }
2724
- };
2725
- assertion.include = (value) => {
2726
- if (typeof actual === 'string') check(actual.includes(String(value)), 'Expected "' + actual + '" to include "' + value + '"');
2727
- else if (Array.isArray(actual)) check(actual.includes(value), 'Expected array to include ' + stringify(value));
2728
- return assertion;
2729
- };
2730
- assertion.contain = assertion.include;
2731
- assertion.lengthOf = (n) => {
2732
- check(actual?.length === n, 'Expected length ' + n + ', got ' + actual?.length);
2733
- return assertion;
2734
- };
2735
- assertion.match = (regex) => {
2736
- const str = String(actual);
2737
- check(regex.test(str), 'Expected "' + str + '" to match ' + regex);
2738
- return assertion;
2739
- };
2740
- assertion.matches = assertion.match;
2741
-
2742
- // .be accessor with type checks
2743
- Object.defineProperty(assertion, 'be', {
2744
- get: () => {
2745
- const beObj = {
2746
- a: (type) => {
2747
- const actualType = actual === null ? 'null' : Array.isArray(actual) ? 'array' : actual instanceof Date ? 'date' : typeof actual;
2748
- check(actualType === type.toLowerCase(), 'Expected ' + stringify(actual) + ' to be a ' + type);
2749
- return assertion;
2750
- },
2751
- above: (n) => { check(actual > n, 'Expected ' + actual + ' to be above ' + n); return assertion; },
2752
- below: (n) => { check(actual < n, 'Expected ' + actual + ' to be below ' + n); return assertion; },
2753
- within: (min, max) => { check(actual >= min && actual <= max, 'Expected ' + actual + ' to be within ' + min + '..' + max); return assertion; },
2754
- oneOf: (arr) => { check(Array.isArray(arr) && arr.includes(actual), 'Expected ' + stringify(actual) + ' to be one of ' + stringify(arr)); return assertion; },
2755
- instanceOf: (cls) => { check(actual instanceof cls, 'Expected to be instance of ' + cls.name); return assertion; }
2756
- };
2757
- beObj.an = beObj.a;
2758
- Object.defineProperty(beObj, 'true', { get: () => { check(actual === true, 'Expected ' + stringify(actual) + ' to be true'); return assertion; } });
2759
- Object.defineProperty(beObj, 'false', { get: () => { check(actual === false, 'Expected ' + stringify(actual) + ' to be false'); return assertion; } });
2760
- Object.defineProperty(beObj, 'ok', { get: () => { check(!!actual, 'Expected ' + stringify(actual) + ' to be truthy'); return assertion; } });
2761
- Object.defineProperty(beObj, 'null', { get: () => { check(actual === null, 'Expected ' + stringify(actual) + ' to be null'); return assertion; } });
2762
- Object.defineProperty(beObj, 'undefined', { get: () => { check(actual === undefined, 'Expected ' + stringify(actual) + ' to be undefined'); return assertion; } });
2763
- Object.defineProperty(beObj, 'empty', { get: () => {
2764
- const isEmpty = actual === '' || (Array.isArray(actual) && actual.length === 0) || (actual && typeof actual === 'object' && Object.keys(actual).length === 0);
2765
- check(isEmpty, 'Expected ' + stringify(actual) + ' to be empty');
2766
- return assertion;
2767
- }});
2768
- return beObj;
2769
- }
2770
- });
2771
-
2772
- // .have accessor with property/keys/lengthOf/at checks
2773
- Object.defineProperty(assertion, 'have', {
2774
- get: () => ({
2775
- property: (name, value) => {
2776
- const hasIt = actual != null && Object.prototype.hasOwnProperty.call(actual, name);
2777
- if (value !== undefined) {
2778
- check(hasIt && actual[name] === value, "Expected property '" + name + "' = " + stringify(value) + ", got " + stringify(actual?.[name]));
2779
- } else {
2780
- check(hasIt, "Expected to have property '" + name + "'");
2781
- }
2782
- if (hasIt) return __createShouldChain__(actual[name], negated);
2783
- return assertion;
2784
- },
2785
- keys: (...keys) => {
2786
- const actualKeys = Object.keys(actual || {});
2787
- check(keys.every(k => actualKeys.includes(k)), 'Expected to have keys ' + stringify(keys));
2788
- return assertion;
2789
- },
2790
- lengthOf: (n) => {
2791
- check(actual?.length === n, 'Expected length ' + n + ', got ' + actual?.length);
2792
- return assertion;
2793
- },
2794
- at: {
2795
- least: (n) => {
2796
- check(actual?.length >= n, 'Expected length at least ' + n + ', got ' + actual?.length);
2797
- return assertion;
2798
- },
2799
- most: (n) => {
2800
- check(actual?.length <= n, 'Expected length at most ' + n + ', got ' + actual?.length);
2801
- return assertion;
2802
- }
2803
- }
2804
- })
2805
- });
2806
-
2807
- // .not negation
2808
- Object.defineProperty(assertion, 'not', {
2809
- get: () => __createShouldChain__(actual, !negated)
2810
- });
2811
-
2812
- // .with passthrough for readability
2813
- Object.defineProperty(assertion, 'with', {
2814
- get: () => assertion
2815
- });
2816
-
2817
- // .that passthrough for chaining (e.g. .have.property('x').that.matches(/.../) )
2818
- Object.defineProperty(assertion, 'that', {
2819
- get: () => assertion
2820
- });
2821
-
2822
- // .and passthrough for chaining
2823
- Object.defineProperty(assertion, 'and', {
2824
- get: () => assertion
2825
- });
2826
-
2827
- return assertion;
2828
- };
2829
-
2830
- // Add .should to Object.prototype
2831
- Object.defineProperty(Object.prototype, 'should', {
2832
- get: function() { return __createShouldChain__(this); },
2833
- configurable: true,
2834
- enumerable: false
2835
- });
2836
- `
2837
- }
2838
-
2839
- /**
2840
- * Extract export names from module code
2841
- * Supports both CommonJS (exports.foo) and ES module (export const foo) syntax
2842
- */
2843
- function getExportNames(moduleCode: string): string {
2844
- const names = new Set<string>()
2845
-
2846
- // Match exports.name = ...
2847
- const dotPattern = /exports\.(\w+)\s*=/g
2848
- let match
2849
- while ((match = dotPattern.exec(moduleCode)) !== null) {
2850
- if (match[1]) names.add(match[1])
2851
- }
2852
-
2853
- // Match exports['name'] = ... or exports["name"] = ...
2854
- const bracketPattern = /exports\[['"](\w+)['"]\]\s*=/g
2855
- while ((match = bracketPattern.exec(moduleCode)) !== null) {
2856
- if (match[1]) names.add(match[1])
2857
- }
2858
-
2859
- // Match export const name = ... or export let name = ... or export var name = ...
2860
- const esConstPattern = /export\s+(?:const|let|var)\s+(\w+)\s*=/g
2861
- while ((match = esConstPattern.exec(moduleCode)) !== null) {
2862
- if (match[1]) names.add(match[1])
2863
- }
2864
-
2865
- // Match export function name(...) or export async function name(...) or export async function* name(...)
2866
- const esFunctionPattern = /export\s+(?:async\s+)?function\*?\s+(\w+)\s*\(/g
2867
- while ((match = esFunctionPattern.exec(moduleCode)) !== null) {
2868
- if (match[1]) names.add(match[1])
2869
- }
2870
-
2871
- // Match export class name
2872
- const esClassPattern = /export\s+class\s+(\w+)/g
2873
- while ((match = esClassPattern.exec(moduleCode)) !== null) {
2874
- if (match[1]) names.add(match[1])
2875
- }
2876
-
2877
- return Array.from(names).join(', ') || '_unused'
2878
- }
2879
-
2880
- /**
2881
- * Transform module code to work in sandbox
2882
- * Converts ES module exports to CommonJS-style for the sandbox
2883
- */
2884
- function transformModuleCode(moduleCode: string): string {
2885
- let code = moduleCode
2886
-
2887
- // Transform: export const foo = ... → const foo = ...; exports.foo = foo;
2888
- code = code.replace(
2889
- /export\s+(const|let|var)\s+(\w+)\s*=/g,
2890
- '$1 $2 = exports.$2 ='
2891
- )
2892
-
2893
- // Transform: export function foo(...) → function foo(...) exports.foo = foo;
2894
- // Also handles async generators: export async function* foo
2895
- code = code.replace(
2896
- /export\s+(async\s+)?function(\*?)\s+(\w+)/g,
2897
- '$1function$2 $3'
2898
- )
2899
- // Add exports for functions after their definition
2900
- const funcNames = [...moduleCode.matchAll(/export\s+(?:async\s+)?function\*?\s+(\w+)/g)]
2901
- for (const [, name] of funcNames) {
2902
- code += `\nexports.${name} = ${name};`
2903
- }
2904
-
2905
- // Transform: export class Foo → class Foo; exports.Foo = Foo;
2906
- code = code.replace(/export\s+class\s+(\w+)/g, 'class $1')
2907
- const classNames = [...moduleCode.matchAll(/export\s+class\s+(\w+)/g)]
2908
- for (const [, name] of classNames) {
2909
- code += `\nexports.${name} = ${name};`
2910
- }
2911
-
2912
- return code
2913
- }
2914
-
2915
- /**
2916
- * Wrap script to auto-return the last expression
2917
- * Converts: `add(1, 2)` → `return add(1, 2)`
2918
- */
2919
- function wrapScriptForReturn(script: string): string {
2920
- const trimmed = script.trim()
2921
- if (!trimmed) return script
2922
-
2923
- // If script already contains a return statement anywhere, don't modify
2924
- if (/\breturn\b/.test(trimmed)) return script
2925
-
2926
- // If script starts with throw, don't modify
2927
- if (/^\s*throw\b/.test(trimmed)) return script
2928
-
2929
- // If it's a single expression (no newlines, no semicolons except at end), wrap it
2930
- const withoutTrailingSemi = trimmed.replace(/;?\s*$/, '')
2931
- const isSingleLine = !withoutTrailingSemi.includes('\n')
2932
-
2933
- // Check if it looks like a single expression (no control flow, no declarations)
2934
- const startsWithKeyword = /^\s*(const|let|var|if|for|while|switch|try|class|function|async\s+function)\b/.test(withoutTrailingSemi)
2935
-
2936
- if (isSingleLine && !startsWithKeyword) {
2937
- return `return ${withoutTrailingSemi}`
2938
- }
2939
-
2940
- // For multi-statement scripts, try to return the last expression
2941
- const lines = trimmed.split('\n')
2942
- const lastLineRaw = lines[lines.length - 1]
2943
- if (!lastLineRaw) return script
2944
- const lastLine = lastLineRaw.trim()
2945
-
2946
- // If last line is an expression (not a declaration, control flow, or throw)
2947
- if (lastLine && !/^\s*(const|let|var|if|for|while|switch|try|class|function|return|throw)\b/.test(lastLine)) {
2948
- lines[lines.length - 1] = `return ${lastLine.replace(/;?\s*$/, '')}`
2949
- return lines.join('\n')
2950
- }
2951
-
2952
- return script
2953
- }
2954
-
2955
- /**
2956
- * Generate worker code for production (uses RPC to ai-tests)
2957
- */
2958
- export function generateWorkerCode(options: {
2959
- module?: string | undefined
2960
- tests?: string | undefined
2961
- script?: string | undefined
2962
- sdk?: SDKConfig | boolean | undefined
2963
- imports?: string[] | undefined
2964
- }): string {
2965
- const { module: rawModule = '', tests = '', script: rawScript = '', sdk, imports = [] } = options
2966
- const sdkConfig = sdk === true ? {} : (sdk || null)
2967
- const module = rawModule ? transformModuleCode(rawModule) : ''
2968
- const script = rawScript ? wrapScriptForReturn(rawScript) : ''
2969
- const exportNames = getExportNames(rawModule)
2970
-
2971
- // Hoisted imports (from MDX test files) - placed at true module top level
2972
- const hoistedImports = imports.length > 0 ? imports.join('\n') + '\n' : ''
2973
-
2974
- return `
2975
- // Sandbox Worker Entry Point
2976
- import { RpcTarget, newWorkersRpcResponse } from 'capnweb';
2977
- ${hoistedImports}
2978
- const logs = [];
2979
-
2980
- ${sdkConfig ? generateShouldCode() : ''}
2981
-
2982
- ${sdkConfig ? generateSDKCode(sdkConfig) : '// SDK not enabled'}
2983
-
2984
- // Capture console output
2985
- const originalConsole = { ...console };
2986
- const captureConsole = (level) => (...args) => {
2987
- logs.push({
2988
- level,
2989
- message: args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '),
2990
- timestamp: Date.now()
2991
- });
2992
- originalConsole[level](...args);
2993
- };
2994
- console.log = captureConsole('log');
2995
- console.warn = captureConsole('warn');
2996
- console.error = captureConsole('error');
2997
- console.info = captureConsole('info');
2998
- console.debug = captureConsole('debug');
2999
-
3000
- // ============================================================
3001
- // USER MODULE CODE (embedded at generation time)
3002
- // ============================================================
3003
- // Module exports object - exports become top-level variables
3004
- const exports = {};
3005
-
3006
- ${module ? `
3007
- // Execute module code
3008
- try {
3009
- ${module}
3010
- } catch (e) {
3011
- console.error('Module error:', e.message);
3012
- }
3013
- ` : '// No module code provided'}
3014
-
3015
- // Expose all exports as top-level variables for tests and scripts
3016
- // This allows: export const add = (a, b) => a + b; then later: add(1, 2)
3017
- ${rawModule ? `
3018
- const { ${exportNames} } = exports;
3019
- `.trim() : ''}
3020
-
3021
- // ============================================================
3022
- // RPC SERVER - Expose exports via capnweb
3023
- // ============================================================
3024
- class ExportsRpcTarget extends RpcTarget {
3025
- // Dynamically expose all exports as RPC methods
3026
- constructor() {
3027
- super();
3028
- for (const [key, value] of Object.entries(exports)) {
3029
- if (typeof value === 'function') {
3030
- this[key] = value;
3031
- }
3032
- }
3033
- }
3034
-
3035
- // List available exports
3036
- list() {
3037
- return Object.keys(exports);
3038
- }
3039
-
3040
- // Get an export by name
3041
- get(name) {
3042
- return exports[name];
3043
- }
3044
- }
3045
-
3046
- // ============================================================
3047
- // WORKER ENTRY POINT
3048
- // ============================================================
3049
- export default {
3050
- async fetch(request, env) {
3051
- const url = new URL(request.url);
3052
-
3053
- // Route: GET / - Return info about exports
3054
- if (request.method === 'GET' && url.pathname === '/') {
3055
- return Response.json({
3056
- exports: Object.keys(exports),
3057
- rpc: '/rpc',
3058
- execute: '/execute'
3059
- });
3060
- }
3061
-
3062
- // Route: /rpc - capnweb RPC to module exports
3063
- if (url.pathname === '/rpc') {
3064
- return newWorkersRpcResponse(request, new ExportsRpcTarget());
3065
- }
3066
-
3067
- // Route: GET /:name - Simple JSON endpoint to access exports
3068
- if (request.method === 'GET' && url.pathname !== '/execute') {
3069
- const name = url.pathname.slice(1); // Remove leading /
3070
- const value = exports[name];
3071
-
3072
- // Check if export exists
3073
- if (!(name in exports)) {
3074
- return Response.json({ error: \`Export "\${name}" not found\` }, { status: 404 });
3075
- }
3076
-
3077
- // If it's not a function, just return the value
3078
- if (typeof value !== 'function') {
3079
- return Response.json({ result: value });
3080
- }
3081
-
3082
- // It's a function - parse args and call it
3083
- try {
3084
- const args = [];
3085
- const argsParam = url.searchParams.get('args');
3086
- if (argsParam) {
3087
- // Support JSON array: ?args=[1,2,3]
3088
- try {
3089
- const parsed = JSON.parse(argsParam);
3090
- if (Array.isArray(parsed)) {
3091
- args.push(...parsed);
3092
- } else {
3093
- args.push(parsed);
3094
- }
3095
- } catch {
3096
- // Not JSON, use as single string arg
3097
- args.push(argsParam);
3098
- }
3099
- } else {
3100
- // Support named params: ?a=1&b=2 -> passed as object
3101
- const params = Object.fromEntries(url.searchParams.entries());
3102
- if (Object.keys(params).length > 0) {
3103
- // Try to parse numeric values
3104
- for (const [key, val] of Object.entries(params)) {
3105
- const num = Number(val);
3106
- params[key] = !isNaN(num) && val !== '' ? num : val;
3107
- }
3108
- args.push(params);
3109
- }
3110
- }
3111
-
3112
- const result = await value(...args);
3113
- return Response.json({ result });
3114
- } catch (e) {
3115
- return Response.json({ error: e.message }, { status: 500 });
3116
- }
3117
- }
3118
-
3119
- // Route: /execute - Run tests and scripts
3120
- // Check for TEST service binding
3121
- if (!env.TEST) {
3122
- return Response.json({
3123
- success: false,
3124
- error: 'TEST service binding not available. Ensure ai-tests worker is bound.',
3125
- logs,
3126
- duration: 0
3127
- });
3128
- }
3129
-
3130
- // Connect to get the TestServiceCore via RPC
3131
- const testService = await env.TEST.connect();
3132
-
3133
- // Create global test functions that proxy to the RPC service
3134
- const describe = (name, fn) => testService.describe(name, fn);
3135
- const it = (name, fn) => testService.it(name, fn);
3136
- const test = (name, fn) => testService.test(name, fn);
3137
- const expect = (value, message) => testService.expect(value, message);
3138
- const should = (value) => testService.should(value);
3139
- const assert = testService.assert;
3140
- const beforeEach = (fn) => testService.beforeEach(fn);
3141
- const afterEach = (fn) => testService.afterEach(fn);
3142
- const beforeAll = (fn) => testService.beforeAll(fn);
3143
- const afterAll = (fn) => testService.afterAll(fn);
3144
-
3145
- // Add skip/only modifiers
3146
- it.skip = (name, fn) => testService.skip(name, fn);
3147
- it.only = (name, fn) => testService.only(name, fn);
3148
- test.skip = it.skip;
3149
- test.only = it.only;
3150
-
3151
- let scriptResult = undefined;
3152
- let scriptError = null;
3153
- let testResults = undefined;
3154
-
3155
- // ============================================================
3156
- // USER TEST CODE (embedded at generation time)
3157
- // ============================================================
3158
-
3159
- ${tests ? `
3160
- // Register tests
3161
- try {
3162
- ${tests}
3163
- } catch (e) {
3164
- console.error('Test registration error:', e.message);
3165
- }
3166
- ` : '// No test code provided'}
3167
-
3168
- // Execute user script
3169
- ${script ? `
3170
- try {
3171
- scriptResult = await (async () => {
3172
- ${script}
3173
- })();
3174
- } catch (e) {
3175
- console.error('Script error:', e.message);
3176
- scriptError = e.message;
3177
- }
3178
- ` : '// No script code provided'}
3179
-
3180
- // Run tests if any were registered
3181
- ${tests ? `
3182
- try {
3183
- testResults = await testService.run();
3184
- } catch (e) {
3185
- console.error('Test run error:', e.message);
3186
- testResults = { total: 0, passed: 0, failed: 1, skipped: 0, tests: [], duration: 0, error: e.message };
3187
- }
3188
- ` : ''}
3189
-
3190
- const hasTests = ${tests ? 'true' : 'false'};
3191
- const success = scriptError === null && (!hasTests || (testResults && testResults.failed === 0));
3192
-
3193
- return Response.json({
3194
- success,
3195
- value: scriptResult,
3196
- logs,
3197
- testResults: hasTests ? testResults : undefined,
3198
- error: scriptError || undefined,
3199
- duration: 0
3200
- });
3201
- }
3202
- };
3203
- `
3204
- }
3205
-
3206
- /**
3207
- * Generate worker code for development (embedded test framework)
3208
- *
3209
- * This version bundles the test framework directly into the worker,
3210
- * avoiding the need for RPC service bindings in local development.
3211
- */
3212
- export function generateDevWorkerCode(options: {
3213
- module?: string | undefined
3214
- tests?: string | undefined
3215
- script?: string | undefined
3216
- sdk?: SDKConfig | boolean | undefined
3217
- imports?: string[] | undefined
3218
- fetch?: null | undefined
3219
- }): string {
3220
- const { module: rawModule = '', tests = '', script: rawScript = '', sdk, imports = [], fetch: fetchOption } = options
3221
- const sdkConfig = sdk === true ? {} : (sdk || null)
3222
- const module = rawModule ? transformModuleCode(rawModule) : ''
3223
- const script = rawScript ? wrapScriptForReturn(rawScript) : ''
3224
- const exportNames = getExportNames(rawModule)
3225
- const blockFetch = fetchOption === null
3226
-
3227
- // Hoisted imports (from MDX test files) - placed at true module top level
3228
- const hoistedImports = imports.length > 0 ? imports.join('\n') + '\n' : ''
3229
-
3230
- return `
3231
- // Sandbox Worker Entry Point (Dev Mode - embedded test framework)
3232
- ${hoistedImports}
3233
- const logs = [];
3234
- const testResults = { total: 0, passed: 0, failed: 0, skipped: 0, tests: [], duration: 0 };
3235
- const pendingTests = [];
3236
-
3237
- ${blockFetch ? `
3238
- // Block fetch when fetch: null is specified
3239
- const __originalFetch__ = globalThis.fetch;
3240
- globalThis.fetch = async (...args) => {
3241
- throw new Error('Network access blocked: fetch is disabled in this sandbox');
3242
- };
3243
- ` : ''}
3244
-
3245
- ${sdkConfig ? generateShouldCode() : ''}
3246
-
3247
- ${sdkConfig ? generateSDKCode(sdkConfig) : '// SDK not enabled'}
3248
-
3249
- // Capture console output
3250
- const originalConsole = { ...console };
3251
- const captureConsole = (level) => (...args) => {
3252
- logs.push({
3253
- level,
3254
- message: args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '),
3255
- timestamp: Date.now()
3256
- });
3257
- originalConsole[level](...args);
3258
- };
3259
- console.log = captureConsole('log');
3260
- console.warn = captureConsole('warn');
3261
- console.error = captureConsole('error');
3262
- console.info = captureConsole('info');
3263
- console.debug = captureConsole('debug');
3264
-
3265
- // Test framework (vitest-compatible API)
3266
- let currentDescribe = '';
3267
- let beforeEachFns = [];
3268
- let afterEachFns = [];
3269
-
3270
- const describe = (name, fn) => {
3271
- const prev = currentDescribe;
3272
- const prevBeforeEach = [...beforeEachFns];
3273
- const prevAfterEach = [...afterEachFns];
3274
- currentDescribe = currentDescribe ? currentDescribe + ' > ' + name : name;
3275
- try { fn(); } finally {
3276
- currentDescribe = prev;
3277
- beforeEachFns = prevBeforeEach;
3278
- afterEachFns = prevAfterEach;
3279
- }
3280
- };
3281
-
3282
- // Hooks
3283
- const beforeEach = (fn) => { beforeEachFns.push(fn); };
3284
- const afterEach = (fn) => { afterEachFns.push(fn); };
3285
-
3286
- const it = (name, fn) => {
3287
- const fullName = currentDescribe ? currentDescribe + ' > ' + name : name;
3288
- const hooks = { before: [...beforeEachFns], after: [...afterEachFns] };
3289
- pendingTests.push({ name: fullName, fn, hooks });
3290
- };
3291
- const test = it;
3292
-
3293
- it.skip = (name, fn) => {
3294
- const fullName = currentDescribe ? currentDescribe + ' > ' + name : name;
3295
- pendingTests.push({ name: fullName, fn: null, skip: true });
3296
- };
3297
- test.skip = it.skip;
3298
-
3299
- it.only = (name, fn) => {
3300
- const fullName = currentDescribe ? currentDescribe + ' > ' + name : name;
3301
- const hooks = { before: [...beforeEachFns], after: [...afterEachFns] };
3302
- pendingTests.push({ name: fullName, fn, hooks, only: true });
3303
- };
3304
- test.only = it.only;
3305
-
3306
- // Deep equality check
3307
- const deepEqual = (a, b) => {
3308
- if (a === b) return true;
3309
- if (a == null || b == null) return false;
3310
- if (typeof a !== typeof b) return false;
3311
- if (typeof a !== 'object') return false;
3312
- if (Array.isArray(a) !== Array.isArray(b)) return false;
3313
- if (Array.isArray(a)) {
3314
- if (a.length !== b.length) return false;
3315
- return a.every((v, i) => deepEqual(v, b[i]));
3316
- }
3317
- const keysA = Object.keys(a);
3318
- const keysB = Object.keys(b);
3319
- if (keysA.length !== keysB.length) return false;
3320
- return keysA.every(k => deepEqual(a[k], b[k]));
3321
- };
3322
-
3323
- // Expect implementation with vitest-compatible matchers
3324
- const expect = (actual) => {
3325
- const matchers = {
3326
- toBe: (expected) => {
3327
- if (actual !== expected) {
3328
- throw new Error(\`Expected \${JSON.stringify(expected)} but got \${JSON.stringify(actual)}\`);
3329
- }
3330
- },
3331
- toEqual: (expected) => {
3332
- if (!deepEqual(actual, expected)) {
3333
- throw new Error(\`Expected \${JSON.stringify(expected)} but got \${JSON.stringify(actual)}\`);
3334
- }
3335
- },
3336
- toStrictEqual: (expected) => {
3337
- if (!deepEqual(actual, expected)) {
3338
- throw new Error(\`Expected \${JSON.stringify(expected)} but got \${JSON.stringify(actual)}\`);
3339
- }
3340
- },
3341
- toBeTruthy: () => {
3342
- if (!actual) throw new Error(\`Expected truthy but got \${JSON.stringify(actual)}\`);
3343
- },
3344
- toBeFalsy: () => {
3345
- if (actual) throw new Error(\`Expected falsy but got \${JSON.stringify(actual)}\`);
3346
- },
3347
- toBeNull: () => {
3348
- if (actual !== null) throw new Error(\`Expected null but got \${JSON.stringify(actual)}\`);
3349
- },
3350
- toBeUndefined: () => {
3351
- if (actual !== undefined) throw new Error(\`Expected undefined but got \${JSON.stringify(actual)}\`);
3352
- },
3353
- toBeDefined: () => {
3354
- if (actual === undefined) throw new Error('Expected defined but got undefined');
3355
- },
3356
- toBeNaN: () => {
3357
- if (!Number.isNaN(actual)) throw new Error(\`Expected NaN but got \${actual}\`);
3358
- },
3359
- toContain: (item) => {
3360
- if (Array.isArray(actual)) {
3361
- if (!actual.includes(item)) throw new Error(\`Expected array to contain \${JSON.stringify(item)}\`);
3362
- } else if (typeof actual === 'string') {
3363
- if (!actual.includes(item)) throw new Error(\`Expected string to contain "\${item}"\`);
3364
- } else {
3365
- throw new Error('toContain only works on arrays and strings');
3366
- }
3367
- },
3368
- toContainEqual: (item) => {
3369
- if (!Array.isArray(actual)) throw new Error('toContainEqual only works on arrays');
3370
- if (!actual.some(v => deepEqual(v, item))) {
3371
- throw new Error(\`Expected array to contain \${JSON.stringify(item)}\`);
3372
- }
3373
- },
3374
- toHaveLength: (length) => {
3375
- if (actual?.length !== length) {
3376
- throw new Error(\`Expected length \${length} but got \${actual?.length}\`);
3377
- }
3378
- },
3379
- toHaveProperty: function(path, value) {
3380
- const parts = typeof path === 'string' ? path.split('.') : [path];
3381
- let obj = actual;
3382
- for (const part of parts) {
3383
- if (obj == null || !(part in obj)) {
3384
- throw new Error(\`Expected object to have property "\${path}"\`);
3385
- }
3386
- obj = obj[part];
3387
- }
3388
- if (arguments.length > 1 && !deepEqual(obj, value)) {
3389
- throw new Error(\`Expected property "\${path}" to be \${JSON.stringify(value)} but got \${JSON.stringify(obj)}\`);
3390
- }
3391
- },
3392
- toMatchObject: (expected) => {
3393
- if (typeof actual !== 'object' || actual === null) {
3394
- throw new Error('toMatchObject expects an object');
3395
- }
3396
- for (const key of Object.keys(expected)) {
3397
- if (!deepEqual(actual[key], expected[key])) {
3398
- throw new Error(\`Expected property "\${key}" to be \${JSON.stringify(expected[key])} but got \${JSON.stringify(actual[key])}\`);
3399
- }
3400
- }
3401
- },
3402
- toThrow: (expected) => {
3403
- if (typeof actual !== 'function') throw new Error('toThrow expects a function');
3404
- let threw = false;
3405
- let error;
3406
- try {
3407
- actual();
3408
- } catch (e) {
3409
- threw = true;
3410
- error = e;
3411
- }
3412
- if (!threw) throw new Error('Expected function to throw');
3413
- if (expected !== undefined) {
3414
- if (typeof expected === 'string' && !error.message.includes(expected)) {
3415
- throw new Error(\`Expected error message to contain "\${expected}" but got "\${error.message}"\`);
3416
- }
3417
- if (expected instanceof RegExp && !expected.test(error.message)) {
3418
- throw new Error(\`Expected error message to match \${expected} but got "\${error.message}"\`);
3419
- }
3420
- if (typeof expected === 'function' && !(error instanceof expected)) {
3421
- throw new Error(\`Expected error to be instance of \${expected.name}\`);
3422
- }
3423
- }
3424
- },
3425
- toBeGreaterThan: (n) => {
3426
- if (!(actual > n)) throw new Error(\`Expected \${actual} to be greater than \${n}\`);
3427
- },
3428
- toBeLessThan: (n) => {
3429
- if (!(actual < n)) throw new Error(\`Expected \${actual} to be less than \${n}\`);
3430
- },
3431
- toBeGreaterThanOrEqual: (n) => {
3432
- if (!(actual >= n)) throw new Error(\`Expected \${actual} to be >= \${n}\`);
3433
- },
3434
- toBeLessThanOrEqual: (n) => {
3435
- if (!(actual <= n)) throw new Error(\`Expected \${actual} to be <= \${n}\`);
3436
- },
3437
- toBeCloseTo: (n, digits = 2) => {
3438
- const diff = Math.abs(actual - n);
3439
- const threshold = Math.pow(10, -digits) / 2;
3440
- if (diff > threshold) {
3441
- throw new Error(\`Expected \${actual} to be close to \${n}\`);
3442
- }
3443
- },
3444
- toMatch: (pattern) => {
3445
- if (typeof actual !== 'string') throw new Error('toMatch expects a string');
3446
- const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;
3447
- if (!regex.test(actual)) {
3448
- throw new Error(\`Expected "\${actual}" to match \${pattern}\`);
3449
- }
3450
- },
3451
- toBeInstanceOf: (cls) => {
3452
- if (!(actual instanceof cls)) {
3453
- throw new Error(\`Expected instance of \${cls.name}\`);
3454
- }
3455
- },
3456
- toBeTypeOf: (type) => {
3457
- if (typeof actual !== type) {
3458
- throw new Error(\`Expected typeof to be "\${type}" but got "\${typeof actual}"\`);
3459
- }
3460
- },
3461
- };
3462
-
3463
- matchers.not = {
3464
- toBe: (expected) => {
3465
- if (actual === expected) throw new Error(\`Expected not \${JSON.stringify(expected)}\`);
3466
- },
3467
- toEqual: (expected) => {
3468
- if (deepEqual(actual, expected)) {
3469
- throw new Error(\`Expected not equal to \${JSON.stringify(expected)}\`);
3470
- }
3471
- },
3472
- toBeTruthy: () => {
3473
- if (actual) throw new Error('Expected not truthy');
3474
- },
3475
- toBeFalsy: () => {
3476
- if (!actual) throw new Error('Expected not falsy');
3477
- },
3478
- toBeNull: () => {
3479
- if (actual === null) throw new Error('Expected not null');
3480
- },
3481
- toBeUndefined: () => {
3482
- if (actual === undefined) throw new Error('Expected not undefined');
3483
- },
3484
- toBeDefined: () => {
3485
- if (actual !== undefined) throw new Error('Expected undefined');
3486
- },
3487
- toContain: (item) => {
3488
- if (Array.isArray(actual) && actual.includes(item)) {
3489
- throw new Error(\`Expected array not to contain \${JSON.stringify(item)}\`);
3490
- }
3491
- if (typeof actual === 'string' && actual.includes(item)) {
3492
- throw new Error(\`Expected string not to contain "\${item}"\`);
3493
- }
3494
- },
3495
- toHaveProperty: (path) => {
3496
- const parts = typeof path === 'string' ? path.split('.') : [path];
3497
- let obj = actual;
3498
- try {
3499
- for (const part of parts) {
3500
- if (obj == null || !(part in obj)) return;
3501
- obj = obj[part];
3502
- }
3503
- throw new Error(\`Expected object not to have property "\${path}"\`);
3504
- } catch {}
3505
- },
3506
- toThrow: () => {
3507
- if (typeof actual !== 'function') throw new Error('toThrow expects a function');
3508
- try {
3509
- actual();
3510
- } catch (e) {
3511
- throw new Error('Expected function not to throw');
3512
- }
3513
- },
3514
- toMatch: (pattern) => {
3515
- if (typeof actual !== 'string') throw new Error('toMatch expects a string');
3516
- const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;
3517
- if (regex.test(actual)) {
3518
- throw new Error(\`Expected "\${actual}" not to match \${pattern}\`);
3519
- }
3520
- },
3521
- };
3522
-
3523
- matchers.resolves = new Proxy({}, {
3524
- get: (_, prop) => async (...args) => {
3525
- const resolved = await actual;
3526
- return expect(resolved)[prop](...args);
3527
- }
3528
- });
3529
-
3530
- matchers.rejects = new Proxy({}, {
3531
- get: (_, prop) => async (...args) => {
3532
- try {
3533
- await actual;
3534
- throw new Error('Expected promise to reject');
3535
- } catch (e) {
3536
- if (e.message === 'Expected promise to reject') throw e;
3537
- return expect(e)[prop](...args);
3538
- }
3539
- }
3540
- });
3541
-
3542
- return matchers;
3543
- };
3544
-
3545
- // ============================================================
3546
- // USER MODULE CODE (embedded at generation time)
3547
- // ============================================================
3548
- // Module exports object - exports become top-level variables
3549
- const exports = {};
3550
-
3551
- ${module ? `
3552
- // Execute module code
3553
- try {
3554
- ${module}
3555
- } catch (e) {
3556
- console.error('Module error:', e.message);
3557
- }
3558
- ` : '// No module code provided'}
3559
-
3560
- // Expose all exports as top-level variables for tests and scripts
3561
- // This allows: export const add = (a, b) => a + b; then later: add(1, 2)
3562
- ${rawModule ? `
3563
- const { ${exportNames} } = exports;
3564
- `.trim() : ''}
3565
-
3566
- // ============================================================
3567
- // USER TEST CODE (embedded at generation time)
3568
- // ============================================================
3569
- ${tests ? `
3570
- // Register tests
3571
- try {
3572
- ${tests}
3573
- } catch (e) {
3574
- console.error('Test registration error:', e.message);
3575
- }
3576
- ` : '// No test code provided'}
3577
-
3578
- // ============================================================
3579
- // SIMPLE RPC HANDLER (dev mode - no capnweb dependency)
3580
- // ============================================================
3581
- async function handleRpc(request) {
3582
- try {
3583
- const { method, args = [] } = await request.json();
3584
- if (method === 'list') {
3585
- return Response.json({ result: Object.keys(exports) });
3586
- }
3587
- if (method === 'get') {
3588
- const [name] = args;
3589
- const value = exports[name];
3590
- if (typeof value === 'function') {
3591
- return Response.json({ result: { type: 'function', name } });
3592
- }
3593
- return Response.json({ result: value });
3594
- }
3595
- // Call an exported function
3596
- const fn = exports[method];
3597
- if (typeof fn !== 'function') {
3598
- return Response.json({ error: \`Export "\${method}" is not a function\` }, { status: 400 });
3599
- }
3600
- const result = await fn(...args);
3601
- return Response.json({ result });
3602
- } catch (e) {
3603
- return Response.json({ error: e.message }, { status: 500 });
3604
- }
3605
- }
3606
-
3607
- // ============================================================
3608
- // WORKER ENTRY POINT
3609
- // ============================================================
3610
- export default {
3611
- async fetch(request, env) {
3612
- const url = new URL(request.url);
3613
-
3614
- // Route: GET / - Return info about exports
3615
- if (request.method === 'GET' && url.pathname === '/') {
3616
- return Response.json({
3617
- exports: Object.keys(exports),
3618
- rpc: '/rpc',
3619
- execute: '/execute'
3620
- });
3621
- }
3622
-
3623
- // Route: POST /rpc - Simple RPC to module exports
3624
- if (url.pathname === '/rpc' && request.method === 'POST') {
3625
- return handleRpc(request);
3626
- }
3627
-
3628
- // Route: GET /:name - Simple JSON endpoint to access exports
3629
- if (request.method === 'GET' && url.pathname !== '/execute') {
3630
- const name = url.pathname.slice(1);
3631
- const value = exports[name];
3632
-
3633
- // Check if export exists
3634
- if (!(name in exports)) {
3635
- return Response.json({ error: \`Export "\${name}" not found\` }, { status: 404 });
3636
- }
3637
-
3638
- // If it's not a function, just return the value
3639
- if (typeof value !== 'function') {
3640
- return Response.json({ result: value });
3641
- }
3642
-
3643
- // It's a function - parse args and call it
3644
- try {
3645
- const args = [];
3646
- const argsParam = url.searchParams.get('args');
3647
- if (argsParam) {
3648
- try {
3649
- const parsed = JSON.parse(argsParam);
3650
- if (Array.isArray(parsed)) args.push(...parsed);
3651
- else args.push(parsed);
3652
- } catch {
3653
- args.push(argsParam);
3654
- }
3655
- } else {
3656
- const params = Object.fromEntries(url.searchParams.entries());
3657
- if (Object.keys(params).length > 0) {
3658
- for (const [key, val] of Object.entries(params)) {
3659
- const num = Number(val);
3660
- params[key] = !isNaN(num) && val !== '' ? num : val;
3661
- }
3662
- args.push(params);
3663
- }
3664
- }
3665
- const result = await value(...args);
3666
- return Response.json({ result });
3667
- } catch (e) {
3668
- return Response.json({ error: e.message }, { status: 500 });
3669
- }
3670
- }
3671
-
3672
- // Route: /execute - Run tests and scripts
3673
- let scriptResult = undefined;
3674
- let scriptError = null;
3675
-
3676
- // Execute user script
3677
- ${script ? `
3678
- try {
3679
- scriptResult = await (async () => {
3680
- ${script}
3681
- })();
3682
- } catch (e) {
3683
- console.error('Script error:', e.message);
3684
- scriptError = e.message;
3685
- }
3686
- ` : '// No script code provided'}
3687
-
3688
- // Run all pending tests
3689
- const testStart = Date.now();
3690
- const hasOnly = pendingTests.some(t => t.only);
3691
- const testsToRun = hasOnly ? pendingTests.filter(t => t.only || t.skip) : pendingTests;
3692
-
3693
- for (const { name, fn, hooks, skip } of testsToRun) {
3694
- testResults.total++;
3695
-
3696
- if (skip) {
3697
- testResults.skipped++;
3698
- testResults.tests.push({ name, passed: true, skipped: true, duration: 0 });
3699
- continue;
3700
- }
3701
-
3702
- const start = Date.now();
3703
- try {
3704
- // Run beforeEach hooks
3705
- if (hooks?.before) {
3706
- for (const hook of hooks.before) {
3707
- const hookResult = hook();
3708
- if (hookResult && typeof hookResult.then === 'function') {
3709
- await hookResult;
3710
- }
3711
- }
3712
- }
3713
-
3714
- // Run the test
3715
- const result = fn();
3716
- if (result && typeof result.then === 'function') {
3717
- await result;
3718
- }
3719
-
3720
- // Run afterEach hooks
3721
- if (hooks?.after) {
3722
- for (const hook of hooks.after) {
3723
- const hookResult = hook();
3724
- if (hookResult && typeof hookResult.then === 'function') {
3725
- await hookResult;
3726
- }
3727
- }
3728
- }
3729
-
3730
- testResults.passed++;
3731
- testResults.tests.push({ name, passed: true, duration: Date.now() - start });
3732
- } catch (e) {
3733
- testResults.failed++;
3734
- testResults.tests.push({
3735
- name,
3736
- passed: false,
3737
- error: e.message || String(e),
3738
- duration: Date.now() - start
3739
- });
3740
- }
3741
- }
3742
-
3743
- testResults.duration = Date.now() - testStart;
3744
-
3745
- const hasTests = ${tests ? 'true' : 'false'};
3746
- const success = scriptError === null && (!hasTests || testResults.failed === 0);
3747
-
3748
- return Response.json({
3749
- success,
3750
- value: scriptResult,
3751
- logs,
3752
- testResults: hasTests ? testResults : undefined,
3753
- error: scriptError || undefined,
3754
- duration: 0
3755
- });
3756
- }
3757
- };
3758
- `
3759
- }