@positronic/spec 0.0.3 → 0.0.5

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/api.js ADDED
@@ -0,0 +1,948 @@
1
+ export async function testStatus(fetch) {
2
+ try {
3
+ const request = new Request('http://example.com/status', {
4
+ method: 'GET',
5
+ });
6
+ const response = await fetch(request);
7
+ if (!response.ok) {
8
+ console.error(`Status endpoint returned ${response.status}`);
9
+ return false;
10
+ }
11
+ const data = await response.json();
12
+ if (data.ready !== true) {
13
+ console.error(`Expected { ready: true }, got ${JSON.stringify(data)}`);
14
+ return false;
15
+ }
16
+ return true;
17
+ }
18
+ catch (error) {
19
+ console.error(`Failed to test status endpoint:`, error);
20
+ return false;
21
+ }
22
+ }
23
+ export const resources = {
24
+ /**
25
+ * Test GET /resources - List all resources
26
+ */
27
+ async list(fetch) {
28
+ try {
29
+ const request = new Request('http://example.com/resources', {
30
+ method: 'GET',
31
+ });
32
+ const response = await fetch(request);
33
+ if (!response.ok) {
34
+ console.error(`GET /resources returned ${response.status}`);
35
+ return false;
36
+ }
37
+ const data = await response.json();
38
+ // Validate response structure
39
+ if (!Array.isArray(data.resources)) {
40
+ console.error(`Expected resources to be an array, got ${typeof data.resources}`);
41
+ return false;
42
+ }
43
+ if (typeof data.truncated !== 'boolean') {
44
+ console.error(`Expected truncated to be boolean, got ${typeof data.truncated}`);
45
+ return false;
46
+ }
47
+ if (typeof data.count !== 'number') {
48
+ console.error(`Expected count to be number, got ${typeof data.count}`);
49
+ return false;
50
+ }
51
+ // Validate each resource has required fields
52
+ for (const resource of data.resources) {
53
+ if (!resource.key ||
54
+ !resource.type ||
55
+ typeof resource.size !== 'number' ||
56
+ !resource.lastModified ||
57
+ typeof resource.local !== 'boolean') {
58
+ console.error(`Resource missing required fields: ${JSON.stringify(resource)}`);
59
+ return false;
60
+ }
61
+ if (!['text', 'binary'].includes(resource.type)) {
62
+ console.error(`Invalid resource type: ${resource.type}`);
63
+ return false;
64
+ }
65
+ }
66
+ return true;
67
+ }
68
+ catch (error) {
69
+ console.error(`Failed to test GET /resources:`, error);
70
+ return false;
71
+ }
72
+ },
73
+ /**
74
+ * Test POST /resources - Upload a resource
75
+ */
76
+ async upload(fetch) {
77
+ try {
78
+ const formData = new FormData();
79
+ formData.append('file', new Blob(['test content'], { type: 'text/plain' }), 'test.txt');
80
+ formData.append('type', 'text');
81
+ formData.append('key', 'test-resource.txt');
82
+ formData.append('local', 'false');
83
+ const request = new Request('http://example.com/resources', {
84
+ method: 'POST',
85
+ body: formData,
86
+ });
87
+ const response = await fetch(request);
88
+ if (response.status !== 201) {
89
+ console.error(`POST /resources returned ${response.status}, expected 201`);
90
+ return false;
91
+ }
92
+ const data = await response.json();
93
+ // Validate response has required fields
94
+ if (!data.key ||
95
+ !data.type ||
96
+ typeof data.size !== 'number' ||
97
+ !data.lastModified ||
98
+ typeof data.local !== 'boolean') {
99
+ console.error(`Response missing required fields: ${JSON.stringify(data)}`);
100
+ return false;
101
+ }
102
+ if (data.key !== 'test-resource.txt') {
103
+ console.error(`Expected key to be 'test-resource.txt', got ${data.key}`);
104
+ return false;
105
+ }
106
+ if (data.type !== 'text') {
107
+ console.error(`Expected type to be 'text', got ${data.type}`);
108
+ return false;
109
+ }
110
+ if (data.local !== false) {
111
+ console.error(`Expected local to be false, got ${data.local}`);
112
+ return false;
113
+ }
114
+ return true;
115
+ }
116
+ catch (error) {
117
+ console.error(`Failed to test POST /resources:`, error);
118
+ return false;
119
+ }
120
+ },
121
+ /**
122
+ * Test DELETE /resources/:key - Delete a specific resource
123
+ */
124
+ async delete(fetch, key) {
125
+ try {
126
+ const request = new Request(`http://example.com/resources/${encodeURIComponent(key)}`, {
127
+ method: 'DELETE',
128
+ });
129
+ const response = await fetch(request);
130
+ if (response.status !== 204) {
131
+ console.error(`DELETE /resources/${key} returned ${response.status}, expected 204`);
132
+ return false;
133
+ }
134
+ return true;
135
+ }
136
+ catch (error) {
137
+ console.error(`Failed to test DELETE /resources/${key}:`, error);
138
+ return false;
139
+ }
140
+ },
141
+ /**
142
+ * Test DELETE /resources - Bulk delete all resources (dev mode only)
143
+ */
144
+ async deleteAll(fetch) {
145
+ try {
146
+ const request = new Request('http://example.com/resources', {
147
+ method: 'DELETE',
148
+ });
149
+ const response = await fetch(request);
150
+ // In production mode, this should return 403
151
+ if (response.status === 403) {
152
+ const data = await response.json();
153
+ if (data.error === 'Bulk delete is only available in development mode') {
154
+ // This is expected behavior in production
155
+ return true;
156
+ }
157
+ }
158
+ if (response.status !== 200) {
159
+ console.error(`DELETE /resources returned ${response.status}, expected 200 or 403`);
160
+ return false;
161
+ }
162
+ const data = await response.json();
163
+ if (typeof data.deletedCount !== 'number') {
164
+ console.error(`Expected deletedCount to be number, got ${typeof data.deletedCount}`);
165
+ return false;
166
+ }
167
+ return true;
168
+ }
169
+ catch (error) {
170
+ console.error(`Failed to test DELETE /resources:`, error);
171
+ return false;
172
+ }
173
+ },
174
+ /**
175
+ * Test POST /resources/presigned-link - Generate presigned URL for upload
176
+ */
177
+ async generatePresignedLink(fetch) {
178
+ try {
179
+ const request = new Request('http://example.com/resources/presigned-link', {
180
+ method: 'POST',
181
+ headers: {
182
+ 'Content-Type': 'application/json',
183
+ },
184
+ body: JSON.stringify({
185
+ key: 'test-files/large-video.mp4',
186
+ type: 'binary',
187
+ size: 150 * 1024 * 1024, // 150MB - larger than Worker limit
188
+ }),
189
+ });
190
+ const response = await fetch(request);
191
+ // If credentials are not configured, expect 400
192
+ if (response.status === 400) {
193
+ const data = await response.json();
194
+ // This is acceptable - implementation may not have credentials configured
195
+ console.log('Presigned URL generation not available - this is acceptable');
196
+ return true;
197
+ }
198
+ if (response.status !== 200) {
199
+ console.error(`POST /resources/presigned-link returned ${response.status}, expected 200 or 400`);
200
+ return false;
201
+ }
202
+ const data = await response.json();
203
+ // Validate response structure (backend-agnostic)
204
+ if (!data.url || typeof data.url !== 'string') {
205
+ console.error(`Expected url to be string, got ${typeof data.url}`);
206
+ return false;
207
+ }
208
+ if (!data.method || data.method !== 'PUT') {
209
+ console.error(`Expected method to be 'PUT', got ${data.method}`);
210
+ return false;
211
+ }
212
+ if (typeof data.expiresIn !== 'number' || data.expiresIn <= 0) {
213
+ console.error(`Expected expiresIn to be positive number, got ${data.expiresIn}`);
214
+ return false;
215
+ }
216
+ // Basic URL validation - just ensure it's a valid URL
217
+ try {
218
+ new URL(data.url);
219
+ console.log('Presigned URL structure validated successfully');
220
+ }
221
+ catch (error) {
222
+ console.error(`Invalid URL returned: ${data.url}`);
223
+ return false;
224
+ }
225
+ return true;
226
+ }
227
+ catch (error) {
228
+ console.error(`Failed to test POST /resources/presigned-link:`, error);
229
+ return false;
230
+ }
231
+ },
232
+ };
233
+ export const brains = {
234
+ /**
235
+ * Test POST /brains/runs - Create a new brain run
236
+ */
237
+ async run(fetch, brainName) {
238
+ try {
239
+ const request = new Request('http://example.com/brains/runs', {
240
+ method: 'POST',
241
+ headers: {
242
+ 'Content-Type': 'application/json',
243
+ },
244
+ body: JSON.stringify({ brainName }),
245
+ });
246
+ const response = await fetch(request);
247
+ if (response.status !== 201) {
248
+ console.error(`POST /brains/runs returned ${response.status}, expected 201`);
249
+ return null;
250
+ }
251
+ const data = await response.json();
252
+ if (!data.brainRunId || typeof data.brainRunId !== 'string') {
253
+ console.error(`Expected brainRunId to be string, got ${typeof data.brainRunId}`);
254
+ return null;
255
+ }
256
+ return data.brainRunId;
257
+ }
258
+ catch (error) {
259
+ console.error(`Failed to test POST /brains/runs:`, error);
260
+ return null;
261
+ }
262
+ },
263
+ /**
264
+ * Test POST /brains/runs with non-existent brain - Should return 404
265
+ */
266
+ async runNotFound(fetch, nonExistentBrainName) {
267
+ try {
268
+ const request = new Request('http://example.com/brains/runs', {
269
+ method: 'POST',
270
+ headers: {
271
+ 'Content-Type': 'application/json',
272
+ },
273
+ body: JSON.stringify({ brainName: nonExistentBrainName }),
274
+ });
275
+ const response = await fetch(request);
276
+ if (response.status !== 404) {
277
+ console.error(`POST /brains/runs with non-existent brain returned ${response.status}, expected 404`);
278
+ return false;
279
+ }
280
+ const data = await response.json();
281
+ if (!data.error || typeof data.error !== 'string') {
282
+ console.error(`Expected error to be string, got ${typeof data.error}`);
283
+ return false;
284
+ }
285
+ // Check that the error message mentions the brain name
286
+ if (!data.error.includes(nonExistentBrainName)) {
287
+ console.error(`Expected error to mention brain name '${nonExistentBrainName}', got: ${data.error}`);
288
+ return false;
289
+ }
290
+ // Check that the error message follows expected format
291
+ const expectedPattern = new RegExp(`Brain '${nonExistentBrainName}' not found`);
292
+ if (!expectedPattern.test(data.error)) {
293
+ console.error(`Expected error message to match pattern "Brain '${nonExistentBrainName}' not found", got: ${data.error}`);
294
+ return false;
295
+ }
296
+ return true;
297
+ }
298
+ catch (error) {
299
+ console.error(`Failed to test POST /brains/runs with non-existent brain:`, error);
300
+ return false;
301
+ }
302
+ },
303
+ /**
304
+ * Test GET /brains/runs/:runId/watch - Watch a brain run via SSE
305
+ */
306
+ async watch(fetch, runId) {
307
+ try {
308
+ const request = new Request(`http://example.com/brains/runs/${runId}/watch`, {
309
+ method: 'GET',
310
+ });
311
+ const response = await fetch(request);
312
+ if (!response.ok) {
313
+ console.error(`GET /brains/runs/${runId}/watch returned ${response.status}`);
314
+ return false;
315
+ }
316
+ // Check that it's an event stream
317
+ const contentType = response.headers.get('content-type');
318
+ if (!contentType || !contentType.includes('text/event-stream')) {
319
+ console.error(`Expected content-type to be text/event-stream, got ${contentType}`);
320
+ return false;
321
+ }
322
+ // Read a bit of the stream to verify it's actually SSE format
323
+ if (response.body) {
324
+ const reader = response.body.getReader();
325
+ try {
326
+ // Read first chunk
327
+ const { value } = await reader.read();
328
+ if (value) {
329
+ const text = new TextDecoder().decode(value);
330
+ // SSE data should contain "data: " lines
331
+ if (!text.includes('data: ')) {
332
+ console.error(`Expected SSE format with "data: " prefix, got: ${text.substring(0, 100)}`);
333
+ return false;
334
+ }
335
+ }
336
+ }
337
+ finally {
338
+ // Always cancel the reader to clean up
339
+ await reader.cancel();
340
+ }
341
+ }
342
+ return true;
343
+ }
344
+ catch (error) {
345
+ console.error(`Failed to test GET /brains/runs/${runId}/watch:`, error);
346
+ return false;
347
+ }
348
+ },
349
+ /**
350
+ * Test GET /brains/:brainName/history - Get history of brain runs
351
+ */
352
+ async history(fetch, brainName, limit) {
353
+ try {
354
+ const url = new URL('http://example.com/brains/' + brainName + '/history');
355
+ if (limit !== undefined) {
356
+ url.searchParams.set('limit', limit.toString());
357
+ }
358
+ const request = new Request(url.toString(), {
359
+ method: 'GET',
360
+ });
361
+ const response = await fetch(request);
362
+ if (!response.ok) {
363
+ console.error(`GET /brains/${brainName}/history returned ${response.status}`);
364
+ return false;
365
+ }
366
+ const data = await response.json();
367
+ // Validate response structure
368
+ if (!data.runs || !Array.isArray(data.runs)) {
369
+ console.error(`Expected runs to be an array, got ${typeof data.runs}`);
370
+ return false;
371
+ }
372
+ // Validate each run has required fields
373
+ for (const run of data.runs) {
374
+ if (!run.brainRunId ||
375
+ !run.brainTitle ||
376
+ !run.type ||
377
+ !run.status ||
378
+ typeof run.createdAt !== 'number') {
379
+ console.error(`Run missing required fields: ${JSON.stringify(run)}`);
380
+ return false;
381
+ }
382
+ }
383
+ return true;
384
+ }
385
+ catch (error) {
386
+ console.error(`Failed to test GET /brains/${brainName}/history:`, error);
387
+ return false;
388
+ }
389
+ },
390
+ /**
391
+ * Test GET /brains/watch - Watch all running brains
392
+ */
393
+ async watchAll(fetch) {
394
+ try {
395
+ const request = new Request('http://example.com/brains/watch', {
396
+ method: 'GET',
397
+ });
398
+ const response = await fetch(request);
399
+ if (!response.ok) {
400
+ console.error(`GET /brains/watch returned ${response.status}`);
401
+ return false;
402
+ }
403
+ // Check that it's an event stream
404
+ const contentType = response.headers.get('content-type');
405
+ if (!contentType || !contentType.includes('text/event-stream')) {
406
+ console.error(`Expected content-type to be text/event-stream, got ${contentType}`);
407
+ return false;
408
+ }
409
+ // Read a bit of the stream to verify it's actually SSE format
410
+ if (response.body) {
411
+ const reader = response.body.getReader();
412
+ try {
413
+ // Read first chunk
414
+ const { value } = await reader.read();
415
+ if (value) {
416
+ const text = new TextDecoder().decode(value);
417
+ // SSE data should contain "data: " lines
418
+ if (!text.includes('data: ')) {
419
+ console.error(`Expected SSE format with "data: " prefix, got: ${text.substring(0, 100)}`);
420
+ return false;
421
+ }
422
+ }
423
+ }
424
+ finally {
425
+ // Always cancel the reader to clean up
426
+ await reader.cancel();
427
+ }
428
+ }
429
+ return true;
430
+ }
431
+ catch (error) {
432
+ console.error(`Failed to test GET /brains/watch:`, error);
433
+ return false;
434
+ }
435
+ },
436
+ /**
437
+ * Test GET /brains - List all brains
438
+ */
439
+ async list(fetch) {
440
+ try {
441
+ const request = new Request('http://example.com/brains', {
442
+ method: 'GET',
443
+ });
444
+ const response = await fetch(request);
445
+ if (!response.ok) {
446
+ console.error(`GET /brains returned ${response.status}`);
447
+ return false;
448
+ }
449
+ const data = await response.json();
450
+ // Validate response structure
451
+ if (!Array.isArray(data.brains)) {
452
+ console.error(`Expected brains to be an array, got ${typeof data.brains}`);
453
+ return false;
454
+ }
455
+ if (typeof data.count !== 'number') {
456
+ console.error(`Expected count to be number, got ${typeof data.count}`);
457
+ return false;
458
+ }
459
+ // Validate each brain has required fields
460
+ for (const brain of data.brains) {
461
+ if (!brain.name ||
462
+ typeof brain.name !== 'string' ||
463
+ !brain.title ||
464
+ typeof brain.title !== 'string' ||
465
+ !brain.description ||
466
+ typeof brain.description !== 'string') {
467
+ console.error(`Brain missing required fields or has invalid types: ${JSON.stringify(brain)}`);
468
+ return false;
469
+ }
470
+ }
471
+ return true;
472
+ }
473
+ catch (error) {
474
+ console.error(`Failed to test GET /brains:`, error);
475
+ return false;
476
+ }
477
+ },
478
+ /**
479
+ * Test GET /brains/:brainName - Get brain structure
480
+ */
481
+ async show(fetch, brainName) {
482
+ try {
483
+ const request = new Request(`http://example.com/brains/${encodeURIComponent(brainName)}`, {
484
+ method: 'GET',
485
+ });
486
+ const response = await fetch(request);
487
+ if (!response.ok) {
488
+ console.error(`GET /brains/${brainName} returned ${response.status}`);
489
+ return false;
490
+ }
491
+ const data = await response.json();
492
+ // Validate response structure
493
+ if (!data.name || typeof data.name !== 'string') {
494
+ console.error(`Expected name to be string, got ${typeof data.name}`);
495
+ return false;
496
+ }
497
+ if (!data.title || typeof data.title !== 'string') {
498
+ console.error(`Expected title to be string, got ${typeof data.title}`);
499
+ return false;
500
+ }
501
+ if (!Array.isArray(data.steps)) {
502
+ console.error(`Expected steps to be an array, got ${typeof data.steps}`);
503
+ return false;
504
+ }
505
+ // Validate each step
506
+ for (const step of data.steps) {
507
+ if (!step.type || !['step', 'brain'].includes(step.type)) {
508
+ console.error(`Invalid step type: ${step.type}`);
509
+ return false;
510
+ }
511
+ if (!step.title || typeof step.title !== 'string') {
512
+ console.error(`Step missing title or has invalid type: ${JSON.stringify(step)}`);
513
+ return false;
514
+ }
515
+ // If it's a brain step, validate the inner brain recursively
516
+ if (step.type === 'brain' && step.innerBrain) {
517
+ if (!step.innerBrain.title ||
518
+ typeof step.innerBrain.title !== 'string') {
519
+ console.error(`Inner brain missing title: ${JSON.stringify(step.innerBrain)}`);
520
+ return false;
521
+ }
522
+ if (!Array.isArray(step.innerBrain.steps)) {
523
+ console.error(`Inner brain missing steps array: ${JSON.stringify(step.innerBrain)}`);
524
+ return false;
525
+ }
526
+ }
527
+ }
528
+ return true;
529
+ }
530
+ catch (error) {
531
+ console.error(`Failed to test GET /brains/${brainName}:`, error);
532
+ return false;
533
+ }
534
+ },
535
+ /**
536
+ * Test GET /brains/:brainName/active-runs - Get active/running brain runs
537
+ */
538
+ async activeRuns(fetch, brainName) {
539
+ try {
540
+ const request = new Request(`http://example.com/brains/${encodeURIComponent(brainName)}/active-runs`, {
541
+ method: 'GET',
542
+ });
543
+ const response = await fetch(request);
544
+ if (!response.ok) {
545
+ console.error(`GET /brains/${brainName}/active-runs returned ${response.status}`);
546
+ return false;
547
+ }
548
+ const data = await response.json();
549
+ // Validate response structure
550
+ if (!data.runs || !Array.isArray(data.runs)) {
551
+ console.error(`Expected runs to be an array, got ${typeof data.runs}`);
552
+ return false;
553
+ }
554
+ // Validate each run has required fields
555
+ for (const run of data.runs) {
556
+ if (!run.brainRunId ||
557
+ !run.brainTitle ||
558
+ !run.type ||
559
+ !run.status ||
560
+ typeof run.createdAt !== 'number') {
561
+ console.error(`Active run missing required fields: ${JSON.stringify(run)}`);
562
+ return false;
563
+ }
564
+ // All active runs should have status 'RUNNING'
565
+ if (run.status !== 'RUNNING') {
566
+ console.error(`Expected active run status to be 'RUNNING', got ${run.status}`);
567
+ return false;
568
+ }
569
+ }
570
+ return true;
571
+ }
572
+ catch (error) {
573
+ console.error(`Failed to test GET /brains/${brainName}/active-runs:`, error);
574
+ return false;
575
+ }
576
+ },
577
+ /**
578
+ * Test POST /brains/runs/rerun - Rerun an existing brain run
579
+ */
580
+ async rerun(fetch, brainName, runId, startsAt, stopsAfter) {
581
+ try {
582
+ const body = { brainName };
583
+ if (runId)
584
+ body.runId = runId;
585
+ if (startsAt !== undefined)
586
+ body.startsAt = startsAt;
587
+ if (stopsAfter !== undefined)
588
+ body.stopsAfter = stopsAfter;
589
+ const request = new Request('http://example.com/brains/runs/rerun', {
590
+ method: 'POST',
591
+ headers: {
592
+ 'Content-Type': 'application/json',
593
+ },
594
+ body: JSON.stringify(body),
595
+ });
596
+ const response = await fetch(request);
597
+ if (response.status !== 201) {
598
+ console.error(`POST /brains/runs/rerun returned ${response.status}, expected 201`);
599
+ return null;
600
+ }
601
+ const data = await response.json();
602
+ if (!data.brainRunId || typeof data.brainRunId !== 'string') {
603
+ console.error(`Expected brainRunId to be string, got ${typeof data.brainRunId}`);
604
+ return null;
605
+ }
606
+ return data.brainRunId;
607
+ }
608
+ catch (error) {
609
+ console.error(`Failed to test POST /brains/runs/rerun:`, error);
610
+ return null;
611
+ }
612
+ },
613
+ };
614
+ export const schedules = {
615
+ /**
616
+ * Test POST /brains/schedules - Create a new schedule
617
+ */
618
+ async create(fetch, brainName, cronExpression) {
619
+ try {
620
+ const request = new Request('http://example.com/brains/schedules', {
621
+ method: 'POST',
622
+ headers: {
623
+ 'Content-Type': 'application/json',
624
+ },
625
+ body: JSON.stringify({ brainName, cronExpression }),
626
+ });
627
+ const response = await fetch(request);
628
+ if (response.status !== 201) {
629
+ console.error(`POST /brains/schedules returned ${response.status}, expected 201`);
630
+ return null;
631
+ }
632
+ const data = await response.json();
633
+ // Validate response structure
634
+ if (!data.id || typeof data.id !== 'string') {
635
+ console.error(`Expected id to be string, got ${typeof data.id}`);
636
+ return null;
637
+ }
638
+ if (data.brainName !== brainName) {
639
+ console.error(`Expected brainName to be '${brainName}', got ${data.brainName}`);
640
+ return null;
641
+ }
642
+ if (data.cronExpression !== cronExpression) {
643
+ console.error(`Expected cronExpression to be '${cronExpression}', got ${data.cronExpression}`);
644
+ return null;
645
+ }
646
+ if (typeof data.enabled !== 'boolean') {
647
+ console.error(`Expected enabled to be boolean, got ${typeof data.enabled}`);
648
+ return null;
649
+ }
650
+ if (typeof data.createdAt !== 'number') {
651
+ console.error(`Expected createdAt to be number, got ${typeof data.createdAt}`);
652
+ return null;
653
+ }
654
+ return data.id;
655
+ }
656
+ catch (error) {
657
+ console.error(`Failed to test POST /brains/schedules:`, error);
658
+ return null;
659
+ }
660
+ },
661
+ /**
662
+ * Test GET /brains/schedules - List all schedules
663
+ */
664
+ async list(fetch) {
665
+ try {
666
+ const request = new Request('http://example.com/brains/schedules', {
667
+ method: 'GET',
668
+ });
669
+ const response = await fetch(request);
670
+ if (!response.ok) {
671
+ console.error(`GET /brains/schedules returned ${response.status}`);
672
+ return false;
673
+ }
674
+ const data = await response.json();
675
+ // Validate response structure
676
+ if (!Array.isArray(data.schedules)) {
677
+ console.error(`Expected schedules to be an array, got ${typeof data.schedules}`);
678
+ return false;
679
+ }
680
+ if (typeof data.count !== 'number') {
681
+ console.error(`Expected count to be number, got ${typeof data.count}`);
682
+ return false;
683
+ }
684
+ // Validate each schedule has required fields
685
+ for (const schedule of data.schedules) {
686
+ if (!schedule.id ||
687
+ !schedule.brainName ||
688
+ !schedule.cronExpression ||
689
+ typeof schedule.enabled !== 'boolean' ||
690
+ typeof schedule.createdAt !== 'number') {
691
+ console.error(`Schedule missing required fields: ${JSON.stringify(schedule)}`);
692
+ return false;
693
+ }
694
+ }
695
+ return true;
696
+ }
697
+ catch (error) {
698
+ console.error(`Failed to test GET /brains/schedules:`, error);
699
+ return false;
700
+ }
701
+ },
702
+ /**
703
+ * Test DELETE /brains/schedules/:scheduleId - Delete a schedule
704
+ */
705
+ async delete(fetch, scheduleId) {
706
+ try {
707
+ const request = new Request(`http://example.com/brains/schedules/${scheduleId}`, {
708
+ method: 'DELETE',
709
+ });
710
+ const response = await fetch(request);
711
+ if (response.status !== 204) {
712
+ console.error(`DELETE /brains/schedules/${scheduleId} returned ${response.status}, expected 204`);
713
+ return false;
714
+ }
715
+ return true;
716
+ }
717
+ catch (error) {
718
+ console.error(`Failed to test DELETE /brains/schedules/${scheduleId}:`, error);
719
+ return false;
720
+ }
721
+ },
722
+ /**
723
+ * Test GET /brains/schedules/runs - Get history of scheduled runs
724
+ */
725
+ async runs(fetch, scheduleId, limit) {
726
+ try {
727
+ const url = new URL('http://example.com/brains/schedules/runs');
728
+ if (scheduleId !== undefined) {
729
+ url.searchParams.set('scheduleId', scheduleId);
730
+ }
731
+ if (limit !== undefined) {
732
+ url.searchParams.set('limit', limit.toString());
733
+ }
734
+ const request = new Request(url.toString(), {
735
+ method: 'GET',
736
+ });
737
+ const response = await fetch(request);
738
+ if (!response.ok) {
739
+ console.error(`GET /brains/schedules/runs returned ${response.status}`);
740
+ return false;
741
+ }
742
+ const data = await response.json();
743
+ // Validate response structure
744
+ if (!Array.isArray(data.runs)) {
745
+ console.error(`Expected runs to be an array, got ${typeof data.runs}`);
746
+ return false;
747
+ }
748
+ if (typeof data.count !== 'number') {
749
+ console.error(`Expected count to be number, got ${typeof data.count}`);
750
+ return false;
751
+ }
752
+ // Validate each run has required fields
753
+ for (const run of data.runs) {
754
+ if (!run.id ||
755
+ !run.scheduleId ||
756
+ !run.status ||
757
+ typeof run.ranAt !== 'number') {
758
+ console.error(`Scheduled run missing required fields: ${JSON.stringify(run)}`);
759
+ return false;
760
+ }
761
+ if (!['triggered', 'failed'].includes(run.status)) {
762
+ console.error(`Invalid run status: ${run.status}`);
763
+ return false;
764
+ }
765
+ }
766
+ return true;
767
+ }
768
+ catch (error) {
769
+ console.error(`Failed to test GET /brains/schedules/runs:`, error);
770
+ return false;
771
+ }
772
+ },
773
+ };
774
+ export const secrets = {
775
+ /**
776
+ * Test POST /secrets - Create or update a secret
777
+ */
778
+ async create(fetch, name, value) {
779
+ try {
780
+ const request = new Request('http://example.com/secrets', {
781
+ method: 'POST',
782
+ headers: {
783
+ 'Content-Type': 'application/json',
784
+ },
785
+ body: JSON.stringify({ name, value }),
786
+ });
787
+ const response = await fetch(request);
788
+ if (response.status !== 201) {
789
+ console.error(`POST /secrets returned ${response.status}, expected 201`);
790
+ return false;
791
+ }
792
+ const data = await response.json();
793
+ // Validate response structure
794
+ if (!data.name || typeof data.name !== 'string') {
795
+ console.error(`Expected name to be string, got ${typeof data.name}`);
796
+ return false;
797
+ }
798
+ if (data.name !== name) {
799
+ console.error(`Expected name to be '${name}', got ${data.name}`);
800
+ return false;
801
+ }
802
+ if (typeof data.createdAt !== 'string') {
803
+ console.error(`Expected createdAt to be string, got ${typeof data.createdAt}`);
804
+ return false;
805
+ }
806
+ if (typeof data.updatedAt !== 'string') {
807
+ console.error(`Expected updatedAt to be string, got ${typeof data.updatedAt}`);
808
+ return false;
809
+ }
810
+ return true;
811
+ }
812
+ catch (error) {
813
+ console.error(`Failed to test POST /secrets:`, error);
814
+ return false;
815
+ }
816
+ },
817
+ /**
818
+ * Test GET /secrets - List all secrets (names only, not values)
819
+ */
820
+ async list(fetch) {
821
+ try {
822
+ const request = new Request('http://example.com/secrets', {
823
+ method: 'GET',
824
+ });
825
+ const response = await fetch(request);
826
+ if (!response.ok) {
827
+ console.error(`GET /secrets returned ${response.status}`);
828
+ return false;
829
+ }
830
+ const data = await response.json();
831
+ // Validate response structure
832
+ if (!Array.isArray(data.secrets)) {
833
+ console.error(`Expected secrets to be an array, got ${typeof data.secrets}`);
834
+ return false;
835
+ }
836
+ if (typeof data.count !== 'number') {
837
+ console.error(`Expected count to be number, got ${typeof data.count}`);
838
+ return false;
839
+ }
840
+ // Validate each secret has required fields (but NOT the value)
841
+ for (const secret of data.secrets) {
842
+ if (!secret.name ||
843
+ typeof secret.name !== 'string' ||
844
+ typeof secret.createdAt !== 'string' ||
845
+ typeof secret.updatedAt !== 'string') {
846
+ console.error(`Secret missing required fields: ${JSON.stringify(secret)}`);
847
+ return false;
848
+ }
849
+ // Ensure value is NOT included
850
+ if ('value' in secret) {
851
+ console.error(`Secret should not include value field: ${JSON.stringify(secret)}`);
852
+ return false;
853
+ }
854
+ }
855
+ return true;
856
+ }
857
+ catch (error) {
858
+ console.error(`Failed to test GET /secrets:`, error);
859
+ return false;
860
+ }
861
+ },
862
+ /**
863
+ * Test DELETE /secrets/:name - Delete a specific secret
864
+ */
865
+ async delete(fetch, name) {
866
+ try {
867
+ const request = new Request(`http://example.com/secrets/${encodeURIComponent(name)}`, {
868
+ method: 'DELETE',
869
+ });
870
+ const response = await fetch(request);
871
+ if (response.status !== 204) {
872
+ console.error(`DELETE /secrets/${name} returned ${response.status}, expected 204`);
873
+ return false;
874
+ }
875
+ return true;
876
+ }
877
+ catch (error) {
878
+ console.error(`Failed to test DELETE /secrets/${name}:`, error);
879
+ return false;
880
+ }
881
+ },
882
+ /**
883
+ * Test GET /secrets/:name/exists - Check if a secret exists
884
+ */
885
+ async exists(fetch, name) {
886
+ try {
887
+ const request = new Request(`http://example.com/secrets/${encodeURIComponent(name)}/exists`, {
888
+ method: 'GET',
889
+ });
890
+ const response = await fetch(request);
891
+ if (!response.ok) {
892
+ console.error(`GET /secrets/${name}/exists returned ${response.status}`);
893
+ return false;
894
+ }
895
+ const data = await response.json();
896
+ // Validate response structure
897
+ if (typeof data.exists !== 'boolean') {
898
+ console.error(`Expected exists to be boolean, got ${typeof data.exists}`);
899
+ return false;
900
+ }
901
+ return true;
902
+ }
903
+ catch (error) {
904
+ console.error(`Failed to test GET /secrets/${name}/exists:`, error);
905
+ return false;
906
+ }
907
+ },
908
+ /**
909
+ * Test POST /secrets/bulk - Create multiple secrets
910
+ */
911
+ async bulk(fetch, secrets) {
912
+ try {
913
+ const request = new Request('http://example.com/secrets/bulk', {
914
+ method: 'POST',
915
+ headers: {
916
+ 'Content-Type': 'application/json',
917
+ },
918
+ body: JSON.stringify({ secrets }),
919
+ });
920
+ const response = await fetch(request);
921
+ if (response.status !== 201) {
922
+ console.error(`POST /secrets/bulk returned ${response.status}, expected 201`);
923
+ return false;
924
+ }
925
+ const data = await response.json();
926
+ // Validate response structure
927
+ if (typeof data.created !== 'number') {
928
+ console.error(`Expected created to be number, got ${typeof data.created}`);
929
+ return false;
930
+ }
931
+ if (typeof data.updated !== 'number') {
932
+ console.error(`Expected updated to be number, got ${typeof data.updated}`);
933
+ return false;
934
+ }
935
+ // Total should match input
936
+ if (data.created + data.updated !== secrets.length) {
937
+ console.error(`Expected total (${data.created + data.updated}) to match input length (${secrets.length})`);
938
+ return false;
939
+ }
940
+ return true;
941
+ }
942
+ catch (error) {
943
+ console.error(`Failed to test POST /secrets/bulk:`, error);
944
+ return false;
945
+ }
946
+ },
947
+ };
948
+ //# sourceMappingURL=api.js.map