@positronic/cloudflare 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.
Files changed (52) hide show
  1. package/dist/src/api.js +1270 -0
  2. package/dist/src/brain-runner-do.js +654 -0
  3. package/dist/src/dev-server.js +1357 -0
  4. package/{src/index.ts → dist/src/index.js} +1 -6
  5. package/dist/src/manifest.js +278 -0
  6. package/dist/src/monitor-do.js +408 -0
  7. package/{src/node-index.ts → dist/src/node-index.js} +3 -7
  8. package/dist/src/r2-loader.js +207 -0
  9. package/dist/src/schedule-do.js +705 -0
  10. package/dist/src/sqlite-adapter.js +69 -0
  11. package/dist/types/api.d.ts +21 -0
  12. package/dist/types/api.d.ts.map +1 -0
  13. package/dist/types/brain-runner-do.d.ts +25 -0
  14. package/dist/types/brain-runner-do.d.ts.map +1 -0
  15. package/dist/types/dev-server.d.ts +45 -0
  16. package/dist/types/dev-server.d.ts.map +1 -0
  17. package/dist/types/index.d.ts +7 -0
  18. package/dist/types/index.d.ts.map +1 -0
  19. package/dist/types/manifest.d.ts +11 -0
  20. package/dist/types/manifest.d.ts.map +1 -0
  21. package/dist/types/monitor-do.d.ts +16 -0
  22. package/dist/types/monitor-do.d.ts.map +1 -0
  23. package/dist/types/node-index.d.ts +10 -0
  24. package/dist/types/node-index.d.ts.map +1 -0
  25. package/dist/types/r2-loader.d.ts +10 -0
  26. package/dist/types/r2-loader.d.ts.map +1 -0
  27. package/dist/types/schedule-do.d.ts +47 -0
  28. package/dist/types/schedule-do.d.ts.map +1 -0
  29. package/dist/types/sqlite-adapter.d.ts +10 -0
  30. package/dist/types/sqlite-adapter.d.ts.map +1 -0
  31. package/package.json +5 -1
  32. package/src/api.ts +0 -579
  33. package/src/brain-runner-do.ts +0 -309
  34. package/src/dev-server.ts +0 -776
  35. package/src/manifest.ts +0 -69
  36. package/src/monitor-do.ts +0 -268
  37. package/src/r2-loader.ts +0 -27
  38. package/src/schedule-do.ts +0 -377
  39. package/src/sqlite-adapter.ts +0 -50
  40. package/test-project/package-lock.json +0 -3010
  41. package/test-project/package.json +0 -21
  42. package/test-project/src/index.ts +0 -70
  43. package/test-project/src/runner.ts +0 -24
  44. package/test-project/tests/api.test.ts +0 -1005
  45. package/test-project/tests/r2loader.test.ts +0 -73
  46. package/test-project/tests/resources-api.test.ts +0 -671
  47. package/test-project/tests/spec.test.ts +0 -135
  48. package/test-project/tests/tsconfig.json +0 -7
  49. package/test-project/tsconfig.json +0 -20
  50. package/test-project/vitest.config.ts +0 -12
  51. package/test-project/wrangler.jsonc +0 -53
  52. package/tsconfig.json +0 -11
