ai-evaluate 2.1.6 → 2.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +90 -3
  2. package/dist/capnweb-bundle.d.ts +10 -0
  3. package/dist/capnweb-bundle.d.ts.map +1 -0
  4. package/dist/capnweb-bundle.js +2596 -0
  5. package/dist/capnweb-bundle.js.map +1 -0
  6. package/dist/evaluate.d.ts +1 -1
  7. package/dist/evaluate.d.ts.map +1 -1
  8. package/dist/evaluate.js +186 -7
  9. package/dist/evaluate.js.map +1 -1
  10. package/dist/index.d.ts +2 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/miniflare-pool.d.ts +109 -0
  15. package/dist/miniflare-pool.d.ts.map +1 -0
  16. package/dist/miniflare-pool.js +308 -0
  17. package/dist/miniflare-pool.js.map +1 -0
  18. package/dist/node.d.ts.map +1 -1
  19. package/dist/node.js +42 -10
  20. package/dist/node.js.map +1 -1
  21. package/dist/shared.d.ts +66 -0
  22. package/dist/shared.d.ts.map +1 -0
  23. package/dist/shared.js +169 -0
  24. package/dist/shared.js.map +1 -0
  25. package/dist/type-guards.d.ts +21 -0
  26. package/dist/type-guards.d.ts.map +1 -0
  27. package/dist/type-guards.js +216 -0
  28. package/dist/type-guards.js.map +1 -0
  29. package/dist/types.d.ts +17 -2
  30. package/dist/types.d.ts.map +1 -1
  31. package/dist/validation.d.ts +26 -0
  32. package/dist/validation.d.ts.map +1 -0
  33. package/dist/validation.js +104 -0
  34. package/dist/validation.js.map +1 -0
  35. package/dist/worker-template/code-transforms.d.ts +9 -0
  36. package/dist/worker-template/code-transforms.d.ts.map +1 -0
  37. package/dist/worker-template/code-transforms.js +28 -0
  38. package/dist/worker-template/code-transforms.js.map +1 -0
  39. package/{src/worker-template.d.ts → dist/worker-template/core.d.ts} +7 -15
  40. package/dist/worker-template/core.d.ts.map +1 -0
  41. package/dist/worker-template/core.js +502 -0
  42. package/dist/worker-template/core.js.map +1 -0
  43. package/dist/worker-template/helpers.d.ts +14 -0
  44. package/dist/worker-template/helpers.d.ts.map +1 -0
  45. package/dist/worker-template/helpers.js +79 -0
  46. package/dist/worker-template/helpers.js.map +1 -0
  47. package/dist/worker-template/index.d.ts +14 -0
  48. package/dist/worker-template/index.d.ts.map +1 -0
  49. package/dist/worker-template/index.js +19 -0
  50. package/dist/worker-template/index.js.map +1 -0
  51. package/dist/worker-template/sdk-generator.d.ts +17 -0
  52. package/dist/worker-template/sdk-generator.d.ts.map +1 -0
  53. package/{src/worker-template.js → dist/worker-template/sdk-generator.js} +377 -1506
  54. package/dist/worker-template/sdk-generator.js.map +1 -0
  55. package/dist/worker-template/test-generator.d.ts +16 -0
  56. package/dist/worker-template/test-generator.d.ts.map +1 -0
  57. package/dist/worker-template/test-generator.js +357 -0
  58. package/dist/worker-template/test-generator.js.map +1 -0
  59. package/dist/worker-template.d.ts +2 -2
  60. package/dist/worker-template.d.ts.map +1 -1
  61. package/dist/worker-template.js +64 -31
  62. package/dist/worker-template.js.map +1 -1
  63. package/example/package.json +7 -3
  64. package/example/src/index.ts +194 -40
  65. package/example/wrangler.jsonc +18 -2
  66. package/package.json +1 -3
  67. package/src/capnweb-bundle.ts +2596 -0
  68. package/src/evaluate.ts +216 -7
  69. package/src/index.ts +3 -1
  70. package/src/miniflare-pool.ts +395 -0
  71. package/src/node.ts +56 -11
  72. package/src/shared.ts +186 -0
  73. package/src/type-guards.ts +323 -0
  74. package/src/types.ts +18 -2
  75. package/src/validation.ts +120 -0
  76. package/src/worker-template/code-transforms.ts +32 -0
  77. package/src/worker-template/core.ts +557 -0
  78. package/src/worker-template/helpers.ts +90 -0
  79. package/src/worker-template/index.ts +23 -0
  80. package/src/{worker-template.ts → worker-template/sdk-generator.ts} +322 -1566
  81. package/src/worker-template/test-generator.ts +358 -0
  82. package/test/miniflare-pool.test.ts +246 -0
  83. package/test/node.test.ts +467 -0
  84. package/test/security.test.ts +1009 -0
  85. package/test/shared.test.ts +105 -0
  86. package/test/type-guards.test.ts +303 -0
  87. package/test/validation.test.ts +240 -0
  88. package/test/worker-template.test.ts +21 -19
  89. package/src/evaluate.js +0 -187
  90. package/src/index.js +0 -10
  91. package/src/node.d.ts +0 -17
  92. package/src/node.d.ts.map +0 -1
  93. package/src/node.js +0 -168
  94. package/src/node.js.map +0 -1
  95. package/src/types.d.ts +0 -172
  96. package/src/types.d.ts.map +0 -1
  97. package/src/types.js +0 -4
  98. package/src/types.js.map +0 -1
  99. package/src/worker-template.d.ts.map +0 -1
  100. package/src/worker-template.js.map +0 -1
