ai-evaluate 2.1.8 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/evaluate.d.ts.map +1 -1
- package/dist/evaluate.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/miniflare-pool.d.ts.map +1 -1
- package/dist/miniflare-pool.js.map +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js.map +1 -1
- package/dist/static/index.d.ts +111 -0
- package/dist/static/index.d.ts.map +1 -0
- package/dist/static/index.js +347 -0
- package/dist/static/index.js.map +1 -0
- package/dist/type-guards.d.ts.map +1 -1
- package/dist/type-guards.js.map +1 -1
- package/dist/worker-template/core.d.ts.map +1 -1
- package/dist/worker-template/core.js +1 -1
- package/dist/worker-template/core.js.map +1 -1
- package/package.json +17 -4
- package/public/capnweb.mjs +220 -0
- package/public/index.mjs +426 -0
- package/public/scaffold.mjs +198 -0
- package/.turbo/turbo-build.log +0 -4
- package/.turbo/turbo-test.log +0 -54
- package/.turbo/turbo-typecheck.log +0 -4
- package/CHANGELOG.md +0 -48
- package/example/package.json +0 -20
- package/example/src/index.ts +0 -221
- package/example/wrangler.jsonc +0 -25
- package/src/capnweb-bundle.ts +0 -2596
- package/src/evaluate.ts +0 -329
- package/src/index.ts +0 -23
- package/src/miniflare-pool.ts +0 -395
- package/src/node.ts +0 -245
- package/src/repl.ts +0 -228
- package/src/shared.ts +0 -186
- package/src/type-guards.ts +0 -323
- package/src/types.ts +0 -196
- package/src/validation.ts +0 -120
- package/src/worker-template/code-transforms.ts +0 -32
- package/src/worker-template/core.ts +0 -557
- package/src/worker-template/helpers.ts +0 -90
- package/src/worker-template/index.ts +0 -23
- package/src/worker-template/sdk-generator.ts +0 -2515
- package/src/worker-template/test-generator.ts +0 -358
- package/test/evaluate-extended.test.js +0 -429
- package/test/evaluate-extended.test.ts +0 -469
- package/test/evaluate.test.js +0 -235
- package/test/evaluate.test.ts +0 -253
- package/test/index.test.js +0 -77
- package/test/index.test.ts +0 -95
- package/test/miniflare-pool.test.ts +0 -246
- package/test/node.test.ts +0 -467
- package/test/security.test.ts +0 -1009
- package/test/shared.test.ts +0 -105
- package/test/type-guards.test.ts +0 -303
- package/test/validation.test.ts +0 -240
- package/test/worker-template.test.js +0 -365
- package/test/worker-template.test.ts +0 -432
- package/tsconfig.json +0 -22
- package/vitest.config.js +0 -21
- package/vitest.config.ts +0 -28
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ai-evaluate v2.1.8
|
|
3
|
+
* Static worker template for evaluate.workers.do
|
|
4
|
+
* Generated: 2026-01-26T01:42:47.000Z
|
|
5
|
+
*
|
|
6
|
+
* @license MIT
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Bundled capnweb RPC library for Cloudflare Workers
|
|
10
|
+
import * as cfw from 'cloudflare:workers';
|
|
11
|
+
|
|
12
|
+
// src/symbols.ts
|
|
13
|
+
var WORKERS_MODULE_SYMBOL = Symbol("workers-module");
|
|
14
|
+
globalThis[WORKERS_MODULE_SYMBOL] = cfw;
|
|
15
|
+
|
|
16
|
+
// src/core.ts
|
|
17
|
+
if (!Symbol.dispose) {
|
|
18
|
+
Symbol.dispose = Symbol.for("dispose");
|
|
19
|
+
}
|
|
20
|
+
if (!Symbol.asyncDispose) {
|
|
21
|
+
Symbol.asyncDispose = Symbol.for("asyncDispose");
|
|
22
|
+
}
|
|
23
|
+
if (!Promise.withResolvers) {
|
|
24
|
+
Promise.withResolvers = function() {
|
|
25
|
+
let resolve;
|
|
26
|
+
let reject;
|
|
27
|
+
const promise = new Promise((res, rej) => {
|
|
28
|
+
resolve = res;
|
|
29
|
+
reject = rej;
|
|
30
|
+
});
|
|
31
|
+
return { promise, resolve, reject };
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
var workersModule = globalThis[WORKERS_MODULE_SYMBOL];
|
|
35
|
+
var RpcTarget = workersModule ? workersModule.RpcTarget : class {
|
|
36
|
+
};
|
|
37
|
+
function typeForRpc(value) {
|
|
38
|
+
switch (typeof value) {
|
|
39
|
+
case "boolean":
|
|
40
|
+
case "number":
|
|
41
|
+
case "string":
|
|
42
|
+
return "primitive";
|
|
43
|
+
case "undefined":
|
|
44
|
+
return "undefined";
|
|
45
|
+
case "object":
|
|
46
|
+
case "function":
|
|
47
|
+
break;
|
|
48
|
+
case "bigint":
|
|
49
|
+
return "bigint";
|
|
50
|
+
default:
|
|
51
|
+
return "unsupported";
|
|
52
|
+
}
|
|
53
|
+
if (value === null) {
|
|
54
|
+
return "primitive";
|
|
55
|
+
}
|
|
56
|
+
let prototype = Object.getPrototypeOf(value);
|
|
57
|
+
switch (prototype) {
|
|
58
|
+
case Object.prototype:
|
|
59
|
+
return "object";
|
|
60
|
+
case Function.prototype:
|
|
61
|
+
return "function";
|
|
62
|
+
case Array.prototype:
|
|
63
|
+
return "array";
|
|
64
|
+
case Date.prototype:
|
|
65
|
+
return "date";
|
|
66
|
+
case Uint8Array.prototype:
|
|
67
|
+
return "bytes";
|
|
68
|
+
// TODO: All other structured clone types.
|
|
69
|
+
case RpcStub.prototype:
|
|
70
|
+
return "stub";
|
|
71
|
+
case RpcPromise.prototype:
|
|
72
|
+
return "rpc-promise";
|
|
73
|
+
// TODO: Promise<T> or thenable
|
|
74
|
+
default:
|
|
75
|
+
if (workersModule) {
|
|
76
|
+
if (prototype == workersModule.RpcStub.prototype || value instanceof workersModule.ServiceStub) {
|
|
77
|
+
return "rpc-target";
|
|
78
|
+
} else if (prototype == workersModule.RpcPromise.prototype || prototype == workersModule.RpcProperty.prototype) {
|
|
79
|
+
return "rpc-thenable";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (value instanceof RpcTarget) {
|
|
83
|
+
return "rpc-target";
|
|
84
|
+
}
|
|
85
|
+
if (value instanceof Error) {
|
|
86
|
+
return "error";
|
|
87
|
+
}
|
|
88
|
+
return "unsupported";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function mapNotLoaded() {
|
|
92
|
+
throw new Error("RPC map() implementation was not loaded.");
|
|
93
|
+
}
|
|
94
|
+
var mapImpl = { applyMap: mapNotLoaded, sendMap: mapNotLoaded };
|
|
95
|
+
var StubHook = class {
|
|
96
|
+
};
|
|
97
|
+
var ErrorStubHook = class extends StubHook {
|
|
98
|
+
constructor(error) {
|
|
99
|
+
super();
|
|
100
|
+
this.error = error;
|
|
101
|
+
}
|
|
102
|
+
call(path, args) {
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
map(path, captures, instructions) {
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
get(path) {
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
dup() {
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
pull() {
|
|
115
|
+
return Promise.reject(this.error);
|
|
116
|
+
}
|
|
117
|
+
ignoreUnhandledRejections() {
|
|
118
|
+
}
|
|
119
|
+
dispose() {
|
|
120
|
+
}
|
|
121
|
+
onBroken(callback) {
|
|
122
|
+
try {
|
|
123
|
+
callback(this.error);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
Promise.resolve(err);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
var DISPOSED_HOOK = new ErrorStubHook(
|
|
130
|
+
new Error("Attempted to use RPC stub after it has been disposed.")
|
|
131
|
+
);
|
|
132
|
+
var doCall = (hook, path, params) => {
|
|
133
|
+
return hook.call(path, params);
|
|
134
|
+
};
|
|
135
|
+
function withCallInterceptor(interceptor, callback) {
|
|
136
|
+
let oldValue = doCall;
|
|
137
|
+
doCall = interceptor;
|
|
138
|
+
try {
|
|
139
|
+
return callback();
|
|
140
|
+
} finally {
|
|
141
|
+
doCall = oldValue;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
var RAW_STUB = Symbol("realStub");
|
|
145
|
+
var PROXY_HANDLERS = {
|
|
146
|
+
apply(target, thisArg, argumentsList) {
|
|
147
|
+
let stub = target.raw;
|
|
148
|
+
return new RpcPromise(doCall(
|
|
149
|
+
stub.hook,
|
|
150
|
+
stub.pathIfPromise || [],
|
|
151
|
+
RpcPayload.fromAppParams(argumentsList)
|
|
152
|
+
), []);
|
|
153
|
+
},
|
|
154
|
+
get(target, prop, receiver) {
|
|
155
|
+
let stub = target.raw;
|
|
156
|
+
if (prop === RAW_STUB) {
|
|
157
|
+
return stub;
|
|
158
|
+
} else if (prop in RpcPromise.prototype) {
|
|
159
|
+
return stub[prop];
|
|
160
|
+
} else if (typeof prop === "string") {
|
|
161
|
+
return new RpcPromise(
|
|
162
|
+
stub.hook,
|
|
163
|
+
stub.pathIfPromise ? [...stub.pathIfPromise, prop] : [prop]
|
|
164
|
+
);
|
|
165
|
+
} else if (prop === Symbol.dispose && (!stub.pathIfPromise || stub.pathIfPromise.length == 0)) {
|
|
166
|
+
return () => {
|
|
167
|
+
stub.hook.dispose();
|
|
168
|
+
stub.hook = DISPOSED_HOOK;
|
|
169
|
+
};
|
|
170
|
+
} else {
|
|
171
|
+
return void 0;
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
has(target, prop) {
|
|
175
|
+
let stub = target.raw;
|
|
176
|
+
if (prop === RAW_STUB) {
|
|
177
|
+
return true;
|
|
178
|
+
} else if (prop in RpcPromise.prototype) {
|
|
179
|
+
return prop in stub;
|
|
180
|
+
} else if (typeof prop === "string") {
|
|
181
|
+
return true;
|
|
182
|
+
} else if (prop === Symbol.dispose && (!stub.pathIfPromise || stub.pathIfPromise.length == 0)) {
|
|
183
|
+
return true;
|
|
184
|
+
} else {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
construct(target, args) {
|
|
189
|
+
throw new Error("An RPC stub cannot be used as a constructor.");
|
|
190
|
+
},
|
|
191
|
+
defineProperty(target, property, attributes) {
|
|
192
|
+
throw new Error("Can't define properties on RPC stubs.");
|
|
193
|
+
},
|
|
194
|
+
deleteProperty(target, p) {
|
|
195
|
+
throw new Error("Can't delete properties on RPC stubs.");
|
|
196
|
+
},
|
|
197
|
+
getOwnPropertyDescriptor(target, p) {
|
|
198
|
+
return void 0;
|
|
199
|
+
},
|
|
200
|
+
getPrototypeOf(target) {
|
|
201
|
+
return Object.getPrototypeOf(target.raw);
|
|
202
|
+
},
|
|
203
|
+
isExtensible(target) {
|
|
204
|
+
return false;
|
|
205
|
+
},
|
|
206
|
+
ownKeys(target) {
|
|
207
|
+
return [];
|
|
208
|
+
},
|
|
209
|
+
preventExtensions(target) {
|
|
210
|
+
return true;
|
|
211
|
+
},
|
|
212
|
+
set(target, p, newValue, receiver) {
|
|
213
|
+
throw new Error("Can't assign properties on RPC stubs.");
|
|
214
|
+
},
|
|
215
|
+
setPrototypeOf(target, v) {
|
|
216
|
+
throw new Error("Can't override prototype of RPC stubs.");
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
var RpcStub = class _RpcStub extends RpcTarget {
|
|
220
|
+
// Although \
|
package/public/index.mjs
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ai-evaluate v2.1.8
|
|
3
|
+
* Static worker template for evaluate.workers.do
|
|
4
|
+
* Generated: 2026-01-26T01:42:47.000Z
|
|
5
|
+
*
|
|
6
|
+
* @license MIT
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Full worker template with capnweb RPC support
|
|
10
|
+
// Import capnweb from the capnweb.js module
|
|
11
|
+
|
|
12
|
+
import { RpcTarget, newWorkersRpcResponse } from 'capnweb.js';
|
|
13
|
+
|
|
14
|
+
const logs = [];
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
// ============================================================
|
|
18
|
+
// Global .should Chainable Assertions
|
|
19
|
+
// ============================================================
|
|
20
|
+
|
|
21
|
+
const __createShouldChain__ = (actual, negated = false) => {
|
|
22
|
+
const check = (condition, message) => {
|
|
23
|
+
const passes = negated ? !condition : condition;
|
|
24
|
+
if (!passes) throw new Error(negated ? 'Expected NOT: ' + message : message);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const stringify = (val) => {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.stringify(val);
|
|
30
|
+
} catch {
|
|
31
|
+
return String(val);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Create a lazy chain getter - returns 'this' assertion for chaining
|
|
36
|
+
const assertion = {};
|
|
37
|
+
|
|
38
|
+
// Core assertion methods
|
|
39
|
+
assertion.equal = (expected) => {
|
|
40
|
+
check(actual === expected, 'Expected ' + stringify(actual) + ' to equal ' + stringify(expected));
|
|
41
|
+
return assertion;
|
|
42
|
+
};
|
|
43
|
+
assertion.deep = {
|
|
44
|
+
equal: (expected) => {
|
|
45
|
+
check(stringify(actual) === stringify(expected), 'Expected deep equal to ' + stringify(expected));
|
|
46
|
+
return assertion;
|
|
47
|
+
},
|
|
48
|
+
include: (expected) => {
|
|
49
|
+
const actualStr = stringify(actual);
|
|
50
|
+
const expectedStr = stringify(expected);
|
|
51
|
+
// Check if expected properties exist with same values
|
|
52
|
+
const matches = Object.entries(expected || {}).every(([k, v]) =>
|
|
53
|
+
actual && stringify(actual[k]) === stringify(v)
|
|
54
|
+
);
|
|
55
|
+
check(matches, 'Expected ' + actualStr + ' to deeply include ' + expectedStr);
|
|
56
|
+
return assertion;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
assertion.include = (value) => {
|
|
60
|
+
if (typeof actual === 'string') check(actual.includes(String(value)), 'Expected "' + actual + '" to include "' + value + '"');
|
|
61
|
+
else if (Array.isArray(actual)) check(actual.includes(value), 'Expected array to include ' + stringify(value));
|
|
62
|
+
return assertion;
|
|
63
|
+
};
|
|
64
|
+
assertion.contain = assertion.include;
|
|
65
|
+
assertion.lengthOf = (n) => {
|
|
66
|
+
check(actual?.length === n, 'Expected length ' + n + ', got ' + actual?.length);
|
|
67
|
+
return assertion;
|
|
68
|
+
};
|
|
69
|
+
assertion.match = (regex) => {
|
|
70
|
+
const str = String(actual);
|
|
71
|
+
check(regex.test(str), 'Expected "' + str + '" to match ' + regex);
|
|
72
|
+
return assertion;
|
|
73
|
+
};
|
|
74
|
+
assertion.matches = assertion.match;
|
|
75
|
+
|
|
76
|
+
// .be accessor with type checks
|
|
77
|
+
Object.defineProperty(assertion, 'be', {
|
|
78
|
+
get: () => {
|
|
79
|
+
const beObj = {
|
|
80
|
+
a: (type) => {
|
|
81
|
+
const actualType = actual === null ? 'null' : Array.isArray(actual) ? 'array' : actual instanceof Date ? 'date' : typeof actual;
|
|
82
|
+
check(actualType === type.toLowerCase(), 'Expected ' + stringify(actual) + ' to be a ' + type);
|
|
83
|
+
return assertion;
|
|
84
|
+
},
|
|
85
|
+
above: (n) => { check(actual > n, 'Expected ' + actual + ' to be above ' + n); return assertion; },
|
|
86
|
+
below: (n) => { check(actual < n, 'Expected ' + actual + ' to be below ' + n); return assertion; },
|
|
87
|
+
within: (min, max) => { check(actual >= min && actual <= max, 'Expected ' + actual + ' to be within ' + min + '..' + max); return assertion; },
|
|
88
|
+
oneOf: (arr) => { check(Array.isArray(arr) && arr.includes(actual), 'Expected ' + stringify(actual) + ' to be one of ' + stringify(arr)); return assertion; },
|
|
89
|
+
instanceOf: (cls) => { check(actual instanceof cls, 'Expected to be instance of ' + cls.name); return assertion; }
|
|
90
|
+
};
|
|
91
|
+
beObj.an = beObj.a;
|
|
92
|
+
Object.defineProperty(beObj, 'true', { get: () => { check(actual === true, 'Expected ' + stringify(actual) + ' to be true'); return assertion; } });
|
|
93
|
+
Object.defineProperty(beObj, 'false', { get: () => { check(actual === false, 'Expected ' + stringify(actual) + ' to be false'); return assertion; } });
|
|
94
|
+
Object.defineProperty(beObj, 'ok', { get: () => { check(!!actual, 'Expected ' + stringify(actual) + ' to be truthy'); return assertion; } });
|
|
95
|
+
Object.defineProperty(beObj, 'null', { get: () => { check(actual === null, 'Expected ' + stringify(actual) + ' to be null'); return assertion; } });
|
|
96
|
+
Object.defineProperty(beObj, 'undefined', { get: () => { check(actual === undefined, 'Expected ' + stringify(actual) + ' to be undefined'); return assertion; } });
|
|
97
|
+
Object.defineProperty(beObj, 'empty', { get: () => {
|
|
98
|
+
const isEmpty = actual === '' || (Array.isArray(actual) && actual.length === 0) || (actual && typeof actual === 'object' && Object.keys(actual).length === 0);
|
|
99
|
+
check(isEmpty, 'Expected ' + stringify(actual) + ' to be empty');
|
|
100
|
+
return assertion;
|
|
101
|
+
}});
|
|
102
|
+
return beObj;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// .have accessor with property/keys/lengthOf/at checks
|
|
107
|
+
Object.defineProperty(assertion, 'have', {
|
|
108
|
+
get: () => ({
|
|
109
|
+
property: (name, value) => {
|
|
110
|
+
const hasIt = actual != null && Object.prototype.hasOwnProperty.call(actual, name);
|
|
111
|
+
if (value !== undefined) {
|
|
112
|
+
check(hasIt && actual[name] === value, "Expected property '" + name + "' = " + stringify(value) + ", got " + stringify(actual?.[name]));
|
|
113
|
+
} else {
|
|
114
|
+
check(hasIt, "Expected to have property '" + name + "'");
|
|
115
|
+
}
|
|
116
|
+
if (hasIt) return __createShouldChain__(actual[name], negated);
|
|
117
|
+
return assertion;
|
|
118
|
+
},
|
|
119
|
+
keys: (...keys) => {
|
|
120
|
+
const actualKeys = Object.keys(actual || {});
|
|
121
|
+
check(keys.every(k => actualKeys.includes(k)), 'Expected to have keys ' + stringify(keys));
|
|
122
|
+
return assertion;
|
|
123
|
+
},
|
|
124
|
+
lengthOf: (n) => {
|
|
125
|
+
check(actual?.length === n, 'Expected length ' + n + ', got ' + actual?.length);
|
|
126
|
+
return assertion;
|
|
127
|
+
},
|
|
128
|
+
at: {
|
|
129
|
+
least: (n) => {
|
|
130
|
+
check(actual?.length >= n, 'Expected length at least ' + n + ', got ' + actual?.length);
|
|
131
|
+
return assertion;
|
|
132
|
+
},
|
|
133
|
+
most: (n) => {
|
|
134
|
+
check(actual?.length <= n, 'Expected length at most ' + n + ', got ' + actual?.length);
|
|
135
|
+
return assertion;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// .not negation
|
|
142
|
+
Object.defineProperty(assertion, 'not', {
|
|
143
|
+
get: () => __createShouldChain__(actual, !negated)
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// .with passthrough for readability
|
|
147
|
+
Object.defineProperty(assertion, 'with', {
|
|
148
|
+
get: () => assertion
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// .that passthrough for chaining (e.g. .have.property('x').that.matches(/.../) )
|
|
152
|
+
Object.defineProperty(assertion, 'that', {
|
|
153
|
+
get: () => assertion
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// .and passthrough for chaining
|
|
157
|
+
Object.defineProperty(assertion, 'and', {
|
|
158
|
+
get: () => assertion
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return assertion;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Add .should to Object.prototype
|
|
165
|
+
Object.defineProperty(Object.prototype, 'should', {
|
|
166
|
+
get: function() { return __createShouldChain__(this); },
|
|
167
|
+
configurable: true,
|
|
168
|
+
enumerable: false
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
// Capture console output
|
|
173
|
+
const originalConsole = { ...console };
|
|
174
|
+
const captureConsole = (level) => (...args) => {
|
|
175
|
+
logs.push({
|
|
176
|
+
level,
|
|
177
|
+
message: args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '),
|
|
178
|
+
timestamp: Date.now()
|
|
179
|
+
});
|
|
180
|
+
originalConsole[level](...args);
|
|
181
|
+
};
|
|
182
|
+
console.log = captureConsole('log');
|
|
183
|
+
console.warn = captureConsole('warn');
|
|
184
|
+
console.error = captureConsole('error');
|
|
185
|
+
console.info = captureConsole('info');
|
|
186
|
+
console.debug = captureConsole('debug');
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
// Test framework (vitest-compatible API)
|
|
190
|
+
let currentDescribe = '';
|
|
191
|
+
let beforeEachFns = [];
|
|
192
|
+
let afterEachFns = [];
|
|
193
|
+
|
|
194
|
+
const describe = (name, fn) => {
|
|
195
|
+
const prev = currentDescribe;
|
|
196
|
+
const prevBeforeEach = [...beforeEachFns];
|
|
197
|
+
const prevAfterEach = [...afterEachFns];
|
|
198
|
+
currentDescribe = currentDescribe ? currentDescribe + ' > ' + name : name;
|
|
199
|
+
try { fn(); } finally {
|
|
200
|
+
currentDescribe = prev;
|
|
201
|
+
beforeEachFns = prevBeforeEach;
|
|
202
|
+
afterEachFns = prevAfterEach;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Hooks
|
|
207
|
+
const beforeEach = (fn) => { beforeEachFns.push(fn); };
|
|
208
|
+
const afterEach = (fn) => { afterEachFns.push(fn); };
|
|
209
|
+
|
|
210
|
+
const it = (name, fn) => {
|
|
211
|
+
const fullName = currentDescribe ? currentDescribe + ' > ' + name : name;
|
|
212
|
+
const hooks = { before: [...beforeEachFns], after: [...afterEachFns] };
|
|
213
|
+
pendingTests.push({ name: fullName, fn, hooks });
|
|
214
|
+
};
|
|
215
|
+
const test = it;
|
|
216
|
+
|
|
217
|
+
it.skip = (name, fn) => {
|
|
218
|
+
const fullName = currentDescribe ? currentDescribe + ' > ' + name : name;
|
|
219
|
+
pendingTests.push({ name: fullName, fn: null, skip: true });
|
|
220
|
+
};
|
|
221
|
+
test.skip = it.skip;
|
|
222
|
+
|
|
223
|
+
it.only = (name, fn) => {
|
|
224
|
+
const fullName = currentDescribe ? currentDescribe + ' > ' + name : name;
|
|
225
|
+
const hooks = { before: [...beforeEachFns], after: [...afterEachFns] };
|
|
226
|
+
pendingTests.push({ name: fullName, fn, hooks, only: true });
|
|
227
|
+
};
|
|
228
|
+
test.only = it.only;
|
|
229
|
+
|
|
230
|
+
// Deep equality check
|
|
231
|
+
const deepEqual = (a, b) => {
|
|
232
|
+
if (a === b) return true;
|
|
233
|
+
if (a == null || b == null) return false;
|
|
234
|
+
if (typeof a !== typeof b) return false;
|
|
235
|
+
if (typeof a !== 'object') return false;
|
|
236
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
237
|
+
if (Array.isArray(a)) {
|
|
238
|
+
if (a.length !== b.length) return false;
|
|
239
|
+
return a.every((v, i) => deepEqual(v, b[i]));
|
|
240
|
+
}
|
|
241
|
+
const keysA = Object.keys(a);
|
|
242
|
+
const keysB = Object.keys(b);
|
|
243
|
+
if (keysA.length !== keysB.length) return false;
|
|
244
|
+
return keysA.every(k => deepEqual(a[k], b[k]));
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Expect implementation with vitest-compatible matchers
|
|
248
|
+
const expect = (actual) => {
|
|
249
|
+
const matchers = {
|
|
250
|
+
toBe: (expected) => {
|
|
251
|
+
if (actual !== expected) {
|
|
252
|
+
throw new Error(\
|
|
253
|
+
|
|
254
|
+
// Module exports object
|
|
255
|
+
const exports = {};
|
|
256
|
+
const pendingTests = [];
|
|
257
|
+
const testResults = { total: 0, passed: 0, failed: 0, skipped: 0, tests: [], duration: 0 };
|
|
258
|
+
|
|
259
|
+
// ============================================================
|
|
260
|
+
// USER CODE PLACEHOLDER
|
|
261
|
+
// ============================================================
|
|
262
|
+
// USER_MODULE_CODE
|
|
263
|
+
// USER_TEST_CODE
|
|
264
|
+
|
|
265
|
+
// ============================================================
|
|
266
|
+
// RPC SERVER - Expose exports via capnweb
|
|
267
|
+
// ============================================================
|
|
268
|
+
class ExportsRpcTarget extends RpcTarget {
|
|
269
|
+
constructor() {
|
|
270
|
+
super();
|
|
271
|
+
for (const [key, value] of Object.entries(exports)) {
|
|
272
|
+
if (typeof value === 'function') {
|
|
273
|
+
this[key] = value;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
list() {
|
|
279
|
+
return Object.keys(exports);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
get(name) {
|
|
283
|
+
return exports[name];
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ============================================================
|
|
288
|
+
// WORKER ENTRY POINT
|
|
289
|
+
// ============================================================
|
|
290
|
+
export default {
|
|
291
|
+
async fetch(request, env) {
|
|
292
|
+
const url = new URL(request.url);
|
|
293
|
+
|
|
294
|
+
// Route: GET / - Return info about exports
|
|
295
|
+
if (request.method === 'GET' && url.pathname === '/') {
|
|
296
|
+
return Response.json({
|
|
297
|
+
exports: Object.keys(exports),
|
|
298
|
+
rpc: '/rpc',
|
|
299
|
+
execute: '/execute'
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Route: /rpc - capnweb RPC to module exports
|
|
304
|
+
if (url.pathname === '/rpc') {
|
|
305
|
+
return newWorkersRpcResponse(request, new ExportsRpcTarget());
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Route: GET /:name - Simple JSON endpoint to access exports
|
|
309
|
+
if (request.method === 'GET' && url.pathname !== '/execute') {
|
|
310
|
+
const name = url.pathname.slice(1);
|
|
311
|
+
const value = exports[name];
|
|
312
|
+
|
|
313
|
+
if (!(name in exports)) {
|
|
314
|
+
return Response.json({ error: `Export "${name}" not found` }, { status: 404 });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (typeof value !== 'function') {
|
|
318
|
+
return Response.json({ result: value });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
const args = [];
|
|
323
|
+
const argsParam = url.searchParams.get('args');
|
|
324
|
+
if (argsParam) {
|
|
325
|
+
try {
|
|
326
|
+
const parsed = JSON.parse(argsParam);
|
|
327
|
+
if (Array.isArray(parsed)) args.push(...parsed);
|
|
328
|
+
else args.push(parsed);
|
|
329
|
+
} catch {
|
|
330
|
+
args.push(argsParam);
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
const params = Object.fromEntries(url.searchParams.entries());
|
|
334
|
+
if (Object.keys(params).length > 0) {
|
|
335
|
+
for (const [key, val] of Object.entries(params)) {
|
|
336
|
+
const num = Number(val);
|
|
337
|
+
params[key] = !isNaN(num) && val !== '' ? num : val;
|
|
338
|
+
}
|
|
339
|
+
args.push(params);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const result = await value(...args);
|
|
343
|
+
return Response.json({ result });
|
|
344
|
+
} catch (e) {
|
|
345
|
+
return Response.json({ error: e.message }, { status: 500 });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Route: /execute - Run tests and scripts
|
|
350
|
+
let scriptResult = undefined;
|
|
351
|
+
let scriptError = null;
|
|
352
|
+
|
|
353
|
+
// USER_SCRIPT_CODE
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
// Run all pending tests
|
|
357
|
+
const testStart = Date.now();
|
|
358
|
+
const hasOnly = pendingTests.some(t => t.only);
|
|
359
|
+
const testsToRun = hasOnly ? pendingTests.filter(t => t.only || t.skip) : pendingTests;
|
|
360
|
+
|
|
361
|
+
for (const { name, fn, hooks, skip } of testsToRun) {
|
|
362
|
+
testResults.total++;
|
|
363
|
+
|
|
364
|
+
if (skip) {
|
|
365
|
+
testResults.skipped++;
|
|
366
|
+
testResults.tests.push({ name, passed: true, skipped: true, duration: 0 });
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const start = Date.now();
|
|
371
|
+
try {
|
|
372
|
+
// Run beforeEach hooks
|
|
373
|
+
if (hooks?.before) {
|
|
374
|
+
for (const hook of hooks.before) {
|
|
375
|
+
const hookResult = hook();
|
|
376
|
+
if (hookResult && typeof hookResult.then === 'function') {
|
|
377
|
+
await hookResult;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Run the test
|
|
383
|
+
const result = fn();
|
|
384
|
+
if (result && typeof result.then === 'function') {
|
|
385
|
+
await result;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Run afterEach hooks
|
|
389
|
+
if (hooks?.after) {
|
|
390
|
+
for (const hook of hooks.after) {
|
|
391
|
+
const hookResult = hook();
|
|
392
|
+
if (hookResult && typeof hookResult.then === 'function') {
|
|
393
|
+
await hookResult;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
testResults.passed++;
|
|
399
|
+
testResults.tests.push({ name, passed: true, duration: Date.now() - start });
|
|
400
|
+
} catch (e) {
|
|
401
|
+
testResults.failed++;
|
|
402
|
+
testResults.tests.push({
|
|
403
|
+
name,
|
|
404
|
+
passed: false,
|
|
405
|
+
error: e.message || String(e),
|
|
406
|
+
duration: Date.now() - start
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
testResults.duration = Date.now() - testStart;
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
const hasTests = pendingTests.length > 0;
|
|
415
|
+
const success = scriptError === null && (!hasTests || testResults.failed === 0);
|
|
416
|
+
|
|
417
|
+
return Response.json({
|
|
418
|
+
success,
|
|
419
|
+
value: scriptResult,
|
|
420
|
+
logs,
|
|
421
|
+
testResults: hasTests ? testResults : undefined,
|
|
422
|
+
error: scriptError || undefined,
|
|
423
|
+
duration: 0
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
};
|