@positronic/cloudflare 0.0.2 → 0.0.4
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/src/api.js +1270 -0
- package/dist/src/brain-runner-do.js +654 -0
- package/dist/src/dev-server.js +1357 -0
- package/{src/index.ts → dist/src/index.js} +1 -6
- package/dist/src/manifest.js +278 -0
- package/dist/src/monitor-do.js +408 -0
- package/{src/node-index.ts → dist/src/node-index.js} +3 -7
- package/dist/src/r2-loader.js +207 -0
- package/dist/src/schedule-do.js +705 -0
- package/dist/src/sqlite-adapter.js +69 -0
- package/dist/types/api.d.ts +21 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/brain-runner-do.d.ts +25 -0
- package/dist/types/brain-runner-do.d.ts.map +1 -0
- package/dist/types/dev-server.d.ts +45 -0
- package/dist/types/dev-server.d.ts.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/manifest.d.ts +11 -0
- package/dist/types/manifest.d.ts.map +1 -0
- package/dist/types/monitor-do.d.ts +16 -0
- package/dist/types/monitor-do.d.ts.map +1 -0
- package/dist/types/node-index.d.ts +10 -0
- package/dist/types/node-index.d.ts.map +1 -0
- package/dist/types/r2-loader.d.ts +10 -0
- package/dist/types/r2-loader.d.ts.map +1 -0
- package/dist/types/schedule-do.d.ts +47 -0
- package/dist/types/schedule-do.d.ts.map +1 -0
- package/dist/types/sqlite-adapter.d.ts +10 -0
- package/dist/types/sqlite-adapter.d.ts.map +1 -0
- package/package.json +8 -4
- package/src/api.ts +0 -579
- package/src/brain-runner-do.ts +0 -309
- package/src/dev-server.ts +0 -776
- package/src/manifest.ts +0 -69
- package/src/monitor-do.ts +0 -268
- package/src/r2-loader.ts +0 -27
- package/src/schedule-do.ts +0 -377
- package/src/sqlite-adapter.ts +0 -50
- package/test-project/package-lock.json +0 -3010
- package/test-project/package.json +0 -21
- package/test-project/src/index.ts +0 -70
- package/test-project/src/runner.ts +0 -24
- package/test-project/tests/api.test.ts +0 -1005
- package/test-project/tests/r2loader.test.ts +0 -73
- package/test-project/tests/resources-api.test.ts +0 -671
- package/test-project/tests/spec.test.ts +0 -135
- package/test-project/tests/tsconfig.json +0 -7
- package/test-project/tsconfig.json +0 -20
- package/test-project/vitest.config.ts +0 -12
- package/test-project/wrangler.jsonc +0 -53
- package/tsconfig.json +0 -11
package/src/brain-runner-do.ts
DELETED
|
@@ -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
|
-
}
|