@effect-agent/storage-cloudflare 0.1.0-beta.41 → 0.1.0-beta.44
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/index.mjs.map +1 -1
- package/dist/testing.mjs.map +1 -1
- package/package.json +1 -52
- package/src/do-journal.ts +42 -0
- package/src/do-ledger.ts +309 -0
- package/src/do-schedule-store.ts +49 -0
- package/src/do-storage-failpoint-testing.ts +1 -0
- package/src/do-subscription-store.ts +153 -0
- package/src/do-thread-store.ts +74 -0
- package/src/errors.ts +1 -0
- package/src/memory-protocol.ts +24 -0
- package/src/port-protocol.ts +3 -0
- package/src/routing.ts +41 -0
package/src/do-schedule-store.ts
CHANGED
|
@@ -136,6 +136,7 @@ const decodeRecord = Effect.fn("DoScheduleStore.decodeRecord")(function* (
|
|
|
136
136
|
const record = yield* Schema.decodeEffect(Schema.fromJsonString(ScheduleRecord))(
|
|
137
137
|
row.record_json,
|
|
138
138
|
).pipe(Effect.mapError(() => corrupt("decode schedule")));
|
|
139
|
+
|
|
139
140
|
if (
|
|
140
141
|
record.owner.tenantId !== row.tenant_id ||
|
|
141
142
|
record.owner.ownerId !== row.owner_id ||
|
|
@@ -144,6 +145,7 @@ const decodeRecord = Effect.fn("DoScheduleStore.decodeRecord")(function* (
|
|
|
144
145
|
) {
|
|
145
146
|
return yield* corrupt("decode schedule index");
|
|
146
147
|
}
|
|
148
|
+
|
|
147
149
|
return record;
|
|
148
150
|
});
|
|
149
151
|
|
|
@@ -153,6 +155,7 @@ const encodeRecord = Effect.fn("DoScheduleStore.encodeRecord")(function* (
|
|
|
153
155
|
const encoded = yield* Schema.encodeEffect(Schema.fromJsonString(ScheduleRecord))(record).pipe(
|
|
154
156
|
Effect.mapError(() => corrupt("encode schedule")),
|
|
155
157
|
);
|
|
158
|
+
|
|
156
159
|
return yield* Schema.decodeUnknownEffect(StoredScheduleJson)(encoded).pipe(
|
|
157
160
|
Effect.mapError(() => corrupt("encode schedule bounds")),
|
|
158
161
|
);
|
|
@@ -161,6 +164,7 @@ const encodeRecord = Effect.fn("DoScheduleStore.encodeRecord")(function* (
|
|
|
161
164
|
const initializeScheduleStore = Effect.fn("DoScheduleStore.initialize")(function* () {
|
|
162
165
|
const sql = yield* SqlClientService.SqlClient;
|
|
163
166
|
const operation = "initialize schedule store";
|
|
167
|
+
|
|
164
168
|
const rawTables = yield* sql<Record<string, unknown>>`
|
|
165
169
|
SELECT name
|
|
166
170
|
FROM sqlite_master
|
|
@@ -171,6 +175,7 @@ const initializeScheduleStore = Effect.fn("DoScheduleStore.initialize")(function
|
|
|
171
175
|
)
|
|
172
176
|
ORDER BY name
|
|
173
177
|
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
178
|
+
|
|
174
179
|
const tables = yield* decodeRows(Schema.Array(ScheduleTableNameRow), rawTables, operation);
|
|
175
180
|
const hasState = tables.some((row) => row.name === "effect_agent_schedule_store_state");
|
|
176
181
|
const hasSchedules = tables.some((row) => row.name === "effect_agent_schedules");
|
|
@@ -215,16 +220,20 @@ const initializeScheduleStore = Effect.fn("DoScheduleStore.initialize")(function
|
|
|
215
220
|
}),
|
|
216
221
|
)
|
|
217
222
|
.pipe(Effect.mapError(() => unavailable(operation)));
|
|
223
|
+
|
|
218
224
|
return;
|
|
219
225
|
}
|
|
220
226
|
|
|
221
227
|
if (!hasSchedules) return yield* corrupt(operation);
|
|
228
|
+
|
|
222
229
|
const rawState = yield* sql<Record<string, unknown>>`
|
|
223
230
|
SELECT storage_version, alarm_generation
|
|
224
231
|
FROM effect_agent_schedule_store_state
|
|
225
232
|
WHERE singleton = 1
|
|
226
233
|
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
234
|
+
|
|
227
235
|
const state = yield* decodeRows(Schema.Array(ScheduleStoreStateRow), rawState, operation);
|
|
236
|
+
|
|
228
237
|
if (state.length !== 1 || state[0].storage_version !== CURRENT_SCHEDULE_STORE_VERSION) {
|
|
229
238
|
return yield* corrupt(
|
|
230
239
|
state.length === 1
|
|
@@ -252,6 +261,7 @@ const makeServices = Effect.gen(function* () {
|
|
|
252
261
|
AND owner_id = ${key.owner.ownerId}
|
|
253
262
|
AND schedule_id = ${key.scheduleId}
|
|
254
263
|
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
264
|
+
|
|
255
265
|
return yield* decodeRows(Schema.Array(ScheduleRow), rows, operation);
|
|
256
266
|
});
|
|
257
267
|
|
|
@@ -260,8 +270,10 @@ const makeServices = Effect.gen(function* () {
|
|
|
260
270
|
operation: string,
|
|
261
271
|
): Effect.fn.Return<ScheduleRecord | null, ScheduleStorageError> {
|
|
262
272
|
const rows = yield* readRows(key, operation);
|
|
273
|
+
|
|
263
274
|
if (rows.length === 0) return null;
|
|
264
275
|
if (rows.length !== 1) return yield* corrupt(operation);
|
|
276
|
+
|
|
265
277
|
return yield* decodeRecord(rows[0]);
|
|
266
278
|
});
|
|
267
279
|
|
|
@@ -283,8 +295,11 @@ const makeServices = Effect.gen(function* () {
|
|
|
283
295
|
AND owner_id = ${owner.ownerId}
|
|
284
296
|
AND deadline_at_millis IS NOT NULL
|
|
285
297
|
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
298
|
+
|
|
286
299
|
const decoded = yield* decodeRows(Schema.Array(ScheduleDeadlineRow), rows, operation);
|
|
300
|
+
|
|
287
301
|
if (decoded.length !== 1) return yield* corrupt(operation);
|
|
302
|
+
|
|
288
303
|
return decoded[0].deadline_at_millis;
|
|
289
304
|
});
|
|
290
305
|
|
|
@@ -299,7 +314,9 @@ const makeServices = Effect.gen(function* () {
|
|
|
299
314
|
WHERE singleton = 1
|
|
300
315
|
RETURNING storage_version, alarm_generation
|
|
301
316
|
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
317
|
+
|
|
302
318
|
const state = yield* decodeRows(Schema.Array(ScheduleStoreStateRow), rawState, operation);
|
|
319
|
+
|
|
303
320
|
if (state.length !== 1 || state[0].storage_version !== CURRENT_SCHEDULE_STORE_VERSION) {
|
|
304
321
|
return yield* corrupt(operation);
|
|
305
322
|
}
|
|
@@ -313,15 +330,19 @@ const makeServices = Effect.gen(function* () {
|
|
|
313
330
|
const operation = "insert schedule";
|
|
314
331
|
const canonical = yield* decodeBoundary(ScheduleRecord, record, operation);
|
|
315
332
|
const recordJson = yield* encodeRecord(canonical);
|
|
333
|
+
|
|
316
334
|
const result = yield* transactions.run((replace) =>
|
|
317
335
|
Effect.gen(function* () {
|
|
318
336
|
const existing = yield* readOne(canonical, operation);
|
|
337
|
+
|
|
319
338
|
if (existing !== null) {
|
|
320
339
|
if (existing.creationFingerprint === canonical.creationFingerprint) {
|
|
321
340
|
return { record: existing, inserted: false } as const;
|
|
322
341
|
}
|
|
342
|
+
|
|
323
343
|
return yield* ScheduleConflict.make({ reason: "creation", key: canonical });
|
|
324
344
|
}
|
|
345
|
+
|
|
325
346
|
const rawCounts = yield* sql<Record<string, unknown>>`
|
|
326
347
|
SELECT COUNT(*) AS schedule_count
|
|
327
348
|
FROM effect_agent_schedules
|
|
@@ -330,7 +351,9 @@ const makeServices = Effect.gen(function* () {
|
|
|
330
351
|
AND (json_extract(record_json, '$.pending') IS NOT NULL OR
|
|
331
352
|
(json_extract(record_json, '$.state') != 'cancelled' AND json_extract(record_json, '$.nextAtMillis') IS NOT NULL))
|
|
332
353
|
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
354
|
+
|
|
333
355
|
const counts = yield* decodeRows(Schema.Array(ScheduleCountRow), rawCounts, operation);
|
|
356
|
+
|
|
334
357
|
if (counts.length !== 1) return yield* corrupt(operation);
|
|
335
358
|
if (counts[0].schedule_count >= ownerLimit) {
|
|
336
359
|
return yield* ScheduleCapacityError.make({ limit: ownerLimit });
|
|
@@ -348,17 +371,22 @@ const makeServices = Effect.gen(function* () {
|
|
|
348
371
|
)
|
|
349
372
|
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
350
373
|
const deadline = yield* readNextDeadline(undefined, operation);
|
|
374
|
+
|
|
351
375
|
yield* replaceAlarm(replace, deadline, operation);
|
|
376
|
+
|
|
352
377
|
return { record: canonical, inserted: true } as const;
|
|
353
378
|
}),
|
|
354
379
|
);
|
|
380
|
+
|
|
355
381
|
if (result.inserted) yield* scheduleFailpoint.hit("schedule:insert:after");
|
|
382
|
+
|
|
356
383
|
return result.record;
|
|
357
384
|
},
|
|
358
385
|
);
|
|
359
386
|
|
|
360
387
|
const get: ScheduleStore["Service"]["get"] = Effect.fn("DoScheduleStore.get")(function* (key) {
|
|
361
388
|
const canonical = yield* decodeBoundary(ScheduleKey, key, "get schedule");
|
|
389
|
+
|
|
362
390
|
return yield* readOne(canonical, "get schedule");
|
|
363
391
|
});
|
|
364
392
|
|
|
@@ -367,6 +395,7 @@ const makeServices = Effect.gen(function* () {
|
|
|
367
395
|
): Effect.fn.Return<SchedulePage, ScheduleStorageError> {
|
|
368
396
|
const operation = "list schedules";
|
|
369
397
|
const request = yield* decodeBoundary(SchedulePageRequest, requestValue, operation);
|
|
398
|
+
|
|
370
399
|
const rows =
|
|
371
400
|
request.after === undefined
|
|
372
401
|
? yield* sql<Record<string, unknown>>`
|
|
@@ -386,10 +415,12 @@ const makeServices = Effect.gen(function* () {
|
|
|
386
415
|
ORDER BY schedule_id
|
|
387
416
|
LIMIT ${request.limit + 1}
|
|
388
417
|
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
418
|
+
|
|
389
419
|
const decoded = yield* decodeRows(Schema.Array(ScheduleRow), rows, operation);
|
|
390
420
|
const records = yield* Effect.forEach(decoded, decodeRecord);
|
|
391
421
|
const hasNext = records.length > request.limit;
|
|
392
422
|
const items = hasNext ? records.slice(0, request.limit) : records;
|
|
423
|
+
|
|
393
424
|
return { items, next: hasNext ? (items.at(-1)?.scheduleId ?? null) : null };
|
|
394
425
|
});
|
|
395
426
|
|
|
@@ -401,13 +432,17 @@ const makeServices = Effect.gen(function* () {
|
|
|
401
432
|
const operation = "change schedule";
|
|
402
433
|
const canonicalKey = yield* decodeBoundary(ScheduleKey, key, operation);
|
|
403
434
|
const canonicalChange = yield* decodeBoundary(ScheduleChange, change, operation);
|
|
435
|
+
|
|
404
436
|
const result = yield* transactions.run((replace) =>
|
|
405
437
|
Effect.gen(function* () {
|
|
406
438
|
const current = yield* readOne(canonicalKey, operation);
|
|
439
|
+
|
|
407
440
|
if (current === null) return yield* ScheduleNotFound.make({ key: canonicalKey });
|
|
408
441
|
const transition = applyScheduleChange(current, canonicalChange);
|
|
442
|
+
|
|
409
443
|
if (Result.isFailure(transition)) return yield* transition.failure;
|
|
410
444
|
const next = transition.success;
|
|
445
|
+
|
|
411
446
|
if (!scheduleUsesCapacity(current) && scheduleUsesCapacity(next)) {
|
|
412
447
|
const rawCounts = yield* sql<Record<string, unknown>>`
|
|
413
448
|
SELECT COUNT(*) AS schedule_count FROM effect_agent_schedules
|
|
@@ -415,13 +450,16 @@ const makeServices = Effect.gen(function* () {
|
|
|
415
450
|
AND (json_extract(record_json, '$.pending') IS NOT NULL OR
|
|
416
451
|
(json_extract(record_json, '$.state') != 'cancelled' AND json_extract(record_json, '$.nextAtMillis') IS NOT NULL))
|
|
417
452
|
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
453
|
+
|
|
418
454
|
const counts = yield* decodeRows(Schema.Array(ScheduleCountRow), rawCounts, operation);
|
|
455
|
+
|
|
419
456
|
if (counts.length !== 1) return yield* corrupt(operation);
|
|
420
457
|
if (counts[0].schedule_count >= ownerLimit)
|
|
421
458
|
return yield* ScheduleCapacityError.make({ limit: ownerLimit });
|
|
422
459
|
}
|
|
423
460
|
if (next === current) return { record: current, changed: false } as const;
|
|
424
461
|
const recordJson = yield* encodeRecord(next);
|
|
462
|
+
|
|
425
463
|
yield* scheduleFailpoint.hit(`schedule:${canonicalChange._tag.toLowerCase()}:before`);
|
|
426
464
|
yield* sql`
|
|
427
465
|
UPDATE effect_agent_schedules
|
|
@@ -431,13 +469,17 @@ const makeServices = Effect.gen(function* () {
|
|
|
431
469
|
AND schedule_id = ${canonicalKey.scheduleId}
|
|
432
470
|
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
433
471
|
const deadline = yield* readNextDeadline(undefined, operation);
|
|
472
|
+
|
|
434
473
|
yield* replaceAlarm(replace, deadline, operation);
|
|
474
|
+
|
|
435
475
|
return { record: next, changed: true } as const;
|
|
436
476
|
}),
|
|
437
477
|
);
|
|
478
|
+
|
|
438
479
|
if (result.changed) {
|
|
439
480
|
yield* scheduleFailpoint.hit(`schedule:${canonicalChange._tag.toLowerCase()}:after`);
|
|
440
481
|
}
|
|
482
|
+
|
|
441
483
|
return result.record;
|
|
442
484
|
});
|
|
443
485
|
|
|
@@ -448,18 +490,21 @@ const makeServices = Effect.gen(function* () {
|
|
|
448
490
|
after?: ScheduleDueCursor,
|
|
449
491
|
) {
|
|
450
492
|
const operation = "query due schedules";
|
|
493
|
+
|
|
451
494
|
const cursor =
|
|
452
495
|
after === undefined
|
|
453
496
|
? undefined
|
|
454
497
|
: yield* Schema.decodeUnknownEffect(ScheduleDueCursor)(after).pipe(
|
|
455
498
|
Effect.mapError(() => corrupt(operation)),
|
|
456
499
|
);
|
|
500
|
+
|
|
457
501
|
const continuation =
|
|
458
502
|
cursor === undefined
|
|
459
503
|
? sql`1 = 1`
|
|
460
504
|
: sql`
|
|
461
505
|
(deadline_at_millis, tenant_id, owner_id, schedule_id) >
|
|
462
506
|
(${cursor.deadlineAtMillis}, ${cursor.owner.tenantId}, ${cursor.owner.ownerId}, ${cursor.scheduleId})`;
|
|
507
|
+
|
|
463
508
|
const rows =
|
|
464
509
|
owner === undefined
|
|
465
510
|
? yield* sql<Record<string, unknown>>`
|
|
@@ -478,7 +523,9 @@ const makeServices = Effect.gen(function* () {
|
|
|
478
523
|
ORDER BY deadline_at_millis, schedule_id
|
|
479
524
|
LIMIT ${limit}
|
|
480
525
|
`.pipe(Effect.mapError(() => unavailable(operation)));
|
|
526
|
+
|
|
481
527
|
const decoded = yield* decodeRows(Schema.Array(ScheduleDueRow), rows, operation);
|
|
528
|
+
|
|
482
529
|
return decoded.map((row) => ({
|
|
483
530
|
owner: { tenantId: row.tenant_id, ownerId: row.owner_id },
|
|
484
531
|
scheduleId: row.schedule_id,
|
|
@@ -490,6 +537,7 @@ const makeServices = Effect.gen(function* () {
|
|
|
490
537
|
"DoScheduleStore.nextDeadline",
|
|
491
538
|
)(function* (owner?: ScheduleOwner) {
|
|
492
539
|
const operation = "query next schedule deadline";
|
|
540
|
+
|
|
493
541
|
return yield* readNextDeadline(owner, operation);
|
|
494
542
|
});
|
|
495
543
|
|
|
@@ -507,6 +555,7 @@ const makeServices = Effect.gen(function* () {
|
|
|
507
555
|
yield* transactions.run((replace) =>
|
|
508
556
|
Effect.gen(function* () {
|
|
509
557
|
const deadline = yield* readNextDeadline(undefined, "reconcile schedule alarm");
|
|
558
|
+
|
|
510
559
|
yield* replaceAlarm(replace, deadline, "reconcile schedule alarm");
|
|
511
560
|
}),
|
|
512
561
|
);
|
|
@@ -17,6 +17,7 @@ export class DoStorageFailpointTestControl extends Context.Service<
|
|
|
17
17
|
static readonly layer = Layer.effectContext(
|
|
18
18
|
Effect.gen(function* () {
|
|
19
19
|
const handler = yield* Ref.make<DoStorageFailpointHandler>(noFailpoint);
|
|
20
|
+
|
|
20
21
|
return Context.make(
|
|
21
22
|
DoStorageFailpoint,
|
|
22
23
|
DoStorageFailpoint.of({
|