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