@@ -0,0 +1,467 @@
1
+ import { describe, it, expect } from 'vitest'
2
+
3
+ describe('ai-evaluate/node', () => {
4
+ describe('JSX transformation', () => {
5
+ // Note: JSX transformation uses esbuild which may produce ESM-wrapped code
6
+ // These tests verify the JSX detection and transformation attempt
7
+
8
+ it('detects and attempts to transform simple JSX', async () => {
9
+ const { evaluate } = await import('../src/node.js')
10
+
11
+ // JSX code that would need transformation
12
+ const result = await evaluate({
13
+ module: `
14
+ function h(tag, props, ...children) {
15
+ return { tag, props, children }
16
+ }
17
+ exports.render = () => <div>Hello</div>
18
+ `,
19
+ script: 'return render()',
20
+ })
21
+
22
+ // The transformation is attempted - result depends on esbuild output format
23
+ // Either it succeeds or returns an error (not a crash)
24
+ expect(typeof result.success).toBe('boolean')
25
+ expect(Array.isArray(result.logs)).toBe(true)
26
+ })
27
+
28
+ it('detects JSX with props', async () => {
29
+ const { evaluate } = await import('../src/node.js')
30
+
31
+ const result = await evaluate({
32
+ module: `
33
+ function h(tag, props, ...children) {
34
+ return { tag, props, children }
35
+ }
36
+ const handler = () => 'clicked'
37
+ exports.render = () => <Button onClick={handler}>Click</Button>
38
+ `,
39
+ script: 'return render()',
40
+ })
41
+
42
+ // Verify the function doesn't crash and returns a valid result shape
43
+ expect(typeof result.success).toBe('boolean')
44
+ expect(typeof result.duration).toBe('number')
45
+ })
46
+
47
+ it('detects JSX fragments', async () => {
48
+ const { evaluate } = await import('../src/node.js')
49
+
50
+ const result = await evaluate({
51
+ module: `
52
+ function h(tag, props, ...children) {
53
+ return { tag, props, children }
54
+ }
55
+ function Fragment(props) {
56
+ return props.children
57
+ }
58
+ exports.render = () => <><span/><span/></>
59
+ `,
60
+ script: 'return render()',
61
+ })
62
+
63
+ // Verify graceful handling
64
+ expect(typeof result.success).toBe('boolean')
65
+ })
66
+
67
+ it('handles JSX transform failure gracefully', async () => {
68
+ const { evaluate } = await import('../src/node.js')
69
+
70
+ // Even with JSX that fails to transform correctly, evaluate should not throw
71
+ const result = await evaluate({
72
+ module: `
73
+ function h(tag, props, ...children) {
74
+ return { tag, props, children }
75
+ }
76
+ exports.element = <div>Test</div>
77
+ `,
78
+ script: 'return element',
79
+ })
80
+
81
+ // Should return a result (success or error), not throw
82
+ expect(result).toHaveProperty('success')
83
+ expect(result).toHaveProperty('logs')
84
+ expect(result).toHaveProperty('duration')
85
+ })
86
+
87
+ it('passes through non-JSX code unchanged', async () => {
88
+ const { evaluate } = await import('../src/node.js')
89
+
90
+ const result = await evaluate({
91
+ module: `
92
+ exports.add = (a, b) => a + b
93
+ `,
94
+ script: 'return add(2, 3)',
95
+ })
96
+ expect(result.success).toBe(true)
97
+ expect(result.value).toBe(5)
98
+ })
99
+
100
+ it('handles code that looks like JSX but is a string', async () => {
101
+ const { evaluate } = await import('../src/node.js')
102
+
103
+ // String literals with angle brackets may still be detected by the regex
104
+ // but the transformation should still produce valid code
105
+ const result = await evaluate({
106
+ module: `
107
+ exports.html = "Not JSX"
108
+ `,
109
+ script: 'return html',
110
+ })
111
+ expect(result.success).toBe(true)
112
+ expect(result.value).toBe('Not JSX')
113
+ })
114
+
115
+ it('handles empty module gracefully', async () => {
116
+ const { evaluate } = await import('../src/node.js')
117
+
118
+ const result = await evaluate({
119
+ script: 'return 123',
120
+ })
121
+ expect(result.success).toBe(true)
122
+ expect(result.value).toBe(123)
123
+ })
124
+ })
125
+
126
+ describe('Miniflare-specific behavior', () => {
127
+ it('evaluates without env binding (uses Miniflare)', async () => {
128
+ const { evaluate } = await import('../src/node.js')
129
+
130
+ // No env passed - should use Miniflare fallback
131
+ const result = await evaluate({
132
+ script: 'return 42',
133
+ })
134
+ expect(result.success).toBe(true)
135
+ expect(result.value).toBe(42)
136
+ })
137
+
138
+ it('handles timeout with AbortController', async () => {
139
+ const { evaluate } = await import('../src/node.js')
140
+
141
+ const result = await evaluate({
142
+ script: `
143
+ // Infinite loop that should be aborted
144
+ while(true) {}
145
+ return 'never'
146
+ `,
147
+ timeout: 100,
148
+ })
149
+ expect(result.success).toBe(false)
150
+ expect(result.error).toContain('Timeout')
151
+ }, 10000)
152
+
153
+ it('blocks network via outboundService when fetch: null', async () => {
154
+ const { evaluate } = await import('../src/node.js')
155
+
156
+ const result = await evaluate({
157
+ script: `
158
+ try {
159
+ await fetch('https://example.com')
160
+ return 'fetch succeeded'
161
+ } catch (e) {
162
+ return 'fetch blocked: ' + e.message
163
+ }
164
+ `,
165
+ fetch: null,
166
+ })
167
+ // Network blocking via outboundService may cause different errors
168
+ // The key is that fetch doesn't succeed
169
+ if (result.success) {
170
+ expect(result.value).toContain('fetch blocked')
171
+ } else {
172
+ // Network blocking might cause an error at the worker level
173
+ expect(result.error).toBeDefined()
174
+ }
175
+ })
176
+
177
+ it('allows network when fetch is not null', async () => {
178
+ const { evaluate } = await import('../src/node.js')
179
+
180
+ // This test verifies network is allowed by default
181
+ // We don't actually make a network call, just verify the option is respected
182
+ const result = await evaluate({
183
+ script: `
184
+ // Verify fetch exists and is callable
185
+ return typeof globalThis.fetch === 'function'
186
+ `,
187
+ })
188
+ expect(result.success).toBe(true)
189
+ expect(result.value).toBe(true)
190
+ })
191
+
192
+ it('disposes Miniflare instance after execution', async () => {
193
+ const { evaluate } = await import('../src/node.js')
194
+
195
+ // Execute multiple evaluations to ensure proper cleanup
196
+ for (let i = 0; i < 3; i++) {
197
+ const result = await evaluate({
198
+ script: `return ${i}`,
199
+ })
200
+ expect(result.success).toBe(true)
201
+ expect(result.value).toBe(i)
202
+ }
203
+ })
204
+
205
+ it('returns duration in result', async () => {
206
+ const { evaluate } = await import('../src/node.js')
207
+
208
+ const result = await evaluate({
209
+ script: 'return true',
210
+ })
211
+ expect(result.success).toBe(true)
212
+ expect(typeof result.duration).toBe('number')
213
+ expect(result.duration).toBeGreaterThanOrEqual(0)
214
+ })
215
+
216
+ it('captures console output in Miniflare', async () => {
217
+ const { evaluate } = await import('../src/node.js')
218
+
219
+ const result = await evaluate({
220
+ script: `
221
+ console.log('log message');
222
+ console.warn('warn message');
223
+ console.error('error message');
224
+ return 'done'
225
+ `,
226
+ })
227
+ expect(result.success).toBe(true)
228
+ expect(result.logs.length).toBeGreaterThanOrEqual(3)
229
+ expect(result.logs.some((l) => l.level === 'log' && l.message === 'log message')).toBe(true)
230
+ expect(result.logs.some((l) => l.level === 'warn' && l.message === 'warn message')).toBe(true)
231
+ expect(result.logs.some((l) => l.level === 'error' && l.message === 'error message')).toBe(
232
+ true
233
+ )
234
+ })
235
+ })
236
+
237
+ describe('esbuild optional dependency', () => {
238
+ it('esbuild is available in dev environment', async () => {
239
+ // esbuild should be available in dev
240
+ const esbuild = await import('esbuild').catch(() => null)
241
+ expect(esbuild).not.toBeNull()
242
+ expect(typeof esbuild?.transform).toBe('function')
243
+ })
244
+
245
+ it('graceful fallback when code has no JSX', async () => {
246
+ const { evaluate } = await import('../src/node.js')
247
+
248
+ // Code without JSX should work regardless of esbuild
249
+ const result = await evaluate({
250
+ module: `
251
+ exports.multiply = (a, b) => a * b
252
+ `,
253
+ script: 'return multiply(6, 7)',
254
+ })
255
+ expect(result.success).toBe(true)
256
+ expect(result.value).toBe(42)
257
+ })
258
+
259
+ it('uses esbuild for JSX detection patterns', async () => {
260
+ // Test that JSX patterns are detected
261
+ // The containsJSX function should identify these patterns
262
+ const patterns = [
263
+ '<div>content</div>', // lowercase tag
264
+ '<Button />', // uppercase tag
265
+ '<>fragment</>', // fragment
266
+ 'return <Component />', // return JSX
267
+ 'return (\n<div>\n</div>\n)', // multiline return JSX
268
+ ]
269
+
270
+ // All these should be detected as JSX
271
+ for (const pattern of patterns) {
272
+ const jsxPattern = /<[A-Z][a-zA-Z0-9]*[\s/>]|<[a-z][a-z0-9-]*[\s/>]|<>|<\/>/
273
+ const jsxReturnPattern = /return\s*\(\s*<|return\s+<[A-Za-z]/
274
+ const isJSX = jsxPattern.test(pattern) || jsxReturnPattern.test(pattern)
275
+ expect(isJSX).toBe(true)
276
+ }
277
+ })
278
+
279
+ it('does not detect non-JSX patterns', async () => {
280
+ // These should NOT be detected as JSX
281
+ const patterns = [
282
+ 'const x = a < b ? c : d', // comparison (no space after <)
283
+ '5 > 3', // comparison
284
+ 'arr.map(x => x * 2)', // arrow function
285
+ ]
286
+
287
+ for (const pattern of patterns) {
288
+ const jsxPattern = /<[A-Z][a-zA-Z0-9]*[\s/>]|<[a-z][a-z0-9-]*[\s/>]|<>|<\/>/
289
+ const jsxReturnPattern = /return\s*\(\s*<|return\s+<[A-Za-z]/
290
+ const isJSX = jsxPattern.test(pattern) || jsxReturnPattern.test(pattern)
291
+ expect(isJSX).toBe(false)
292
+ }
293
+ })
294
+ })
295
+
296
+ describe('import from ai-evaluate/node', () => {
297
+ it('exports evaluate function', async () => {
298
+ const nodeModule = await import('../src/node.js')
299
+ expect(typeof nodeModule.evaluate).toBe('function')
300
+ })
301
+
302
+ it('exports createEvaluator function', async () => {
303
+ const nodeModule = await import('../src/node.js')
304
+ expect(typeof nodeModule.createEvaluator).toBe('function')
305
+ })
306
+
307
+ it('createEvaluator returns working evaluator', async () => {
308
+ const { createEvaluator } = await import('../src/node.js')
309
+
310
+ const evaluator = createEvaluator()
311
+ expect(typeof evaluator).toBe('function')
312
+
313
+ const result = await evaluator({
314
+ script: 'return "hello from evaluator"',
315
+ })
316
+ expect(result.success).toBe(true)
317
+ expect(result.value).toBe('hello from evaluator')
318
+ })
319
+
320
+ it('evaluate function works standalone', async () => {
321
+ const { evaluate } = await import('../src/node.js')
322
+
323
+ const result = await evaluate({
324
+ module: `
325
+ exports.greet = (name) => 'Hello, ' + name
326
+ `,
327
+ script: 'return greet("World")',
328
+ })
329
+ expect(result.success).toBe(true)
330
+ expect(result.value).toBe('Hello, World')
331
+ })
332
+
333
+ it('re-exports types from types.js', async () => {
334
+ // Types are compile-time only, just verify module loads
335
+ const nodeModule = await import('../src/node.js')
336
+ expect(nodeModule).toBeDefined()
337
+ // Verify the module exports the expected functions
338
+ expect(Object.keys(nodeModule)).toContain('evaluate')
339
+ expect(Object.keys(nodeModule)).toContain('createEvaluator')
340
+ })
341
+ })
342
+
343
+ describe('error handling', () => {
344
+ it('handles evaluation errors gracefully', async () => {
345
+ const { evaluate } = await import('../src/node.js')
346
+
347
+ const result = await evaluate({
348
+ script: 'throw new Error("intentional error")',
349
+ })
350
+ expect(result.success).toBe(false)
351
+ expect(result.error).toContain('intentional error')
352
+ })
353
+
354
+ it('handles syntax errors in module', async () => {
355
+ const { evaluate } = await import('../src/node.js')
356
+
357
+ const result = await evaluate({
358
+ module: 'exports.foo = {;', // Invalid syntax
359
+ })
360
+ expect(result.success).toBe(false)
361
+ expect(result.error).toBeDefined()
362
+ })
363
+
364
+ it('returns error for undefined function calls', async () => {
365
+ const { evaluate } = await import('../src/node.js')
366
+
367
+ const result = await evaluate({
368
+ script: 'return undefinedFunction()',
369
+ })
370
+ expect(result.success).toBe(false)
371
+ expect(result.error).toBeDefined()
372
+ })
373
+
374
+ it('handles async errors in script', async () => {
375
+ const { evaluate } = await import('../src/node.js')
376
+
377
+ const result = await evaluate({
378
+ script: `
379
+ return Promise.reject(new Error('async failure'))
380
+ `,
381
+ })
382
+ expect(result.success).toBe(false)
383
+ expect(result.error).toContain('async failure')
384
+ })
385
+
386
+ it('catches module initialization errors', async () => {
387
+ const { evaluate } = await import('../src/node.js')
388
+
389
+ const result = await evaluate({
390
+ module: 'throw new Error("init error")',
391
+ script: 'return true',
392
+ })
393
+ // Module errors are logged, and execution may continue or fail
394
+ expect(result).toHaveProperty('logs')
395
+ })
396
+
397
+ it('returns proper error shape on failure', async () => {
398
+ const { evaluate } = await import('../src/node.js')
399
+
400
+ const result = await evaluate({
401
+ script: 'throw new Error("test")',
402
+ })
403
+ expect(result).toHaveProperty('success', false)
404
+ expect(result).toHaveProperty('error')
405
+ expect(result).toHaveProperty('logs')
406
+ expect(result).toHaveProperty('duration')
407
+ expect(typeof result.error).toBe('string')
408
+ expect(typeof result.duration).toBe('number')
409
+ })
410
+ })
411
+
412
+ describe('test execution', () => {
413
+ it('runs tests with passing results', async () => {
414
+ const { evaluate } = await import('../src/node.js')
415
+
416
+ const result = await evaluate({
417
+ tests: `
418
+ describe('math', () => {
419
+ it('adds numbers', () => {
420
+ expect(1 + 1).toBe(2);
421
+ });
422
+ });
423
+ `,
424
+ })
425
+ expect(result.success).toBe(true)
426
+ expect(result.testResults?.total).toBe(1)
427
+ expect(result.testResults?.passed).toBe(1)
428
+ })
429
+
430
+ it('runs tests with failing results', async () => {
431
+ const { evaluate } = await import('../src/node.js')
432
+
433
+ const result = await evaluate({
434
+ tests: `
435
+ it('fails', () => {
436
+ expect(1).toBe(2);
437
+ });
438
+ `,
439
+ })
440
+ expect(result.success).toBe(false)
441
+ expect(result.testResults?.failed).toBe(1)
442
+ })
443
+
444
+ it('combines module and tests', async () => {
445
+ const { evaluate } = await import('../src/node.js')
446
+
447
+ const result = await evaluate({
448
+ module: `
449
+ exports.double = (n) => n * 2
450
+ `,
451
+ tests: `
452
+ describe('double', () => {
453
+ it('doubles 5', () => {
454
+ expect(double(5)).toBe(10);
455
+ });
456
+ it('doubles 0', () => {
457
+ expect(double(0)).toBe(0);
458
+ });
459
+ });
460
+ `,
461
+ })
462
+ expect(result.success).toBe(true)
463
+ expect(result.testResults?.total).toBe(2)
464
+ expect(result.testResults?.passed).toBe(2)
465
+ })
466
+ })
467
+ })