@@ -1,309 +0,0 @@
1
- import { BrainRunner, type Resources } from '@positronic/core';
2
- import { DurableObject } from 'cloudflare:workers';
3
-
4
- import type { Adapter, BrainEvent } from '@positronic/core';
5
- import { BrainRunSQLiteAdapter } from './sqlite-adapter.js';
6
- import type { MonitorDO } from './monitor-do.js';
7
- import type { ScheduleDO } from './schedule-do.js';
8
- import { PositronicManifest } from './manifest.js';
9
- import { CloudflareR2Loader } from './r2-loader.js';
10
- import { createResources, type ResourceManifest } from '@positronic/core';
11
- import type { R2Bucket } from '@cloudflare/workers-types';
12
-
13
- let manifest: PositronicManifest | null = null;
14
- export function setManifest(generatedManifest: PositronicManifest) {
15
- manifest = generatedManifest;
16
- }
17
-
18
- export function getManifest(): PositronicManifest | null {
19
- return manifest;
20
- }
21
-
22
- let brainRunner: BrainRunner | null = null;
23
- export function setBrainRunner(runner: BrainRunner) {
24
- brainRunner = runner;
25
- }
26
-
27
- export interface Env {
28
- BRAIN_RUNNER_DO: DurableObjectNamespace;
29
- MONITOR_DO: DurableObjectNamespace<MonitorDO>;
30
- SCHEDULE_DO: DurableObjectNamespace<ScheduleDO>;
31
- RESOURCES_BUCKET: R2Bucket;
32
- }
33
-
34
- class EventStreamAdapter implements Adapter {
35
- private subscribers: Set<ReadableStreamDefaultController> = new Set();
36
- private encoder = new TextEncoder();
37
-
38
- subscribe(controller: ReadableStreamDefaultController) {
39
- this.subscribers.add(controller);
40
- }
41
-
42
- unsubscribe(controller: ReadableStreamDefaultController) {
43
- this.subscribers.delete(controller);
44
- }
45
-
46
- private broadcast(event: BrainEvent<any>) {
47
- const message = `data: ${JSON.stringify(event)}\n\n`;
48
- const encodedMessage = this.encoder.encode(message);
49
- this.subscribers.forEach((controller) => {
50
- try {
51
- controller.enqueue(encodedMessage);
52
- } catch (e) {
53
- console.error(
54
- '[DO_SSE_ADAPTER] Failed to send message to subscriber, removing.',
55
- e
56
- );
57
- this.unsubscribe(controller);
58
- }
59
- });
60
- }
61
-
62
- async dispatch(event: BrainEvent<any>): Promise<void> {
63
- try {
64
- this.broadcast(event);
65
- } catch (e) {
66
- console.error('[DO_SSE_ADAPTER] Error dispatching event:', e);
67
- throw e;
68
- }
69
- }
70
- }
71
-
72
- class MonitorAdapter implements Adapter {
73
- constructor(private monitorStub: DurableObjectStub<MonitorDO>) {}
74
-
75
- async dispatch(event: BrainEvent<any>): Promise<void> {
76
- await this.monitorStub.handleBrainEvent(event);
77
- }
78
- }
79
-
80
- class ScheduleAdapter implements Adapter {
81
- constructor(private scheduleStub: DurableObjectStub<ScheduleDO>) {}
82
-
83
- async dispatch(event: BrainEvent<any>): Promise<void> {
84
- await this.scheduleStub.handleBrainEvent(event);
85
- }
86
- }
87
-
88
- export class BrainRunnerDO extends DurableObject<Env> {
89
- private sql: SqlStorage;
90
- private brainRunId: string;
91
- private eventStreamAdapter = new EventStreamAdapter();
92
-
93
- constructor(state: DurableObjectState, env: Env) {
94
- super(state, env);
95
- this.sql = state.storage.sql;
96
- this.brainRunId = state.id.toString();
97
- this.env = env;
98
- }
99
-
100
- private async loadResourcesFromR2(): Promise<Resources | null> {
101
- const bucket = this.env.RESOURCES_BUCKET;
102
-
103
- // List all resources in R2
104
- const listed = await bucket.list();
105
- // Check if results are truncated
106
- if (listed.truncated) {
107
- throw new Error(
108
- `Too many resources in R2 bucket (more than 1000). ` +
109
- `Resource pagination is not yet supported. ` +
110
- `Please reduce the number of resources.`
111
- );
112
- }
113
-
114
- if (listed.objects.length === 0) {
115
- return null;
116
- }
117
-
118
- // Build the manifest structure
119
- const manifest: ResourceManifest = {};
120
- let resourceCount = 0;
121
-
122
- for (const object of listed.objects) {
123
- // Get object metadata
124
- const r2Object = await bucket.head(object.key);
125
-
126
- if (!r2Object || !r2Object.customMetadata?.type) {
127
- console.warn(
128
- `[DO ${this.brainRunId}] Skipping resource ${object.key} - ` +
129
- `missing metadata.type (found: ${JSON.stringify(
130
- r2Object?.customMetadata || {}
131
- )})`
132
- );
133
- continue;
134
- }
135
-
136
- // Parse the key to create nested structure
137
- // e.g., "folder/file.txt" becomes manifest.folder["file.txt"]
138
- const keyParts = object.key.split('/');
139
-
140
- // Get the file name (with extension preserved)
141
- const fileName = keyParts[keyParts.length - 1];
142
-
143
- // Navigate/create nested structure
144
- let current: any = manifest;
145
- for (let i = 0; i < keyParts.length - 1; i++) {
146
- const part = keyParts[i];
147
- if (!current[part]) {
148
- current[part] = {};
149
- }
150
- current = current[part];
151
- }
152
-
153
- // Add the resource entry with full filename
154
- current[fileName] = {
155
- type: r2Object.customMetadata.type as 'text' | 'binary',
156
- key: object.key,
157
- path: r2Object.customMetadata.path || object.key,
158
- };
159
-
160
- resourceCount++;
161
- }
162
-
163
- if (resourceCount === 0) {
164
- return null;
165
- }
166
-
167
- // Create the loader and resources
168
- const loader = new CloudflareR2Loader(bucket);
169
- const resources = createResources(loader, manifest);
170
-
171
- return resources;
172
- }
173
-
174
- async start(
175
- brainName: string,
176
- brainRunId: string,
177
- initialData?: Record<string, any>
178
- ) {
179
- const { sql } = this;
180
-
181
- if (!manifest) {
182
- throw new Error('Runtime manifest not initialized');
183
- }
184
-
185
- const brainToRun = await manifest.import(brainName);
186
- if (!brainToRun) {
187
- console.error(
188
- `[DO ${brainRunId}] Brain ${brainName} not found in manifest.`
189
- );
190
- console.error(JSON.stringify(manifest, null, 2));
191
- throw new Error(`Brain ${brainName} not found`);
192
- }
193
-
194
- const sqliteAdapter = new BrainRunSQLiteAdapter(sql);
195
- const { eventStreamAdapter } = this;
196
- const monitorAdapter = new MonitorAdapter(
197
- this.env.MONITOR_DO.get(this.env.MONITOR_DO.idFromName('singleton'))
198
- );
199
- const scheduleAdapter = new ScheduleAdapter(
200
- this.env.SCHEDULE_DO.get(this.env.SCHEDULE_DO.idFromName('singleton'))
201
- );
202
-
203
- if (!brainRunner) {
204
- throw new Error('BrainRunner not initialized');
205
- }
206
-
207
- // Load resources from R2
208
- const r2Resources = await this.loadResourcesFromR2();
209
- // Create an enhanced runner with resources if available
210
- let runnerWithResources = brainRunner;
211
-
212
- // Use R2 resources if available
213
- if (r2Resources) {
214
- runnerWithResources = brainRunner.withResources(r2Resources);
215
- }
216
-
217
- runnerWithResources
218
- .withAdapters([
219
- sqliteAdapter,
220
- eventStreamAdapter,
221
- monitorAdapter,
222
- scheduleAdapter,
223
- ])
224
- .run(brainToRun, {
225
- initialState: initialData || {},
226
- brainRunId,
227
- })
228
- .catch((err: any) => {
229
- console.error(`[DO ${brainRunId}] BrainRunner run failed:`, err);
230
- });
231
- }
232
-
233
- async fetch(request: Request) {
234
- const { sql, eventStreamAdapter, brainRunId } = this;
235
- const url = new URL(request.url);
236
- const encoder = new TextEncoder();
237
-
238
- const sendEvent = (
239
- controller: ReadableStreamDefaultController,
240
- data: any
241
- ) => {
242
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
243
- };
244
-
245
- let streamController: ReadableStreamDefaultController | null = null;
246
- try {
247
- if (url.pathname === '/watch') {
248
- const stream = new ReadableStream({
249
- async start(controller) {
250
- streamController = controller;
251
- try {
252
- streamController = controller;
253
- const existingEventsSql = `
254
- SELECT serialized_event
255
- FROM brain_events
256
- ORDER BY event_id ASC;
257
- `;
258
- const existingEventsResult = sql
259
- .exec<{ serialized_event: string }>(existingEventsSql)
260
- .toArray();
261
-
262
- for (const row of existingEventsResult) {
263
- try {
264
- const event = JSON.parse(row.serialized_event);
265
- sendEvent(controller, event);
266
- } catch (parseError) {
267
- console.error(
268
- `[DO ${brainRunId} WATCH] Failed to parse historical event JSON: ${row.serialized_event}`,
269
- parseError
270
- );
271
- }
272
- }
273
-
274
- eventStreamAdapter.subscribe(controller);
275
- } catch (err) {
276
- console.error(
277
- `[DO ${brainRunId} WATCH] Error during stream start:`,
278
- err
279
- );
280
- controller.close();
281
- eventStreamAdapter.unsubscribe(streamController);
282
- throw err;
283
- }
284
- },
285
- cancel(reason) {
286
- if (streamController)
287
- eventStreamAdapter.unsubscribe(streamController);
288
- },
289
- });
290
-
291
- return new Response(stream, {
292
- headers: {
293
- 'Content-Type': 'text/event-stream',
294
- 'Cache-Control': 'no-cache',
295
- Connection: 'keep-alive',
296
- },
297
- });
298
- }
299
-
300
- console.warn(
301
- `[DO ${brainRunId}] fetch() called with unhandled path: ${url.pathname}`
302
- );
303
- return new Response('Not found', { status: 404 });
304
- } catch (error) {
305
- console.error(`[DO ${brainRunId}] Error in fetch():`, error);
306
- return new Response('Internal server error', { status: 500 });
307
- }
308
- }
309
- }