@effect-agent/storage-cloudflare 0.1.0-beta.42 → 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
|
@@ -29,12 +29,15 @@ const CURRENT_SUBSCRIPTION_STORE_VERSION = 2;
|
|
|
29
29
|
const StoredJson = Schema.String.check(Schema.isMaxLength(1_900_000));
|
|
30
30
|
const CountRow = Schema.Struct({ count: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)) });
|
|
31
31
|
const SequenceRow = Schema.Struct({ sequence: Schema.Natural });
|
|
32
|
+
|
|
32
33
|
const ScanRow = Schema.Struct({
|
|
33
34
|
event_scan_cursor: Schema.String,
|
|
34
35
|
delivery_scan_cursor: Schema.String,
|
|
35
36
|
recovery_scan_cursor: Schema.Natural,
|
|
36
37
|
});
|
|
38
|
+
|
|
37
39
|
const JsonRow = Schema.Struct({ record_json: StoredJson });
|
|
40
|
+
|
|
38
41
|
const RegistrationRow = Schema.Struct({
|
|
39
42
|
owner_id: Schema.String,
|
|
40
43
|
subscription_id: Schema.String,
|
|
@@ -47,6 +50,7 @@ const RegistrationRow = Schema.Struct({
|
|
|
47
50
|
recovery_at_millis: Schema.NullOr(Schema.Number),
|
|
48
51
|
record_json: StoredJson,
|
|
49
52
|
});
|
|
53
|
+
|
|
50
54
|
const EventRow = Schema.Struct({
|
|
51
55
|
event_id: Schema.String,
|
|
52
56
|
source_name: Schema.String,
|
|
@@ -59,6 +63,7 @@ const EventRow = Schema.Struct({
|
|
|
59
63
|
next_attempt_at_millis: Schema.Number,
|
|
60
64
|
record_json: StoredJson,
|
|
61
65
|
});
|
|
66
|
+
|
|
62
67
|
const DeliveryRow = Schema.Struct({
|
|
63
68
|
owner_id: Schema.String,
|
|
64
69
|
subscription_id: Schema.String,
|
|
@@ -68,6 +73,7 @@ const DeliveryRow = Schema.Struct({
|
|
|
68
73
|
next_attempt_at_millis: Schema.Number,
|
|
69
74
|
record_json: StoredJson,
|
|
70
75
|
});
|
|
76
|
+
|
|
71
77
|
const StoreStateRow = Schema.Struct({
|
|
72
78
|
storage_version: Schema.Int,
|
|
73
79
|
alarm_generation: Schema.Natural,
|
|
@@ -77,6 +83,7 @@ export interface DoSubscriptionAlarmReplacement {
|
|
|
77
83
|
readonly deadlineAtMillis: number | null;
|
|
78
84
|
readonly generation: number;
|
|
79
85
|
}
|
|
86
|
+
|
|
80
87
|
export type DoSubscriptionReplaceAlarm = (
|
|
81
88
|
replacement: DoSubscriptionAlarmReplacement,
|
|
82
89
|
) => Effect.Effect<void, SubscriptionError>;
|
|
@@ -103,6 +110,7 @@ export class DoSubscriptionAlarmControl extends Context.Service<
|
|
|
103
110
|
|
|
104
111
|
const error = (reason: SubscriptionError["reason"], code: string) =>
|
|
105
112
|
SubscriptionError.make({ reason, code });
|
|
113
|
+
|
|
106
114
|
const unavailable = (operation: string) => error("storage", operation);
|
|
107
115
|
const corrupt = (operation: string) => error("corrupt", operation);
|
|
108
116
|
const bytes = (value: unknown) => new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
@@ -135,14 +143,17 @@ const sameDeliveryIdentity = (left: SubscriptionDelivery, right: SubscriptionDel
|
|
|
135
143
|
|
|
136
144
|
const initializeDoSubscriptionStore = Effect.fn("DoSubscriptionStore.initialize")(function* () {
|
|
137
145
|
const sql = yield* SqlClientService.SqlClient;
|
|
146
|
+
|
|
138
147
|
const names = yield* sql<Record<string, unknown>>`
|
|
139
148
|
SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'effect_agent_subscription_%'
|
|
140
149
|
`.pipe(Effect.mapError(() => unavailable("inspect subscription storage")));
|
|
150
|
+
|
|
141
151
|
const decodedNames = yield* decodeRows(
|
|
142
152
|
Schema.Struct({ name: Schema.String }),
|
|
143
153
|
names,
|
|
144
154
|
"inspect subscription storage",
|
|
145
155
|
);
|
|
156
|
+
|
|
146
157
|
const expected = new Set([
|
|
147
158
|
"effect_agent_subscription_store_state",
|
|
148
159
|
"effect_agent_subscription_sequences",
|
|
@@ -150,9 +161,11 @@ const initializeDoSubscriptionStore = Effect.fn("DoSubscriptionStore.initialize"
|
|
|
150
161
|
"effect_agent_subscription_events",
|
|
151
162
|
"effect_agent_subscription_deliveries",
|
|
152
163
|
]);
|
|
164
|
+
|
|
153
165
|
const hasState = decodedNames.some(
|
|
154
166
|
({ name }) => name === "effect_agent_subscription_store_state",
|
|
155
167
|
);
|
|
168
|
+
|
|
156
169
|
if (!hasState && decodedNames.length > 0) return yield* corrupt("partial subscription storage");
|
|
157
170
|
if (!hasState) {
|
|
158
171
|
yield* sql
|
|
@@ -201,16 +214,20 @@ const initializeDoSubscriptionStore = Effect.fn("DoSubscriptionStore.initialize"
|
|
|
201
214
|
}),
|
|
202
215
|
)
|
|
203
216
|
.pipe(Effect.mapError(() => unavailable("initialize subscription storage")));
|
|
217
|
+
|
|
204
218
|
return;
|
|
205
219
|
}
|
|
206
220
|
if (decodedNames.length !== expected.size || decodedNames.some(({ name }) => !expected.has(name)))
|
|
207
221
|
return yield* corrupt("subscription storage tables");
|
|
222
|
+
|
|
208
223
|
const rows = yield* sql<
|
|
209
224
|
Record<string, unknown>
|
|
210
225
|
>`SELECT storage_version, alarm_generation FROM effect_agent_subscription_store_state WHERE singleton=1`.pipe(
|
|
211
226
|
Effect.mapError(() => unavailable("read subscription storage version")),
|
|
212
227
|
);
|
|
228
|
+
|
|
213
229
|
const state = yield* decodeRows(StoreStateRow, rows, "read subscription storage version");
|
|
230
|
+
|
|
214
231
|
if (state.length !== 1 || state[0].storage_version !== CURRENT_SUBSCRIPTION_STORE_VERSION)
|
|
215
232
|
return yield* corrupt(
|
|
216
233
|
state.length === 1
|
|
@@ -226,6 +243,7 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
226
243
|
const sql = yield* SqlClientService.SqlClient;
|
|
227
244
|
const failpoint = yield* SubscriptionFailpoint;
|
|
228
245
|
const transactions = yield* DoSubscriptionTransaction;
|
|
246
|
+
|
|
229
247
|
yield* initializeDoSubscriptionStore();
|
|
230
248
|
yield* sql`
|
|
231
249
|
INSERT INTO effect_agent_subscription_sequences (
|
|
@@ -237,6 +255,7 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
237
255
|
effect: Effect.Effect<ReadonlyArray<A>, SqlError>,
|
|
238
256
|
code: string,
|
|
239
257
|
) => effect.pipe(Effect.mapError(() => unavailable(code)));
|
|
258
|
+
|
|
240
259
|
const readIndexedDeadline = Effect.fn("DoSubscriptionStore.readIndexedDeadline")(function* () {
|
|
241
260
|
const scanRows = yield* query(
|
|
242
261
|
sql<Record<string, unknown>>`
|
|
@@ -245,7 +264,9 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
245
264
|
`,
|
|
246
265
|
"read scan cursor deadline",
|
|
247
266
|
);
|
|
267
|
+
|
|
248
268
|
const scans = yield* decodeRows(ScanRow, scanRows, "read scan cursor deadline");
|
|
269
|
+
|
|
249
270
|
if (scans.length !== 1) return yield* corrupt("scan cursor deadline");
|
|
250
271
|
if (
|
|
251
272
|
scans[0].event_scan_cursor !== "" ||
|
|
@@ -253,6 +274,7 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
253
274
|
scans[0].recovery_scan_cursor !== 0
|
|
254
275
|
)
|
|
255
276
|
return 0;
|
|
277
|
+
|
|
256
278
|
const rows = yield* query(
|
|
257
279
|
sql<Record<string, unknown>>`
|
|
258
280
|
SELECT MIN(deadline) AS deadline FROM (
|
|
@@ -266,14 +288,18 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
266
288
|
`,
|
|
267
289
|
"read subscription deadline",
|
|
268
290
|
);
|
|
291
|
+
|
|
269
292
|
const decoded = yield* decodeRows(
|
|
270
293
|
Schema.Struct({ deadline: Schema.NullOr(Schema.Number) }),
|
|
271
294
|
rows,
|
|
272
295
|
"read subscription deadline",
|
|
273
296
|
);
|
|
297
|
+
|
|
274
298
|
if (decoded.length !== 1) return yield* corrupt("subscription deadline");
|
|
299
|
+
|
|
275
300
|
return decoded[0].deadline;
|
|
276
301
|
});
|
|
302
|
+
|
|
277
303
|
const replaceAlarm = Effect.fn("DoSubscriptionStore.replaceAlarm")(function* (
|
|
278
304
|
replace: DoSubscriptionReplaceAlarm,
|
|
279
305
|
deadlineAtMillis: number | null,
|
|
@@ -285,33 +311,43 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
285
311
|
`,
|
|
286
312
|
"advance subscription alarm generation",
|
|
287
313
|
);
|
|
314
|
+
|
|
288
315
|
const state = yield* decodeRows(StoreStateRow, rows, "advance subscription alarm generation");
|
|
316
|
+
|
|
289
317
|
if (state.length !== 1 || state[0].storage_version !== CURRENT_SUBSCRIPTION_STORE_VERSION)
|
|
290
318
|
return yield* corrupt("subscription alarm state");
|
|
291
319
|
yield* failpoint.hit("subscription:alarm:before");
|
|
292
320
|
yield* replace({ deadlineAtMillis, generation: state[0].alarm_generation });
|
|
293
321
|
yield* failpoint.hit("subscription:alarm:after");
|
|
294
322
|
});
|
|
323
|
+
|
|
295
324
|
const transact = <A>(effect: Effect.Effect<A, SubscriptionError | SubscriptionFailpointError>) =>
|
|
296
325
|
transactions.run((replace) =>
|
|
297
326
|
Effect.gen(function* () {
|
|
298
327
|
const value = yield* effect;
|
|
328
|
+
|
|
299
329
|
yield* replaceAlarm(replace, yield* readIndexedDeadline());
|
|
330
|
+
|
|
300
331
|
return value;
|
|
301
332
|
}),
|
|
302
333
|
);
|
|
334
|
+
|
|
303
335
|
const requirePartition = (candidate: SourcePartition, code: string) =>
|
|
304
336
|
sameSourcePartition(candidate, partition)
|
|
305
337
|
? Effect.void
|
|
306
338
|
: Effect.fail(error("validation", code));
|
|
339
|
+
|
|
307
340
|
const requireKey = Effect.fn("SqliteSubscriptionStore.requireKey")(function* (
|
|
308
341
|
input: SubscriptionKey,
|
|
309
342
|
code: string,
|
|
310
343
|
) {
|
|
311
344
|
const key = yield* validate(SubscriptionKey, input, code);
|
|
345
|
+
|
|
312
346
|
yield* requirePartition(key.partition, code);
|
|
347
|
+
|
|
313
348
|
return key;
|
|
314
349
|
});
|
|
350
|
+
|
|
315
351
|
const readRegistration = Effect.fn("SqliteSubscriptionStore.readRegistration")(function* (
|
|
316
352
|
key: SubscriptionKey,
|
|
317
353
|
code: string,
|
|
@@ -325,11 +361,15 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
325
361
|
`,
|
|
326
362
|
code,
|
|
327
363
|
);
|
|
364
|
+
|
|
328
365
|
const decodedRows = yield* decodeRows(RegistrationRow, rows, code);
|
|
366
|
+
|
|
329
367
|
if (decodedRows.length > 1) return yield* corrupt(code);
|
|
330
368
|
const row = decodedRows[0];
|
|
369
|
+
|
|
331
370
|
if (row === undefined) return null;
|
|
332
371
|
const record = yield* decode(SubscriptionRecord, row.record_json, code);
|
|
372
|
+
|
|
333
373
|
if (
|
|
334
374
|
!sameSourcePartition(record.key.partition, partition) ||
|
|
335
375
|
record.key.ownerId !== row.owner_id ||
|
|
@@ -343,8 +383,10 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
343
383
|
(record.recovery?.nextAttemptAtMillis ?? null) !== row.recovery_at_millis
|
|
344
384
|
)
|
|
345
385
|
return yield* corrupt(`${code}-projection`);
|
|
386
|
+
|
|
346
387
|
return record;
|
|
347
388
|
});
|
|
389
|
+
|
|
348
390
|
const readEvent = Effect.fn("SqliteSubscriptionStore.readEvent")(function* (
|
|
349
391
|
eventId: string,
|
|
350
392
|
code: string,
|
|
@@ -357,11 +399,15 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
357
399
|
`,
|
|
358
400
|
code,
|
|
359
401
|
);
|
|
402
|
+
|
|
360
403
|
const decodedRows = yield* decodeRows(EventRow, rows, code);
|
|
404
|
+
|
|
361
405
|
if (decodedRows.length > 1) return yield* corrupt(code);
|
|
362
406
|
const row = decodedRows[0];
|
|
407
|
+
|
|
363
408
|
if (row === undefined) return null;
|
|
364
409
|
const event = yield* decode(AcceptedEvent, row.record_json, code);
|
|
410
|
+
|
|
365
411
|
if (
|
|
366
412
|
!sameSourcePartition(event.partition, partition) ||
|
|
367
413
|
event.eventId !== row.event_id ||
|
|
@@ -375,8 +421,10 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
375
421
|
event.nextAttemptAtMillis !== row.next_attempt_at_millis
|
|
376
422
|
)
|
|
377
423
|
return yield* corrupt(`${code}-projection`);
|
|
424
|
+
|
|
378
425
|
return event;
|
|
379
426
|
});
|
|
427
|
+
|
|
380
428
|
const readDelivery = Effect.fn("SqliteSubscriptionStore.readDelivery")(function* (
|
|
381
429
|
key: SubscriptionDeliveryKey,
|
|
382
430
|
code: string,
|
|
@@ -391,11 +439,15 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
391
439
|
`,
|
|
392
440
|
code,
|
|
393
441
|
);
|
|
442
|
+
|
|
394
443
|
const decodedRows = yield* decodeRows(DeliveryRow, rows, code);
|
|
444
|
+
|
|
395
445
|
if (decodedRows.length > 1) return yield* corrupt(code);
|
|
396
446
|
const row = decodedRows[0];
|
|
447
|
+
|
|
397
448
|
if (row === undefined) return null;
|
|
398
449
|
const delivery = yield* decode(SubscriptionDelivery, row.record_json, code);
|
|
450
|
+
|
|
399
451
|
if (
|
|
400
452
|
!sameSourcePartition(delivery.key.subscription.partition, partition) ||
|
|
401
453
|
delivery.key.subscription.ownerId !== row.owner_id ||
|
|
@@ -406,17 +458,22 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
406
458
|
delivery.retry.nextAttemptAtMillis !== row.next_attempt_at_millis
|
|
407
459
|
)
|
|
408
460
|
return yield* corrupt(`${code}-projection`);
|
|
461
|
+
|
|
409
462
|
return delivery;
|
|
410
463
|
});
|
|
464
|
+
|
|
411
465
|
const count = Effect.fn("SqliteSubscriptionStore.count")(function* (
|
|
412
466
|
statement: Effect.Effect<ReadonlyArray<Record<string, unknown>>, SqlError>,
|
|
413
467
|
code: string,
|
|
414
468
|
) {
|
|
415
469
|
const rows = yield* query(statement, code);
|
|
416
470
|
const decoded = yield* decodeRows(CountRow, rows, code);
|
|
471
|
+
|
|
417
472
|
if (decoded.length !== 1) return yield* corrupt(code);
|
|
473
|
+
|
|
418
474
|
return decoded[0].count;
|
|
419
475
|
});
|
|
476
|
+
|
|
420
477
|
const nextSequence = Effect.fn("SqliteSubscriptionStore.nextSequence")(function* () {
|
|
421
478
|
const rows = yield* query(
|
|
422
479
|
sql<Record<string, unknown>>`
|
|
@@ -426,14 +483,19 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
426
483
|
`,
|
|
427
484
|
"advance subscription sequence",
|
|
428
485
|
);
|
|
486
|
+
|
|
429
487
|
const decoded = yield* decodeRows(SequenceRow, rows, "advance subscription sequence");
|
|
488
|
+
|
|
430
489
|
if (decoded.length !== 1) return yield* corrupt("subscription sequence");
|
|
490
|
+
|
|
431
491
|
return decoded[0].sequence;
|
|
432
492
|
});
|
|
493
|
+
|
|
433
494
|
const writeRegistration = Effect.fn("SqliteSubscriptionStore.writeRegistration")(function* (
|
|
434
495
|
record: SubscriptionRecord,
|
|
435
496
|
) {
|
|
436
497
|
const json = yield* encode(SubscriptionRecord, record, "encode subscription");
|
|
498
|
+
|
|
437
499
|
yield* query(
|
|
438
500
|
sql<Record<string, unknown>>`
|
|
439
501
|
UPDATE effect_agent_subscriptions SET state=${record.state}, expires_at_millis=${record.configuration.expiresAtMillis},
|
|
@@ -444,10 +506,12 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
444
506
|
"write subscription",
|
|
445
507
|
);
|
|
446
508
|
});
|
|
509
|
+
|
|
447
510
|
const writeEvent = Effect.fn("SqliteSubscriptionStore.writeEvent")(function* (
|
|
448
511
|
event: AcceptedEvent,
|
|
449
512
|
) {
|
|
450
513
|
const json = yield* encode(AcceptedEvent, event, "encode event");
|
|
514
|
+
|
|
451
515
|
yield* query(
|
|
452
516
|
sql<Record<string, unknown>>`
|
|
453
517
|
UPDATE effect_agent_subscription_events SET cursor=${event.cursor}, routing_complete=${event.routingComplete ? 1 : 0},
|
|
@@ -457,10 +521,12 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
457
521
|
"write event",
|
|
458
522
|
);
|
|
459
523
|
});
|
|
524
|
+
|
|
460
525
|
const writeDelivery = Effect.fn("SqliteSubscriptionStore.writeDelivery")(function* (
|
|
461
526
|
delivery: SubscriptionDelivery,
|
|
462
527
|
) {
|
|
463
528
|
const json = yield* encode(SubscriptionDelivery, delivery, "encode delivery");
|
|
529
|
+
|
|
464
530
|
yield* query(
|
|
465
531
|
sql<Record<string, unknown>>`
|
|
466
532
|
UPDATE effect_agent_subscription_deliveries SET state=${delivery.state},
|
|
@@ -478,13 +544,17 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
478
544
|
)(function* (input, inputLimits) {
|
|
479
545
|
const record = yield* validate(SubscriptionRecord, input, "register-record");
|
|
480
546
|
const limits = yield* validate(SubscriptionLimits, inputLimits, "register-limits");
|
|
547
|
+
|
|
481
548
|
yield* requirePartition(record.key.partition, "register-partition");
|
|
549
|
+
|
|
482
550
|
const result = yield* transact(
|
|
483
551
|
Effect.gen(function* () {
|
|
484
552
|
const existing = yield* readRegistration(record.key, "register-existing");
|
|
553
|
+
|
|
485
554
|
if (existing !== null) {
|
|
486
555
|
if (existing.creationFingerprint !== record.creationFingerprint)
|
|
487
556
|
return yield* error("conflict", "registration-identity");
|
|
557
|
+
|
|
488
558
|
return { value: existing, changed: false } as const;
|
|
489
559
|
}
|
|
490
560
|
if (bytes(record.configuration.context) > limits.maxContextBytes)
|
|
@@ -516,6 +586,7 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
516
586
|
return yield* error("capacity", "owner-registrations");
|
|
517
587
|
const assigned = { ...record, ordinal: yield* nextSequence() };
|
|
518
588
|
const json = yield* encode(SubscriptionRecord, assigned, "encode registration");
|
|
589
|
+
|
|
519
590
|
yield* failpoint.hit("subscription:register:before");
|
|
520
591
|
yield* query(
|
|
521
592
|
sql<Record<string, unknown>>`
|
|
@@ -527,10 +598,13 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
527
598
|
`,
|
|
528
599
|
"insert registration",
|
|
529
600
|
);
|
|
601
|
+
|
|
530
602
|
return { value: assigned, changed: true } as const;
|
|
531
603
|
}),
|
|
532
604
|
);
|
|
605
|
+
|
|
533
606
|
if (result.changed) yield* failpoint.hit("subscription:register:after");
|
|
607
|
+
|
|
534
608
|
return result.value;
|
|
535
609
|
});
|
|
536
610
|
|
|
@@ -549,6 +623,7 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
549
623
|
`,
|
|
550
624
|
"list subscriptions",
|
|
551
625
|
);
|
|
626
|
+
|
|
552
627
|
const decoded = yield* decodeRows(
|
|
553
628
|
Schema.Struct({
|
|
554
629
|
owner_id: Schema.String,
|
|
@@ -558,6 +633,7 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
558
633
|
rows,
|
|
559
634
|
"list subscriptions",
|
|
560
635
|
);
|
|
636
|
+
|
|
561
637
|
return yield* Effect.forEach(
|
|
562
638
|
decoded,
|
|
563
639
|
Effect.fn("SqliteSubscriptionStore.listRecord")(function* (row) {
|
|
@@ -565,8 +641,10 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
565
641
|
{ partition, ownerId: row.owner_id, subscriptionId: row.subscription_id },
|
|
566
642
|
"list subscription",
|
|
567
643
|
);
|
|
644
|
+
|
|
568
645
|
if (record === null || record.ordinal !== row.ordinal)
|
|
569
646
|
return yield* corrupt("list subscription projection");
|
|
647
|
+
|
|
570
648
|
return record;
|
|
571
649
|
}),
|
|
572
650
|
);
|
|
@@ -577,18 +655,24 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
577
655
|
"SqliteSubscriptionStore.cancel",
|
|
578
656
|
)(function* (input) {
|
|
579
657
|
const key = yield* requireKey(input, "cancel-key");
|
|
658
|
+
|
|
580
659
|
const result = yield* transact(
|
|
581
660
|
Effect.gen(function* () {
|
|
582
661
|
const current = yield* readRegistration(key, "cancel subscription");
|
|
662
|
+
|
|
583
663
|
if (current === null) return yield* error("not-found", "subscription");
|
|
584
664
|
if (current.state === "cancelled") return { value: current, changed: false } as const;
|
|
585
665
|
const updated = { ...current, state: "cancelled" as const, recovery: null };
|
|
666
|
+
|
|
586
667
|
yield* failpoint.hit("subscription:cancel:before");
|
|
587
668
|
yield* writeRegistration(updated);
|
|
669
|
+
|
|
588
670
|
return { value: updated, changed: true } as const;
|
|
589
671
|
}),
|
|
590
672
|
);
|
|
673
|
+
|
|
591
674
|
if (result.changed) yield* failpoint.hit("subscription:cancel:after");
|
|
675
|
+
|
|
592
676
|
return result.value;
|
|
593
677
|
});
|
|
594
678
|
|
|
@@ -597,13 +681,17 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
597
681
|
)(function* (input, inputLimits) {
|
|
598
682
|
const event = yield* validate(AcceptedEvent, input, "accept-event");
|
|
599
683
|
const limits = yield* validate(SubscriptionLimits, inputLimits, "accept-limits");
|
|
684
|
+
|
|
600
685
|
yield* requirePartition(event.partition, "accept-partition");
|
|
686
|
+
|
|
601
687
|
const result = yield* transact(
|
|
602
688
|
Effect.gen(function* () {
|
|
603
689
|
const existing = yield* readEvent(event.eventId, "accept event");
|
|
690
|
+
|
|
604
691
|
if (existing !== null) {
|
|
605
692
|
if (!sameAcceptedEventIdentity(existing, event))
|
|
606
693
|
return yield* error("conflict", "event-identity");
|
|
694
|
+
|
|
607
695
|
return { value: existing, changed: false } as const;
|
|
608
696
|
}
|
|
609
697
|
if (bytes(event.payload) > limits.maxPayloadBytes)
|
|
@@ -617,6 +705,7 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
617
705
|
)) >= limits.maxEvents
|
|
618
706
|
)
|
|
619
707
|
return yield* error("capacity", "events");
|
|
708
|
+
|
|
620
709
|
const accepted: AcceptedEvent = {
|
|
621
710
|
...event,
|
|
622
711
|
cutoff: yield* nextSequence(),
|
|
@@ -624,7 +713,9 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
624
713
|
routingComplete: false,
|
|
625
714
|
routingFailure: null,
|
|
626
715
|
};
|
|
716
|
+
|
|
627
717
|
const json = yield* encode(AcceptedEvent, accepted, "encode accepted event");
|
|
718
|
+
|
|
628
719
|
yield* failpoint.hit("subscription:accept:before");
|
|
629
720
|
yield* query(
|
|
630
721
|
sql<Record<string, unknown>>`
|
|
@@ -635,10 +726,13 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
635
726
|
`,
|
|
636
727
|
"insert event",
|
|
637
728
|
);
|
|
729
|
+
|
|
638
730
|
return { value: accepted, changed: true } as const;
|
|
639
731
|
}),
|
|
640
732
|
);
|
|
733
|
+
|
|
641
734
|
if (result.changed) yield* failpoint.hit("subscription:accept:after");
|
|
735
|
+
|
|
642
736
|
return result.value;
|
|
643
737
|
});
|
|
644
738
|
|
|
@@ -656,6 +750,7 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
656
750
|
`,
|
|
657
751
|
"pending events",
|
|
658
752
|
);
|
|
753
|
+
|
|
659
754
|
return yield* decodeRows(
|
|
660
755
|
Schema.Struct({ event_id: Schema.String }),
|
|
661
756
|
rows,
|
|
@@ -667,10 +762,13 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
667
762
|
"SqliteSubscriptionStore.candidates",
|
|
668
763
|
)(function* (input, limit) {
|
|
669
764
|
const supplied = yield* validate(AcceptedEvent, input, "candidates-event");
|
|
765
|
+
|
|
670
766
|
yield* requirePartition(supplied.partition, "candidates-partition");
|
|
671
767
|
const stored = yield* readEvent(supplied.eventId, "candidates event");
|
|
768
|
+
|
|
672
769
|
if (stored === null) return yield* error("not-found", "event");
|
|
673
770
|
if (!sameAcceptedEventIdentity(stored, supplied)) return yield* error("conflict", "event");
|
|
771
|
+
|
|
674
772
|
const rows = yield* query(
|
|
675
773
|
sql<Record<string, unknown>>`
|
|
676
774
|
SELECT owner_id, subscription_id, ordinal FROM effect_agent_subscriptions WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}
|
|
@@ -679,6 +777,7 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
679
777
|
`,
|
|
680
778
|
"subscription candidates",
|
|
681
779
|
);
|
|
780
|
+
|
|
682
781
|
const decoded = yield* decodeRows(
|
|
683
782
|
Schema.Struct({
|
|
684
783
|
owner_id: Schema.String,
|
|
@@ -688,6 +787,7 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
688
787
|
rows,
|
|
689
788
|
"subscription candidates",
|
|
690
789
|
);
|
|
790
|
+
|
|
691
791
|
return yield* Effect.forEach(
|
|
692
792
|
decoded,
|
|
693
793
|
Effect.fn("SqliteSubscriptionStore.candidateRecord")(function* (row) {
|
|
@@ -695,8 +795,10 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
695
795
|
{ partition, ownerId: row.owner_id, subscriptionId: row.subscription_id },
|
|
696
796
|
"subscription candidate",
|
|
697
797
|
);
|
|
798
|
+
|
|
698
799
|
if (record === null || record.ordinal !== row.ordinal)
|
|
699
800
|
return yield* corrupt("subscription candidate projection");
|
|
801
|
+
|
|
700
802
|
return record;
|
|
701
803
|
}),
|
|
702
804
|
);
|
|
@@ -706,6 +808,7 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
706
808
|
delivery: SubscriptionDelivery,
|
|
707
809
|
) {
|
|
708
810
|
const json = yield* encode(SubscriptionDelivery, delivery, "encode selected delivery");
|
|
811
|
+
|
|
709
812
|
yield* query(
|
|
710
813
|
sql<Record<string, unknown>>`
|
|
711
814
|
INSERT INTO effect_agent_subscription_deliveries (tenant_id, source_address, owner_id, subscription_id, event_id,
|
|
@@ -721,18 +824,23 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
721
824
|
"SqliteSubscriptionStore.select",
|
|
722
825
|
)(function* (inputEvent, inputDeliveries, cursor, complete, nowMillis, inputLimits) {
|
|
723
826
|
const supplied = yield* validate(AcceptedEvent, inputEvent, "select-event");
|
|
827
|
+
|
|
724
828
|
const deliveries = yield* validate(
|
|
725
829
|
Schema.Array(SubscriptionDelivery),
|
|
726
830
|
inputDeliveries,
|
|
727
831
|
"select-deliveries",
|
|
728
832
|
);
|
|
833
|
+
|
|
729
834
|
const limits = yield* validate(SubscriptionLimits, inputLimits, "select-limits");
|
|
835
|
+
|
|
730
836
|
yield* requirePartition(supplied.partition, "select-partition");
|
|
731
837
|
for (const candidate of deliveries)
|
|
732
838
|
yield* requirePartition(candidate.key.subscription.partition, "select-delivery-partition");
|
|
839
|
+
|
|
733
840
|
const changed = yield* transact(
|
|
734
841
|
Effect.gen(function* () {
|
|
735
842
|
const accepted = yield* readEvent(supplied.eventId, "select event");
|
|
843
|
+
|
|
736
844
|
if (accepted === null) return yield* error("not-found", "event");
|
|
737
845
|
if (!sameAcceptedEventIdentity(accepted, supplied) || accepted.cursor !== supplied.cursor)
|
|
738
846
|
return yield* error("conflict", "event-cursor");
|
|
@@ -742,8 +850,10 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
742
850
|
yield* failpoint.hit("subscription:select:before");
|
|
743
851
|
const effectiveNowMillis = Math.max(nowMillis, yield* Clock.currentTimeMillis);
|
|
744
852
|
const additions: Array<{ delivery: SubscriptionDelivery; record: SubscriptionRecord }> = [];
|
|
853
|
+
|
|
745
854
|
for (const delivery of deliveries) {
|
|
746
855
|
const record = yield* readRegistration(delivery.key.subscription, "select registration");
|
|
856
|
+
|
|
747
857
|
if (record === null) return yield* error("not-found", "subscription");
|
|
748
858
|
if (
|
|
749
859
|
!subscriptionDeliveryCanSelect(delivery, record, accepted) ||
|
|
@@ -755,6 +865,7 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
755
865
|
)
|
|
756
866
|
return yield* error("conflict", "selection");
|
|
757
867
|
const existing = yield* readDelivery(delivery.key, "select existing delivery");
|
|
868
|
+
|
|
758
869
|
if (existing !== null) {
|
|
759
870
|
if (!sameDeliveryIdentity(existing, delivery))
|
|
760
871
|
return yield* error("conflict", "delivery-identity");
|
|
@@ -763,12 +874,14 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
763
874
|
if (!subscriptionCanSelect(record, accepted, effectiveNowMillis, false)) continue;
|
|
764
875
|
additions.push({ delivery, record });
|
|
765
876
|
}
|
|
877
|
+
|
|
766
878
|
const total = yield* count(
|
|
767
879
|
sql<
|
|
768
880
|
Record<string, unknown>
|
|
769
881
|
>`SELECT COUNT(*) AS count FROM effect_agent_subscription_deliveries WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}`,
|
|
770
882
|
"count deliveries",
|
|
771
883
|
);
|
|
884
|
+
|
|
772
885
|
if (total + additions.length > limits.maxDeliveries)
|
|
773
886
|
return yield* error("capacity", "deliveries");
|
|
774
887
|
for (const ownerId of new Set(additions.map(({ record }) => record.key.ownerId))) {
|
|
@@ -778,6 +891,7 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
778
891
|
>`SELECT COUNT(*) AS count FROM effect_agent_subscription_deliveries WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address} AND owner_id=${ownerId}`,
|
|
779
892
|
"count owner deliveries",
|
|
780
893
|
);
|
|
894
|
+
|
|
781
895
|
if (
|
|
782
896
|
existing + additions.filter(({ record }) => record.key.ownerId === ownerId).length >
|
|
783
897
|
limits.maxDeliveriesPerOwner
|
|
@@ -790,9 +904,11 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
790
904
|
yield* writeRegistration({ ...addition.record, state: "consumed", recovery: null });
|
|
791
905
|
}
|
|
792
906
|
yield* writeEvent({ ...accepted, cursor, routingComplete: complete, routingFailure: null });
|
|
907
|
+
|
|
793
908
|
return true;
|
|
794
909
|
}),
|
|
795
910
|
);
|
|
911
|
+
|
|
796
912
|
if (changed) yield* failpoint.hit("subscription:select:after");
|
|
797
913
|
});
|
|
798
914
|
|
|
@@ -802,12 +918,15 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
802
918
|
const supplied = yield* validate(AcceptedEvent, inputEvent, "catch-up-event");
|
|
803
919
|
const delivery = yield* validate(SubscriptionDelivery, inputDelivery, "catch-up-delivery");
|
|
804
920
|
const limits = yield* validate(SubscriptionLimits, inputLimits, "catch-up-limits");
|
|
921
|
+
|
|
805
922
|
yield* requirePartition(supplied.partition, "catch-up-partition");
|
|
806
923
|
yield* requirePartition(delivery.key.subscription.partition, "catch-up-delivery-partition");
|
|
924
|
+
|
|
807
925
|
const changed = yield* transact(
|
|
808
926
|
Effect.gen(function* () {
|
|
809
927
|
const accepted = yield* readEvent(supplied.eventId, "catch-up event");
|
|
810
928
|
const record = yield* readRegistration(delivery.key.subscription, "catch-up subscription");
|
|
929
|
+
|
|
811
930
|
if (accepted === null || record === null)
|
|
812
931
|
return yield* error("not-found", accepted === null ? "event" : "subscription");
|
|
813
932
|
if (
|
|
@@ -820,13 +939,16 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
820
939
|
)
|
|
821
940
|
return yield* error("conflict", "catch-up-identity");
|
|
822
941
|
const existing = yield* readDelivery(delivery.key, "catch-up existing delivery");
|
|
942
|
+
|
|
823
943
|
if (existing !== null) {
|
|
824
944
|
if (!sameDeliveryIdentity(existing, delivery))
|
|
825
945
|
return yield* error("conflict", "delivery-identity");
|
|
946
|
+
|
|
826
947
|
return false;
|
|
827
948
|
}
|
|
828
949
|
yield* failpoint.hit("subscription:catch-up:before");
|
|
829
950
|
const effectiveNowMillis = Math.max(nowMillis, yield* Clock.currentTimeMillis);
|
|
951
|
+
|
|
830
952
|
if (!subscriptionCanSelect(record, accepted, effectiveNowMillis, true))
|
|
831
953
|
return yield* error("conflict", "catch-up-eligibility");
|
|
832
954
|
if (
|
|
@@ -849,9 +971,11 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
849
971
|
return yield* error("capacity", "owner-deliveries");
|
|
850
972
|
yield* insertDelivery(delivery);
|
|
851
973
|
yield* writeRegistration({ ...record, state: "consumed", recovery: null });
|
|
974
|
+
|
|
852
975
|
return true;
|
|
853
976
|
}),
|
|
854
977
|
);
|
|
978
|
+
|
|
855
979
|
if (changed) yield* failpoint.hit("subscription:catch-up:after");
|
|
856
980
|
});
|
|
857
981
|
|
|
@@ -862,9 +986,11 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
862
986
|
code === undefined
|
|
863
987
|
? "routing-failed"
|
|
864
988
|
: yield* validate(SubscriptionName, code, "routing-failure");
|
|
989
|
+
|
|
865
990
|
yield* transact(
|
|
866
991
|
Effect.gen(function* () {
|
|
867
992
|
const accepted = yield* readEvent(eventId, "defer event");
|
|
993
|
+
|
|
868
994
|
if (accepted === null) return yield* error("not-found", "event");
|
|
869
995
|
yield* failpoint.hit("subscription:defer-event:before");
|
|
870
996
|
yield* writeEvent({ ...accepted, nextAttemptAtMillis, routingFailure });
|
|
@@ -877,7 +1003,9 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
877
1003
|
"SqliteSubscriptionStore.delivery",
|
|
878
1004
|
)(function* (input) {
|
|
879
1005
|
const key = yield* validate(SubscriptionDeliveryKey, input, "delivery-key");
|
|
1006
|
+
|
|
880
1007
|
yield* requirePartition(key.subscription.partition, "delivery-partition");
|
|
1008
|
+
|
|
881
1009
|
return yield* readDelivery(key, "get delivery");
|
|
882
1010
|
});
|
|
883
1011
|
|
|
@@ -892,11 +1020,13 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
892
1020
|
`,
|
|
893
1021
|
"pending deliveries",
|
|
894
1022
|
);
|
|
1023
|
+
|
|
895
1024
|
const rowSchema = Schema.Struct({
|
|
896
1025
|
owner_id: Schema.String,
|
|
897
1026
|
subscription_id: Schema.String,
|
|
898
1027
|
event_id: Schema.String,
|
|
899
1028
|
});
|
|
1029
|
+
|
|
900
1030
|
return yield* decodeRows(rowSchema, rows, "pending delivery keys").pipe(
|
|
901
1031
|
Effect.map((items) =>
|
|
902
1032
|
items.map((item) => ({
|
|
@@ -911,6 +1041,7 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
911
1041
|
"SqliteSubscriptionStore.listDeliveries",
|
|
912
1042
|
)(function* (input, after, limit) {
|
|
913
1043
|
const key = yield* requireKey(input, "list-deliveries-key");
|
|
1044
|
+
|
|
914
1045
|
const rows = yield* query(
|
|
915
1046
|
sql<Record<string, unknown>>`
|
|
916
1047
|
SELECT record_json FROM effect_agent_subscription_deliveries WHERE tenant_id=${partition.tenantId} AND source_address=${partition.address}
|
|
@@ -918,7 +1049,9 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
918
1049
|
`,
|
|
919
1050
|
"list deliveries",
|
|
920
1051
|
);
|
|
1052
|
+
|
|
921
1053
|
const decoded = yield* decodeRows(JsonRow, rows, "list deliveries");
|
|
1054
|
+
|
|
922
1055
|
return yield* Effect.forEach(decoded, (row) =>
|
|
923
1056
|
decode(SubscriptionDelivery, row.record_json, "list delivery"),
|
|
924
1057
|
);
|
|
@@ -930,32 +1063,41 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
930
1063
|
const key = yield* validate(SubscriptionDeliveryKey, inputKey, "change-delivery-key");
|
|
931
1064
|
const deliveryId = yield* validate(Digest, inputDeliveryId, "change-delivery-id");
|
|
932
1065
|
const change = yield* validate(DeliveryChange, inputChange, "change-delivery-change");
|
|
1066
|
+
|
|
933
1067
|
yield* requirePartition(key.subscription.partition, "change-delivery-partition");
|
|
1068
|
+
|
|
934
1069
|
const result = yield* transact(
|
|
935
1070
|
Effect.gen(function* () {
|
|
936
1071
|
const existing = yield* readDelivery(key, "change delivery");
|
|
937
1072
|
const record = yield* readRegistration(key.subscription, "change delivery subscription");
|
|
1073
|
+
|
|
938
1074
|
if (existing === null || record === null)
|
|
939
1075
|
return yield* error("not-found", existing === null ? "delivery" : "subscription");
|
|
940
1076
|
yield* failpoint.hit(`subscription:delivery-${change._tag.toLowerCase()}:before`);
|
|
1077
|
+
|
|
941
1078
|
const effectiveChange =
|
|
942
1079
|
change._tag === "Prepare"
|
|
943
1080
|
? { ...change, nowMillis: Math.max(change.nowMillis, yield* Clock.currentTimeMillis) }
|
|
944
1081
|
: change;
|
|
1082
|
+
|
|
945
1083
|
const transition = applySubscriptionDeliveryChange(
|
|
946
1084
|
existing,
|
|
947
1085
|
record,
|
|
948
1086
|
deliveryId,
|
|
949
1087
|
effectiveChange,
|
|
950
1088
|
);
|
|
1089
|
+
|
|
951
1090
|
if (Result.isFailure(transition)) return yield* transition.failure;
|
|
952
1091
|
if (transition.success === existing) return { value: existing, changed: false } as const;
|
|
953
1092
|
yield* writeDelivery(transition.success);
|
|
1093
|
+
|
|
954
1094
|
return { value: transition.success, changed: true } as const;
|
|
955
1095
|
}),
|
|
956
1096
|
);
|
|
1097
|
+
|
|
957
1098
|
if (result.changed)
|
|
958
1099
|
yield* failpoint.hit(`subscription:delivery-${change._tag.toLowerCase()}:after`);
|
|
1100
|
+
|
|
959
1101
|
return result.value;
|
|
960
1102
|
});
|
|
961
1103
|
|
|
@@ -971,11 +1113,13 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
971
1113
|
`,
|
|
972
1114
|
"recovering subscriptions",
|
|
973
1115
|
);
|
|
1116
|
+
|
|
974
1117
|
const rowSchema = Schema.Struct({
|
|
975
1118
|
owner_id: Schema.String,
|
|
976
1119
|
subscription_id: Schema.String,
|
|
977
1120
|
ordinal: Schema.Natural,
|
|
978
1121
|
});
|
|
1122
|
+
|
|
979
1123
|
return yield* decodeRows(rowSchema, rows, "recovering subscription keys").pipe(
|
|
980
1124
|
Effect.map((items) =>
|
|
981
1125
|
items.map((item) => ({
|
|
@@ -990,9 +1134,11 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
990
1134
|
"SqliteSubscriptionStore.deferRecovery",
|
|
991
1135
|
)(function* (input, recovery) {
|
|
992
1136
|
const key = yield* requireKey(input, "defer-recovery-key");
|
|
1137
|
+
|
|
993
1138
|
yield* transact(
|
|
994
1139
|
Effect.gen(function* () {
|
|
995
1140
|
const record = yield* readRegistration(key, "defer recovery");
|
|
1141
|
+
|
|
996
1142
|
if (record === null) return yield* error("not-found", "subscription");
|
|
997
1143
|
yield* failpoint.hit("subscription:defer-recovery:before");
|
|
998
1144
|
yield* writeRegistration({
|
|
@@ -1013,8 +1159,11 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
1013
1159
|
`,
|
|
1014
1160
|
"read subscription scan cursors",
|
|
1015
1161
|
);
|
|
1162
|
+
|
|
1016
1163
|
const decoded = yield* decodeRows(ScanRow, rows, "read subscription scan cursors");
|
|
1164
|
+
|
|
1017
1165
|
if (decoded.length !== 1) return yield* corrupt("subscription scan cursors");
|
|
1166
|
+
|
|
1018
1167
|
return {
|
|
1019
1168
|
events: decoded[0].event_scan_cursor,
|
|
1020
1169
|
deliveries: decoded[0].delivery_scan_cursor,
|
|
@@ -1026,6 +1175,7 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
1026
1175
|
"SqliteSubscriptionStore.advanceScanCursors",
|
|
1027
1176
|
)(function* (input) {
|
|
1028
1177
|
const cursors = yield* validate(SubscriptionScanCursors, input, "scan-cursors");
|
|
1178
|
+
|
|
1029
1179
|
yield* transact(
|
|
1030
1180
|
Effect.gen(function* () {
|
|
1031
1181
|
yield* failpoint.hit("subscription:advance-scan-cursors:before");
|
|
@@ -1071,7 +1221,9 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
1071
1221
|
|
|
1072
1222
|
const nextDeadline = Effect.gen(function* () {
|
|
1073
1223
|
const cursors = yield* readScanCursors;
|
|
1224
|
+
|
|
1074
1225
|
if (cursors.events !== "" || cursors.deliveries !== "" || cursors.recovery !== 0) return 0;
|
|
1226
|
+
|
|
1075
1227
|
return yield* indexedDeadline;
|
|
1076
1228
|
});
|
|
1077
1229
|
|
|
@@ -1079,6 +1231,7 @@ const makeSubscriptionStore = Effect.fn("SqliteSubscriptionStore.make")(function
|
|
|
1079
1231
|
yield* transactions.run((replace) => replaceAlarm(replace, deadlineAtMillis));
|
|
1080
1232
|
yield* failpoint.hit("subscription:prearm:after");
|
|
1081
1233
|
});
|
|
1234
|
+
|
|
1082
1235
|
const reconcile = Effect.gen(function* () {
|
|
1083
1236
|
yield* transactions.run((replace) =>
|
|
1084
1237
|
Effect.gen(function* () {
|