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