ai-evaluate 0.1.0

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