@kairos-es/feed 0.0.0-stage → 0.0.0
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/LICENSE +28 -0
- package/README.md +109 -2
- package/dist/cjs/Feed.js +6 -0
- package/dist/cjs/Feed.js.map +1 -0
- package/dist/cjs/FeedSink.js +54 -0
- package/dist/cjs/FeedSink.js.map +1 -0
- package/dist/cjs/FeedSyncStore.js +10 -0
- package/dist/cjs/FeedSyncStore.js.map +1 -0
- package/dist/cjs/deliver.js +86 -0
- package/dist/cjs/deliver.js.map +1 -0
- package/dist/cjs/errors.js +114 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/identifiers.js +63 -0
- package/dist/cjs/identifiers.js.map +1 -0
- package/dist/cjs/inMemoryFeed.js +328 -0
- package/dist/cjs/inMemoryFeed.js.map +1 -0
- package/dist/cjs/inMemorySink.js +142 -0
- package/dist/cjs/inMemorySink.js.map +1 -0
- package/dist/cjs/index.js +184 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/inspect.js +68 -0
- package/dist/cjs/inspect.js.map +1 -0
- package/dist/cjs/readiness.js +100 -0
- package/dist/cjs/readiness.js.map +1 -0
- package/dist/cjs/reconcile.js +164 -0
- package/dist/cjs/reconcile.js.map +1 -0
- package/dist/cjs/runFeedSync.js +603 -0
- package/dist/cjs/runFeedSync.js.map +1 -0
- package/dist/cjs/tuning.js +60 -0
- package/dist/cjs/tuning.js.map +1 -0
- package/dist/dts-cjs/Feed.d.ts +99 -0
- package/dist/dts-cjs/Feed.d.ts.map +1 -0
- package/dist/dts-cjs/FeedSink.d.ts +189 -0
- package/dist/dts-cjs/FeedSink.d.ts.map +1 -0
- package/dist/dts-cjs/FeedSyncStore.d.ts +281 -0
- package/dist/dts-cjs/FeedSyncStore.d.ts.map +1 -0
- package/dist/dts-cjs/deliver.d.ts +16 -0
- package/dist/dts-cjs/deliver.d.ts.map +1 -0
- package/dist/dts-cjs/errors.d.ts +131 -0
- package/dist/dts-cjs/errors.d.ts.map +1 -0
- package/dist/dts-cjs/identifiers.d.ts +67 -0
- package/dist/dts-cjs/identifiers.d.ts.map +1 -0
- package/dist/dts-cjs/inMemoryFeed.d.ts +32 -0
- package/dist/dts-cjs/inMemoryFeed.d.ts.map +1 -0
- package/dist/dts-cjs/inMemorySink.d.ts +61 -0
- package/dist/dts-cjs/inMemorySink.d.ts.map +1 -0
- package/dist/dts-cjs/index.d.ts +63 -0
- package/dist/dts-cjs/index.d.ts.map +1 -0
- package/dist/dts-cjs/inspect.d.ts +71 -0
- package/dist/dts-cjs/inspect.d.ts.map +1 -0
- package/dist/dts-cjs/package.json +3 -0
- package/dist/dts-cjs/readiness.d.ts +84 -0
- package/dist/dts-cjs/readiness.d.ts.map +1 -0
- package/dist/dts-cjs/reconcile.d.ts +68 -0
- package/dist/dts-cjs/reconcile.d.ts.map +1 -0
- package/dist/dts-cjs/runFeedSync.d.ts +220 -0
- package/dist/dts-cjs/runFeedSync.d.ts.map +1 -0
- package/dist/dts-cjs/tuning.d.ts +40 -0
- package/dist/dts-cjs/tuning.d.ts.map +1 -0
- package/dist/dts-esm/Feed.d.ts +99 -0
- package/dist/dts-esm/Feed.d.ts.map +1 -0
- package/dist/dts-esm/FeedSink.d.ts +189 -0
- package/dist/dts-esm/FeedSink.d.ts.map +1 -0
- package/dist/dts-esm/FeedSyncStore.d.ts +281 -0
- package/dist/dts-esm/FeedSyncStore.d.ts.map +1 -0
- package/dist/dts-esm/deliver.d.ts +16 -0
- package/dist/dts-esm/deliver.d.ts.map +1 -0
- package/dist/dts-esm/errors.d.ts +131 -0
- package/dist/dts-esm/errors.d.ts.map +1 -0
- package/dist/dts-esm/identifiers.d.ts +67 -0
- package/dist/dts-esm/identifiers.d.ts.map +1 -0
- package/dist/dts-esm/inMemoryFeed.d.ts +32 -0
- package/dist/dts-esm/inMemoryFeed.d.ts.map +1 -0
- package/dist/dts-esm/inMemorySink.d.ts +61 -0
- package/dist/dts-esm/inMemorySink.d.ts.map +1 -0
- package/dist/dts-esm/index.d.ts +63 -0
- package/dist/dts-esm/index.d.ts.map +1 -0
- package/dist/dts-esm/inspect.d.ts +71 -0
- package/dist/dts-esm/inspect.d.ts.map +1 -0
- package/dist/dts-esm/package.json +3 -0
- package/dist/dts-esm/readiness.d.ts +84 -0
- package/dist/dts-esm/readiness.d.ts.map +1 -0
- package/dist/dts-esm/reconcile.d.ts +68 -0
- package/dist/dts-esm/reconcile.d.ts.map +1 -0
- package/dist/dts-esm/runFeedSync.d.ts +220 -0
- package/dist/dts-esm/runFeedSync.d.ts.map +1 -0
- package/dist/dts-esm/tuning.d.ts +40 -0
- package/dist/dts-esm/tuning.d.ts.map +1 -0
- package/dist/esm/Feed.js +2 -0
- package/dist/esm/Feed.js.map +1 -0
- package/dist/esm/FeedSink.js +46 -0
- package/dist/esm/FeedSink.js.map +1 -0
- package/dist/esm/FeedSyncStore.js +3 -0
- package/dist/esm/FeedSyncStore.js.map +1 -0
- package/dist/esm/deliver.js +78 -0
- package/dist/esm/deliver.js.map +1 -0
- package/dist/esm/errors.js +98 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/identifiers.js +56 -0
- package/dist/esm/identifiers.js.map +1 -0
- package/dist/esm/inMemoryFeed.js +320 -0
- package/dist/esm/inMemoryFeed.js.map +1 -0
- package/dist/esm/inMemorySink.js +134 -0
- package/dist/esm/inMemorySink.js.map +1 -0
- package/dist/esm/index.js +62 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/inspect.js +58 -0
- package/dist/esm/inspect.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/dist/esm/readiness.js +92 -0
- package/dist/esm/readiness.js.map +1 -0
- package/dist/esm/reconcile.js +153 -0
- package/dist/esm/reconcile.js.map +1 -0
- package/dist/esm/runFeedSync.js +595 -0
- package/dist/esm/runFeedSync.js.map +1 -0
- package/dist/esm/tuning.js +51 -0
- package/dist/esm/tuning.js.map +1 -0
- package/package.json +34 -3
- package/src/Feed.ts +111 -0
- package/src/FeedSink.ts +130 -0
- package/src/FeedSyncStore.ts +326 -0
- package/src/deliver.ts +108 -0
- package/src/errors.ts +134 -0
- package/src/identifiers.ts +92 -0
- package/src/inMemoryFeed.ts +582 -0
- package/src/inMemorySink.ts +215 -0
- package/src/index.ts +136 -0
- package/src/inspect.ts +114 -0
- package/src/readiness.ts +137 -0
- package/src/reconcile.ts +224 -0
- package/src/runFeedSync.ts +882 -0
- package/src/tuning.ts +65 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FeedSink.js","names":["Data","Option","FeedChange","taggedEnum","changeOf","row","match","doc","onNone","Delete","key","onSome","Upsert"],"sources":["../../src/FeedSink.ts"],"sourcesContent":[null],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,SAASA,IAAI,EAAeC,MAAM,QAAqB,QAAQ;AAiB/D;AACA,OAAO,MAAMC,UAAU,gBAAGF,IAAI,CAACG,UAAU,EAAwB;AAEjE;AACA,OAAO,MAAMC,QAAQ,GAAOC,GAAe,IACzCJ,MAAM,CAACK,KAAK,CAACD,GAAG,CAACE,GAAG,EAAE;EACpBC,MAAM,EAAEA,CAAA,KAAMN,UAAU,CAACO,MAAM,CAAC;IAAEC,GAAG,EAAEL,GAAG,CAACK;EAAG,CAAE,CAAC;EACjDC,MAAM,EAAGJ,GAAG,IAAKL,UAAU,CAACU,MAAM,CAAC;IAAEF,GAAG,EAAEL,GAAG,CAACK,GAAG;IAAEH;EAAG,CAAE;CACzD,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Whether two optional cursors are the same cursor, as `casCursor` compares them. */
|
|
2
|
+
export const sameCursor = (left, right) => left._tag === 'None' ? right._tag === 'None' : right._tag === 'Some' && left.value.generation === right.value.generation && left.value.seq === right.value.seq;
|
|
3
|
+
//# sourceMappingURL=FeedSyncStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FeedSyncStore.js","names":["sameCursor","left","right","_tag","value","generation","seq"],"sources":["../../src/FeedSyncStore.ts"],"sourcesContent":[null],"mappings":"AA4NA;AACA,OAAO,MAAMA,UAAU,GAAGA,CACxBC,IAA+B,EAC/BC,KAAgC,KAEhCD,IAAI,CAACE,IAAI,KAAK,MAAM,GAChBD,KAAK,CAACC,IAAI,KAAK,MAAM,GACrBD,KAAK,CAACC,IAAI,KAAK,MAAM,IACrBF,IAAI,CAACG,KAAK,CAACC,UAAU,KAAKH,KAAK,CAACE,KAAK,CAACC,UAAU,IAChDJ,IAAI,CAACG,KAAK,CAACE,GAAG,KAAKJ,KAAK,CAACE,KAAK,CAACE,GAAG","ignoreList":[]}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One batch to a sink, end to end: send, park what the sink refuses, send the rest
|
|
3
|
+
* again, then confirm. Package-internal, and the ONE place the driver talks to a
|
|
4
|
+
* sink's write path, so the steady state, a resync's load and a reconcile's
|
|
5
|
+
* deletes cannot each grow their own idea of what a refusal means.
|
|
6
|
+
*
|
|
7
|
+
* ## Why a refusal re-sends the rest rather than skipping the batch
|
|
8
|
+
*
|
|
9
|
+
* A sink that refuses one record of a batch has, by the port's contract, applied
|
|
10
|
+
* none of it — Algolia, the first sink, rejects a whole `batch` call for one
|
|
11
|
+
* oversized record. So the records it did NOT name must go again, without the
|
|
12
|
+
* refused ones, until the sink takes what is left or there is nothing left. Each
|
|
13
|
+
* round removes at least one key, so the loop ends. A sink that names no key of
|
|
14
|
+
* the batch would loop forever instead, and that is a sink bug the driver turns
|
|
15
|
+
* into `SinkHalted` rather than spin on.
|
|
16
|
+
*
|
|
17
|
+
* ## The lease, before every send
|
|
18
|
+
*
|
|
19
|
+
* `ensureHeld` runs before each `send`, not once per batch: a refusal round is a
|
|
20
|
+
* send like any other, and a worker that lost its lease between two rounds must
|
|
21
|
+
* not make the second.
|
|
22
|
+
*/
|
|
23
|
+
import { Array as Arr, DateTime, Effect } from 'effect';
|
|
24
|
+
import { SinkHalted } from "./errors.js";
|
|
25
|
+
/** The reason recorded for a refused key the sink gave no reason for. */
|
|
26
|
+
export const UNEXPLAINED_REFUSAL = 'refused by the sink, with no reason given';
|
|
27
|
+
/**
|
|
28
|
+
* Send `pending` through `send`, parking each refused key with the sink's reason,
|
|
29
|
+
* and confirm the receipt of whatever was finally taken. See the module doc.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export const deliver = args => Effect.gen(function* () {
|
|
34
|
+
let remaining = args.pending;
|
|
35
|
+
const parked = [];
|
|
36
|
+
for (;;) {
|
|
37
|
+
if (!Arr.isReadonlyArrayNonEmpty(remaining)) {
|
|
38
|
+
return {
|
|
39
|
+
sent: [],
|
|
40
|
+
parked
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const batch = Arr.map(remaining, p => p.change);
|
|
44
|
+
yield* args.lease.ensureHeld;
|
|
45
|
+
const sent = yield* args.send(batch).pipe(Effect.map(receipt => ({
|
|
46
|
+
_tag: 'Taken',
|
|
47
|
+
receipt
|
|
48
|
+
})), Effect.catchTag('SinkRejected', rejected => Effect.succeed({
|
|
49
|
+
_tag: 'Refused',
|
|
50
|
+
rejected
|
|
51
|
+
})));
|
|
52
|
+
if (sent._tag === 'Taken') {
|
|
53
|
+
yield* args.confirm(sent.receipt);
|
|
54
|
+
return {
|
|
55
|
+
sent: remaining.map(p => p.change.key),
|
|
56
|
+
parked
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const refused = new Set(sent.rejected.keys);
|
|
60
|
+
const named = remaining.filter(p => refused.has(p.change.key));
|
|
61
|
+
if (named.length === 0) {
|
|
62
|
+
return yield* new SinkHalted({
|
|
63
|
+
reason: `the sink refused keys that are not in the batch (${sent.rejected.keys.join(', ')})`
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const parkedAt = yield* DateTime.now;
|
|
67
|
+
for (const p of named) {
|
|
68
|
+
parked.push({
|
|
69
|
+
key: p.change.key,
|
|
70
|
+
seq: p.seq,
|
|
71
|
+
reason: sent.rejected.reasons.get(p.change.key) ?? UNEXPLAINED_REFUSAL,
|
|
72
|
+
parkedAt
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
remaining = remaining.filter(p => !refused.has(p.change.key));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
//# sourceMappingURL=deliver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deliver.js","names":["Array","Arr","DateTime","Effect","SinkHalted","UNEXPLAINED_REFUSAL","deliver","args","gen","remaining","pending","parked","isReadonlyArrayNonEmpty","sent","batch","map","p","change","lease","ensureHeld","send","pipe","receipt","_tag","catchTag","rejected","succeed","confirm","key","refused","Set","keys","named","filter","has","length","reason","join","parkedAt","now","push","seq","reasons","get"],"sources":["../../src/deliver.ts"],"sourcesContent":[null],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;AAsBA,SAASA,KAAK,IAAIC,GAAG,EAAEC,QAAQ,EAAEC,MAAM,QAAQ,QAAQ;AAEvD,SAGEC,UAAU,QAGL,aAAU;AAiBjB;AACA,OAAO,MAAMC,mBAAmB,GAAG,2CAA2C;AAE9E;;;;;;AAMA,OAAO,MAAMC,OAAO,GAAgBC,IASnC,IAICJ,MAAM,CAACK,GAAG,CAAC,aAAS;EAClB,IAAIC,SAAS,GAA8BF,IAAI,CAACG,OAAO;EACvD,MAAMC,MAAM,GAAwB,EAAE;EACtC,SAAS;IACP,IAAI,CAACV,GAAG,CAACW,uBAAuB,CAACH,SAAS,CAAC,EAAE;MAC3C,OAAO;QAAEI,IAAI,EAAE,EAAE;QAAEF;MAAM,CAAE;IAC7B;IACA,MAAMG,KAAK,GAAGb,GAAG,CAACc,GAAG,CAACN,SAAS,EAAGO,CAAC,IAAKA,CAAC,CAACC,MAAM,CAAC;IACjD,OAAOV,IAAI,CAACW,KAAK,CAACC,UAAU;IAC5B,MAAMN,IAAI,GAAG,OAAON,IAAI,CAACa,IAAI,CAACN,KAAK,CAAC,CAACO,IAAI,CACvClB,MAAM,CAACY,GAAG,CAAEO,OAAO,KAAM;MAAEC,IAAI,EAAE,OAAgB;MAAED;IAAO,CAAE,CAAC,CAAC,EAC9DnB,MAAM,CAACqB,QAAQ,CAAC,cAAc,EAAGC,QAAQ,IACvCtB,MAAM,CAACuB,OAAO,CAAC;MAAEH,IAAI,EAAE,SAAkB;MAAEE;IAAQ,CAAE,CAAC,CACvD,CACF;IACD,IAAIZ,IAAI,CAACU,IAAI,KAAK,OAAO,EAAE;MACzB,OAAOhB,IAAI,CAACoB,OAAO,CAACd,IAAI,CAACS,OAAO,CAAC;MACjC,OAAO;QAAET,IAAI,EAAEJ,SAAS,CAACM,GAAG,CAAEC,CAAC,IAAKA,CAAC,CAACC,MAAM,CAACW,GAAG,CAAC;QAAEjB;MAAM,CAAE;IAC7D;IACA,MAAMkB,OAAO,GAAG,IAAIC,GAAG,CAAUjB,IAAI,CAACY,QAAQ,CAACM,IAAI,CAAC;IACpD,MAAMC,KAAK,GAAGvB,SAAS,CAACwB,MAAM,CAAEjB,CAAC,IAAKa,OAAO,CAACK,GAAG,CAAClB,CAAC,CAACC,MAAM,CAACW,GAAG,CAAC,CAAC;IAChE,IAAII,KAAK,CAACG,MAAM,KAAK,CAAC,EAAE;MACtB,OAAO,OAAO,IAAI/B,UAAU,CAAC;QAC3BgC,MAAM,EAAE,oDAAoDvB,IAAI,CAACY,QAAQ,CAACM,IAAI,CAACM,IAAI,CAAC,IAAI,CAAC;OAC1F,CAAC;IACJ;IACA,MAAMC,QAAQ,GAAG,OAAOpC,QAAQ,CAACqC,GAAG;IACpC,KAAK,MAAMvB,CAAC,IAAIgB,KAAK,EAAE;MACrBrB,MAAM,CAAC6B,IAAI,CAAC;QACVZ,GAAG,EAAEZ,CAAC,CAACC,MAAM,CAACW,GAAG;QACjBa,GAAG,EAAEzB,CAAC,CAACyB,GAAG;QACVL,MAAM,EACJvB,IAAI,CAACY,QAAQ,CAACiB,OAAO,CAACC,GAAG,CAAC3B,CAAC,CAACC,MAAM,CAACW,GAAG,CAAC,IAAIvB,mBAAmB;QAChEiC;OACD,CAAC;IACJ;IACA7B,SAAS,GAAGA,SAAS,CAACwB,MAAM,CAAEjB,CAAC,IAAK,CAACa,OAAO,CAACK,GAAG,CAAClB,CAAC,CAACC,MAAM,CAACW,GAAG,CAAC,CAAC;EACjE;AACF,CAAC,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The change feed's error vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* ## The sink's three answers
|
|
5
|
+
*
|
|
6
|
+
* A sink classifies every failure into one of three, and the driver's whole
|
|
7
|
+
* behaviour on failure follows from which. The names borrow the meaning of
|
|
8
|
+
* `@kairos-es/automation`'s classification without depending on that package: a
|
|
9
|
+
* feed is not an automation (ADR-0019), and a dependency for three class names
|
|
10
|
+
* would make it look like one.
|
|
11
|
+
*
|
|
12
|
+
* - `SinkRetryable` — the sink could not take the batch NOW (a rate limit, a 5xx, a
|
|
13
|
+
* timeout). The cursor stays where it is and the batch is sent again after a
|
|
14
|
+
* back-off, never sooner than `retryAfter` when the sink names one.
|
|
15
|
+
* - `SinkRejected` — the sink will never take THESE records as they stand (too
|
|
16
|
+
* large, invalid). The named keys are parked with the sink's reasons, the rest of
|
|
17
|
+
* the batch is sent again without them, and the cursor moves past the whole page.
|
|
18
|
+
* A parked record is retried when its row next changes.
|
|
19
|
+
* - `SinkHalted` — something is wrong that retrying will not mend (bad
|
|
20
|
+
* credentials, a missing index). The worker stops, and supervision decides.
|
|
21
|
+
*
|
|
22
|
+
* ## The feed's own tables
|
|
23
|
+
*
|
|
24
|
+
* `FeedWriteFailed` and `FeedUnreadable` are infrastructure faults on the feed's
|
|
25
|
+
* own durable state, split by direction: a write that did not land, a read that
|
|
26
|
+
* could not be made. They are tagged errors rather than defects for the reason the
|
|
27
|
+
* `ProjectionStore` port gives for its own: the driver's supervision has to tell
|
|
28
|
+
* "back off and try again" from "give up", and only a value can carry that.
|
|
29
|
+
*
|
|
30
|
+
* ## The worker's lease and cursor
|
|
31
|
+
*
|
|
32
|
+
* `LeaseHeld`, `LeaseLost` and `CursorSuperseded` are the three ways the
|
|
33
|
+
* single-worker guarantee shows itself: another worker holds the lease, this
|
|
34
|
+
* worker's lease went while it ran, or the cursor it expected to advance is not
|
|
35
|
+
* the one stored (a rival moved it, or the lease changed hands).
|
|
36
|
+
*/
|
|
37
|
+
import { Data, Duration } from 'effect';
|
|
38
|
+
/** The sink cannot take the batch now: keep the cursor and back off. */
|
|
39
|
+
export class SinkRetryable extends /*#__PURE__*/Data.TaggedError('SinkRetryable') {
|
|
40
|
+
get message() {
|
|
41
|
+
return this.retryAfter === undefined ? this.reason : `${this.reason} (retry after ${Duration.format(this.retryAfter)})`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** The sink refuses these records as they stand: park them, send the rest. */
|
|
45
|
+
export class SinkRejected extends /*#__PURE__*/Data.TaggedError('SinkRejected') {
|
|
46
|
+
get message() {
|
|
47
|
+
return `the sink refused ${this.keys.length} record(s): ${this.keys.join(', ')}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Something retrying will not mend: stop the worker. */
|
|
51
|
+
export class SinkHalted extends /*#__PURE__*/Data.TaggedError('SinkHalted') {
|
|
52
|
+
get message() {
|
|
53
|
+
return this.reason;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** A write to the feed's own tables did not land. */
|
|
57
|
+
export class FeedWriteFailed extends /*#__PURE__*/Data.TaggedError('FeedWriteFailed') {
|
|
58
|
+
get message() {
|
|
59
|
+
return this.reason;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** A read of the feed's own tables could not be made. */
|
|
63
|
+
export class FeedUnreadable extends /*#__PURE__*/Data.TaggedError('FeedUnreadable') {
|
|
64
|
+
get message() {
|
|
65
|
+
return this.reason;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Another worker holds the lease for this feed and sink. */
|
|
69
|
+
export class LeaseHeld extends /*#__PURE__*/Data.TaggedError('LeaseHeld') {
|
|
70
|
+
get message() {
|
|
71
|
+
return `another worker holds the lease for feed '${this.feed}' and sink '${this.sink}'`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** This worker's lease is no longer its own; it must stop sending at once. */
|
|
75
|
+
export class LeaseLost extends /*#__PURE__*/Data.TaggedError('LeaseLost') {
|
|
76
|
+
get message() {
|
|
77
|
+
return `the lease for feed '${this.feed}' and sink '${this.sink}' was lost`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The cursor compare-and-set lost: the stored cursor is not the one expected, or
|
|
82
|
+
* the lease is not held by the worker that asked. Nothing the call carried landed.
|
|
83
|
+
*/
|
|
84
|
+
export class CursorSuperseded extends /*#__PURE__*/Data.TaggedError('CursorSuperseded') {
|
|
85
|
+
get message() {
|
|
86
|
+
return `the cursor for feed '${this.feed}' and sink '${this.sink}' was superseded`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The sync worker gave up: it restarted `restarts` times in a row without moving
|
|
91
|
+
* its cursor once. `reason` is the failure that ended the last attempt.
|
|
92
|
+
*/
|
|
93
|
+
export class FeedSyncStalled extends /*#__PURE__*/Data.TaggedError('FeedSyncStalled') {
|
|
94
|
+
get message() {
|
|
95
|
+
return `the sync of feed '${this.feed}' to sink '${this.sink}' made no progress in ${this.restarts} consecutive restarts`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","names":["Data","Duration","SinkRetryable","TaggedError","message","retryAfter","undefined","reason","format","SinkRejected","keys","length","join","SinkHalted","FeedWriteFailed","FeedUnreadable","LeaseHeld","feed","sink","LeaseLost","CursorSuperseded","FeedSyncStalled","restarts"],"sources":["../../src/errors.ts"],"sourcesContent":[null],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAASA,IAAI,EAAEC,QAAQ,QAAQ,QAAQ;AAIvC;AACA,OAAM,MAAOC,aAAc,sBAAQF,IAAI,CAACG,WAAW,CAAC,eAAe,CAIjE;EACA,IAAaC,OAAOA,CAAA;IAClB,OAAO,IAAI,CAACC,UAAU,KAAKC,SAAS,GAChC,IAAI,CAACC,MAAM,GACX,GAAG,IAAI,CAACA,MAAM,iBAAiBN,QAAQ,CAACO,MAAM,CAAC,IAAI,CAACH,UAAU,CAAC,GAAG;EACxE;;AAGF;AACA,OAAM,MAAOI,YAAa,sBAAQT,IAAI,CAACG,WAAW,CAAC,cAAc,CAI/D;EACA,IAAaC,OAAOA,CAAA;IAClB,OAAO,oBAAoB,IAAI,CAACM,IAAI,CAACC,MAAM,eAAe,IAAI,CAACD,IAAI,CAACE,IAAI,CAAC,IAAI,CAAC,EAAE;EAClF;;AAGF;AACA,OAAM,MAAOC,UAAW,sBAAQb,IAAI,CAACG,WAAW,CAAC,YAAY,CAE3D;EACA,IAAaC,OAAOA,CAAA;IAClB,OAAO,IAAI,CAACG,MAAM;EACpB;;AAGF;AACA,OAAM,MAAOO,eAAgB,sBAAQd,IAAI,CAACG,WAAW,CAAC,iBAAiB,CAGrE;EACA,IAAaC,OAAOA,CAAA;IAClB,OAAO,IAAI,CAACG,MAAM;EACpB;;AAGF;AACA,OAAM,MAAOQ,cAAe,sBAAQf,IAAI,CAACG,WAAW,CAAC,gBAAgB,CAGnE;EACA,IAAaC,OAAOA,CAAA;IAClB,OAAO,IAAI,CAACG,MAAM;EACpB;;AAGF;AACA,OAAM,MAAOS,SAAU,sBAAQhB,IAAI,CAACG,WAAW,CAAC,WAAW,CAAa;EACtE,IAAaC,OAAOA,CAAA;IAClB,OAAO,4CAA4C,IAAI,CAACa,IAAI,eAAe,IAAI,CAACC,IAAI,GAAG;EACzF;;AAGF;AACA,OAAM,MAAOC,SAAU,sBAAQnB,IAAI,CAACG,WAAW,CAAC,WAAW,CAAa;EACtE,IAAaC,OAAOA,CAAA;IAClB,OAAO,uBAAuB,IAAI,CAACa,IAAI,eAAe,IAAI,CAACC,IAAI,YAAY;EAC7E;;AAGF;;;;AAIA,OAAM,MAAOE,gBAAiB,sBAAQpB,IAAI,CAACG,WAAW,CACpD,kBAAkB,CACP;EACX,IAAaC,OAAOA,CAAA;IAClB,OAAO,wBAAwB,IAAI,CAACa,IAAI,eAAe,IAAI,CAACC,IAAI,kBAAkB;EACpF;;AAGF;;;;AAIA,OAAM,MAAOG,eAAgB,sBAAQrB,IAAI,CAACG,WAAW,CAAC,iBAAiB,CAKrE;EACA,IAAaC,OAAOA,CAAA;IAClB,OAAO,qBAAqB,IAAI,CAACa,IAAI,cAAc,IAAI,CAACC,IAAI,yBAAyB,IAAI,CAACI,QAAQ,uBAAuB;EAC3H","ignoreList":[]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The change feed's branded identifiers.
|
|
3
|
+
*
|
|
4
|
+
* Every one of them is a branded `Schema`, as every domain identifier in this
|
|
5
|
+
* repository is, so a raw string or `bigint` cannot stand in for one by accident.
|
|
6
|
+
* Two of them are worth a word, because they look like things the read side
|
|
7
|
+
* already has and are deliberately not those things.
|
|
8
|
+
*
|
|
9
|
+
* - `FeedSeq` is NOT a `Position`. A `Position` is a place in the global event log,
|
|
10
|
+
* and one event may fan out into many feed rows (a teacher renamed, fifty courses
|
|
11
|
+
* touched). Give those fifty rows the event's position as their cursor and they
|
|
12
|
+
* all share one value, so a page boundary that falls among them loses the rest of
|
|
13
|
+
* them: the next page asks for rows strictly after a value the lost ones also
|
|
14
|
+
* carry. A feed-local sequence gives every row a cursor value of its own. The
|
|
15
|
+
* event position is still kept on each row, as `eventPosition`, for the one
|
|
16
|
+
* question it answers — "is the sink at least as fresh as P?" — and is never a
|
|
17
|
+
* cursor.
|
|
18
|
+
* - `Generation` is NOT a counter a caller reads meaning into. It is compared for
|
|
19
|
+
* EQUALITY only: a sink whose cursor was saved at a generation other than the
|
|
20
|
+
* feed's current one resyncs. That is what lets the in-memory feed draw a fresh
|
|
21
|
+
* generation from `Random` on every boot while a durable feed counts upwards, and
|
|
22
|
+
* both satisfy the one rule the driver needs.
|
|
23
|
+
*/
|
|
24
|
+
import { Schema } from 'effect';
|
|
25
|
+
/**
|
|
26
|
+
* The name of one change feed. A feed is bound to ONE materialisation, because a
|
|
27
|
+
* feed sequence taken inside a commit is ascending in commit order only while one
|
|
28
|
+
* transaction at a time writes that feed — which the read side gives per
|
|
29
|
+
* materialisation and no wider.
|
|
30
|
+
*/
|
|
31
|
+
export const FeedName = /*#__PURE__*/Schema.String.pipe(/*#__PURE__*/Schema.check(/*#__PURE__*/Schema.isNonEmpty()), /*#__PURE__*/Schema.brand('FeedName'));
|
|
32
|
+
/** A record's key within a feed, and the sink's object id for that record. */
|
|
33
|
+
export const FeedKey = /*#__PURE__*/Schema.String.pipe(/*#__PURE__*/Schema.check(/*#__PURE__*/Schema.isNonEmpty()), /*#__PURE__*/Schema.brand('FeedKey'));
|
|
34
|
+
/**
|
|
35
|
+
* The feed-local cursor: unique, and ascending in commit order. Compared, never
|
|
36
|
+
* arithmetic'd, and never assumed contiguous — a sequence shared by several feeds
|
|
37
|
+
* leaves gaps in each, and a coalesced row gives up its old value when it moves.
|
|
38
|
+
*/
|
|
39
|
+
export const FeedSeq = /*#__PURE__*/Schema.BigInt.pipe(/*#__PURE__*/Schema.check(/*#__PURE__*/Schema.isGreaterThanOrEqualToBigInt(0n)), /*#__PURE__*/Schema.brand('FeedSeq'));
|
|
40
|
+
/** The sentinel feed sequence meaning "before the first row". */
|
|
41
|
+
export const FEED_ORIGIN = /*#__PURE__*/FeedSeq.make(0n);
|
|
42
|
+
/**
|
|
43
|
+
* The feed's generation: it moves on every reset of the materialisation the feed
|
|
44
|
+
* is bound to, and on every boot of an ephemeral feed. Compared for equality only;
|
|
45
|
+
* see the module doc.
|
|
46
|
+
*/
|
|
47
|
+
export const Generation = /*#__PURE__*/Schema.BigInt.pipe(/*#__PURE__*/Schema.check(/*#__PURE__*/Schema.isGreaterThanOrEqualToBigInt(0n)), /*#__PURE__*/Schema.brand('Generation'));
|
|
48
|
+
/** The name of one sink. A feed keeps one cursor per sink. */
|
|
49
|
+
export const SinkName = /*#__PURE__*/Schema.String.pipe(/*#__PURE__*/Schema.check(/*#__PURE__*/Schema.isNonEmpty()), /*#__PURE__*/Schema.brand('SinkName'));
|
|
50
|
+
/**
|
|
51
|
+
* Who holds a sync worker's lease. Issued by the `FeedSyncStore` when the lease is
|
|
52
|
+
* taken, and checked again by every cursor compare-and-set, so a worker that has
|
|
53
|
+
* lost its lease cannot move the cursor even if it is still running.
|
|
54
|
+
*/
|
|
55
|
+
export const LeaseHolder = /*#__PURE__*/Schema.String.pipe(/*#__PURE__*/Schema.check(/*#__PURE__*/Schema.isNonEmpty()), /*#__PURE__*/Schema.brand('LeaseHolder'));
|
|
56
|
+
//# sourceMappingURL=identifiers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identifiers.js","names":["Schema","FeedName","String","pipe","check","isNonEmpty","brand","FeedKey","FeedSeq","BigInt","isGreaterThanOrEqualToBigInt","FEED_ORIGIN","make","Generation","SinkName","LeaseHolder"],"sources":["../../src/identifiers.ts"],"sourcesContent":[null],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAASA,MAAM,QAAQ,QAAQ;AAE/B;;;;;;AAMA,OAAO,MAAMC,QAAQ,gBAAGD,MAAM,CAACE,MAAM,CAACC,IAAI,cACxCH,MAAM,CAACI,KAAK,cAACJ,MAAM,CAACK,UAAU,EAAE,CAAC,eACjCL,MAAM,CAACM,KAAK,CAAC,UAAU,CAAC,CACzB;AAGD;AACA,OAAO,MAAMC,OAAO,gBAAGP,MAAM,CAACE,MAAM,CAACC,IAAI,cACvCH,MAAM,CAACI,KAAK,cAACJ,MAAM,CAACK,UAAU,EAAE,CAAC,eACjCL,MAAM,CAACM,KAAK,CAAC,SAAS,CAAC,CACxB;AAGD;;;;;AAKA,OAAO,MAAME,OAAO,gBAAGR,MAAM,CAACS,MAAM,CAACN,IAAI,cACvCH,MAAM,CAACI,KAAK,cAACJ,MAAM,CAACU,4BAA4B,CAAC,EAAE,CAAC,CAAC,eACrDV,MAAM,CAACM,KAAK,CAAC,SAAS,CAAC,CACxB;AAGD;AACA,OAAO,MAAMK,WAAW,gBAAYH,OAAO,CAACI,IAAI,CAAC,EAAE,CAAC;AAEpD;;;;;AAKA,OAAO,MAAMC,UAAU,gBAAGb,MAAM,CAACS,MAAM,CAACN,IAAI,cAC1CH,MAAM,CAACI,KAAK,cAACJ,MAAM,CAACU,4BAA4B,CAAC,EAAE,CAAC,CAAC,eACrDV,MAAM,CAACM,KAAK,CAAC,YAAY,CAAC,CAC3B;AAGD;AACA,OAAO,MAAMQ,QAAQ,gBAAGd,MAAM,CAACE,MAAM,CAACC,IAAI,cACxCH,MAAM,CAACI,KAAK,cAACJ,MAAM,CAACK,UAAU,EAAE,CAAC,eACjCL,MAAM,CAACM,KAAK,CAAC,UAAU,CAAC,CACzB;AAGD;;;;;AAKA,OAAO,MAAMS,WAAW,gBAAGf,MAAM,CAACE,MAAM,CAACC,IAAI,cAC3CH,MAAM,CAACI,KAAK,cAACJ,MAAM,CAACK,UAAU,EAAE,CAAC,eACjCL,MAAM,CAACM,KAAK,CAAC,aAAa,CAAC,CAC5B","ignoreList":[]}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { DateTime, Effect, Option, PubSub, Random, Ref, Schema, Stream } from 'effect';
|
|
2
|
+
import { CursorSuperseded, LeaseHeld } from "./errors.js";
|
|
3
|
+
import { sameCursor } from "./FeedSyncStore.js";
|
|
4
|
+
import { FEED_ORIGIN, FeedSeq, Generation, LeaseHolder } from "./identifiers.js";
|
|
5
|
+
const EMPTY_SINK = {
|
|
6
|
+
cursor: /*#__PURE__*/Option.none(),
|
|
7
|
+
lastAdvance: /*#__PURE__*/Option.none(),
|
|
8
|
+
holder: /*#__PURE__*/Option.none(),
|
|
9
|
+
parked: /*#__PURE__*/new Map()
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* A JSON value as a string with every object's keys sorted, so two encodings that
|
|
13
|
+
* differ only in key order compare equal.
|
|
14
|
+
*/
|
|
15
|
+
const canonicalJson = value => JSON.stringify(value, (_key, held) => held !== null && typeof held === 'object' && !Array.isArray(held) ? Object.fromEntries(Object.entries(held).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) : held);
|
|
16
|
+
const internals = /*#__PURE__*/new WeakMap();
|
|
17
|
+
/** Build an in-memory feed at a fresh generation; see the module doc. */
|
|
18
|
+
export const makeInMemoryFeed = options => Effect.gen(function* () {
|
|
19
|
+
const feed = options.feed;
|
|
20
|
+
const initial = yield* Random.nextIntBetween(1, Number.MAX_SAFE_INTEGER);
|
|
21
|
+
const state = yield* Ref.make({
|
|
22
|
+
generation: Generation.make(BigInt(initial)),
|
|
23
|
+
lastSeq: 0n,
|
|
24
|
+
rows: new Map(),
|
|
25
|
+
sinks: new Map(),
|
|
26
|
+
boundTo: Option.none()
|
|
27
|
+
});
|
|
28
|
+
const wake = yield* PubSub.sliding(1);
|
|
29
|
+
const encode = Schema.encodeEffect(options.doc);
|
|
30
|
+
const requireFeed = name => name === feed ? Effect.void : Effect.die(new Error(`in-memory feed '${feed}' was asked about feed '${name}', which it does not hold`));
|
|
31
|
+
const sinkOf = (s, sink) => s.sinks.get(sink) ?? EMPTY_SINK;
|
|
32
|
+
const withSink = (s, sink, f) => ({
|
|
33
|
+
...s,
|
|
34
|
+
sinks: new Map(s.sinks).set(sink, f(sinkOf(s, sink)))
|
|
35
|
+
});
|
|
36
|
+
// --- the write side ---------------------------------------------------
|
|
37
|
+
const write = (at, body) => Effect.gen(function* () {
|
|
38
|
+
const buffer = new Map();
|
|
39
|
+
const set = (key, doc) => Effect.sync(() => {
|
|
40
|
+
buffer.delete(key);
|
|
41
|
+
buffer.set(key, doc);
|
|
42
|
+
});
|
|
43
|
+
const result = yield* body({
|
|
44
|
+
upsert: (key, doc) => set(key, Option.some(doc)),
|
|
45
|
+
remove: key => set(key, Option.none())
|
|
46
|
+
});
|
|
47
|
+
const staged = [];
|
|
48
|
+
for (const [key, doc] of buffer) {
|
|
49
|
+
const encoded = Option.isSome(doc) ? canonicalJson(yield* Effect.orDie(encode(doc.value))) : undefined;
|
|
50
|
+
staged.push({
|
|
51
|
+
key,
|
|
52
|
+
doc,
|
|
53
|
+
encoded
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const wrote = yield* Ref.modify(state, s => {
|
|
57
|
+
const rows = new Map(s.rows);
|
|
58
|
+
let lastSeq = s.lastSeq;
|
|
59
|
+
for (const {
|
|
60
|
+
key,
|
|
61
|
+
doc,
|
|
62
|
+
encoded
|
|
63
|
+
} of staged) {
|
|
64
|
+
const stored = rows.get(key);
|
|
65
|
+
const unchanged = encoded === undefined ? stored === undefined || stored.encoded === undefined : stored !== undefined && stored.encoded === encoded;
|
|
66
|
+
if (unchanged) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
lastSeq += 1n;
|
|
70
|
+
rows.set(key, {
|
|
71
|
+
row: {
|
|
72
|
+
key,
|
|
73
|
+
doc,
|
|
74
|
+
seq: FeedSeq.make(lastSeq),
|
|
75
|
+
eventPosition: at
|
|
76
|
+
},
|
|
77
|
+
encoded
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return lastSeq === s.lastSeq ? [false, s] : [true, {
|
|
81
|
+
...s,
|
|
82
|
+
rows,
|
|
83
|
+
lastSeq
|
|
84
|
+
}];
|
|
85
|
+
});
|
|
86
|
+
if (wrote) {
|
|
87
|
+
yield* PubSub.publish(wake, undefined);
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
});
|
|
91
|
+
// --- the read side ----------------------------------------------------
|
|
92
|
+
const changesAfter = (after, limit) => Effect.map(Ref.get(state), s => Array.from(s.rows.values(), stored => stored.row).filter(row => row.seq > after).sort((left, right) => left.seq < right.seq ? -1 : left.seq > right.seq ? 1 : 0).slice(0, limit));
|
|
93
|
+
const source = {
|
|
94
|
+
feed,
|
|
95
|
+
generation: Effect.map(Ref.get(state), s => s.generation),
|
|
96
|
+
changesAfter,
|
|
97
|
+
listen: Stream.fromPubSub(wake)
|
|
98
|
+
};
|
|
99
|
+
// --- the sync store ---------------------------------------------------
|
|
100
|
+
// Take-if-free and record the holder in ONE `Ref.modify`, so the check and the
|
|
101
|
+
// take cannot be split by a rival, and the lease has one home that `casCursor`,
|
|
102
|
+
// `describe` and `removeSink` read. `acquireRelease` runs the take
|
|
103
|
+
// uninterruptibly, so a lease taken is always paired with its release.
|
|
104
|
+
const acquireLease = id => Effect.gen(function* () {
|
|
105
|
+
yield* requireFeed(id.feed);
|
|
106
|
+
const holder = LeaseHolder.make(`in-memory:${yield* Random.nextIntBetween(1, Number.MAX_SAFE_INTEGER)}`);
|
|
107
|
+
const take = Effect.flatMap(Ref.modify(state, s => Option.isSome(sinkOf(s, id.sink).holder) ? [false, s] : [true, withSink(s, id.sink, sink => ({
|
|
108
|
+
...sink,
|
|
109
|
+
holder: Option.some(holder)
|
|
110
|
+
}))]), taken => taken ? Effect.void : Effect.fail(new LeaseHeld(id)));
|
|
111
|
+
// Cleared only while it is still this holder's, so a release can never
|
|
112
|
+
// take the lease from anyone else.
|
|
113
|
+
const release = Ref.update(state, s => {
|
|
114
|
+
const current = s.sinks.get(id.sink);
|
|
115
|
+
return current !== undefined && Option.isSome(current.holder) && current.holder.value === holder ? withSink(s, id.sink, sink => ({
|
|
116
|
+
...sink,
|
|
117
|
+
holder: Option.none()
|
|
118
|
+
})) : s;
|
|
119
|
+
});
|
|
120
|
+
yield* Effect.acquireRelease(take, () => release);
|
|
121
|
+
const lease = {
|
|
122
|
+
holder,
|
|
123
|
+
ensureHeld: Effect.void
|
|
124
|
+
};
|
|
125
|
+
return lease;
|
|
126
|
+
});
|
|
127
|
+
const casCursor = args => Effect.flatMap(Effect.zip(requireFeed(args.feed), DateTime.now), ([, now]) => Effect.flatMap(Ref.modify(state, s => {
|
|
128
|
+
const current = sinkOf(s, args.sink);
|
|
129
|
+
const fenced = Option.isSome(current.holder) && current.holder.value === args.holder;
|
|
130
|
+
if (!fenced || !sameCursor(current.cursor, args.expected)) {
|
|
131
|
+
return [false, s];
|
|
132
|
+
}
|
|
133
|
+
return [true, withSink(s, args.sink, sink => ({
|
|
134
|
+
...sink,
|
|
135
|
+
cursor: Option.some(args.next),
|
|
136
|
+
lastAdvance: Option.some(now),
|
|
137
|
+
parked: moveParked(sink.parked, args.parked)
|
|
138
|
+
}))];
|
|
139
|
+
}), landed => landed ? Effect.void : Effect.fail(new CursorSuperseded({
|
|
140
|
+
feed: args.feed,
|
|
141
|
+
sink: args.sink
|
|
142
|
+
}))));
|
|
143
|
+
const retryParked = args => Effect.gen(function* () {
|
|
144
|
+
yield* requireFeed(args.feed);
|
|
145
|
+
const retried = yield* Ref.modify(state, s => {
|
|
146
|
+
const sink = sinkOf(s, args.sink);
|
|
147
|
+
if (!sink.parked.has(args.key)) {
|
|
148
|
+
return [false, s];
|
|
149
|
+
}
|
|
150
|
+
const parked = new Map(sink.parked);
|
|
151
|
+
parked.delete(args.key);
|
|
152
|
+
const unparked = withSink(s, args.sink, current => ({
|
|
153
|
+
...current,
|
|
154
|
+
parked
|
|
155
|
+
}));
|
|
156
|
+
const stored = s.rows.get(args.key);
|
|
157
|
+
if (stored === undefined) {
|
|
158
|
+
return [true, unparked];
|
|
159
|
+
}
|
|
160
|
+
const lastSeq = s.lastSeq + 1n;
|
|
161
|
+
return [true, {
|
|
162
|
+
...unparked,
|
|
163
|
+
lastSeq,
|
|
164
|
+
rows: new Map(s.rows).set(args.key, {
|
|
165
|
+
...stored,
|
|
166
|
+
row: {
|
|
167
|
+
...stored.row,
|
|
168
|
+
seq: FeedSeq.make(lastSeq)
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
}];
|
|
172
|
+
});
|
|
173
|
+
if (retried) {
|
|
174
|
+
yield* PubSub.publish(wake, undefined);
|
|
175
|
+
}
|
|
176
|
+
return retried;
|
|
177
|
+
});
|
|
178
|
+
const collectTombstones = name => Effect.flatMap(requireFeed(name), () => Ref.modify(state, s => {
|
|
179
|
+
// Every sink with a lease or a cursor counts, whatever its cursor's
|
|
180
|
+
// generation, and one with no cursor blocks collection outright: the
|
|
181
|
+
// port's doc says why a resyncing sink still needs the tombstones it has
|
|
182
|
+
// not read, and why an older generation needs no arm of its own.
|
|
183
|
+
let lowest;
|
|
184
|
+
for (const sink of s.sinks.values()) {
|
|
185
|
+
if (Option.isNone(sink.cursor)) {
|
|
186
|
+
return [0, s];
|
|
187
|
+
}
|
|
188
|
+
if (lowest === undefined || sink.cursor.value.seq < lowest) {
|
|
189
|
+
lowest = sink.cursor.value.seq;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (lowest === undefined) {
|
|
193
|
+
return [0, s];
|
|
194
|
+
}
|
|
195
|
+
const rows = new Map(s.rows);
|
|
196
|
+
let collected = 0;
|
|
197
|
+
for (const [key, stored] of s.rows) {
|
|
198
|
+
if (stored.encoded === undefined && stored.row.seq <= lowest) {
|
|
199
|
+
rows.delete(key);
|
|
200
|
+
collected += 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return collected === 0 ? [0, s] : [collected, {
|
|
204
|
+
...s,
|
|
205
|
+
rows
|
|
206
|
+
}];
|
|
207
|
+
}));
|
|
208
|
+
const describe = name => Effect.flatMap(requireFeed(name), () => Effect.map(Ref.get(state), s => {
|
|
209
|
+
let head = FEED_ORIGIN;
|
|
210
|
+
for (const stored of s.rows.values()) {
|
|
211
|
+
if (stored.row.seq > head) {
|
|
212
|
+
head = stored.row.seq;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const sinks = Array.from(s.sinks.entries(), ([sink, state]) => ({
|
|
216
|
+
sink,
|
|
217
|
+
cursor: state.cursor,
|
|
218
|
+
lastAdvance: state.lastAdvance,
|
|
219
|
+
lease: Option.map(state.holder, holder => ({
|
|
220
|
+
holder,
|
|
221
|
+
expiresAt: Option.none()
|
|
222
|
+
})),
|
|
223
|
+
parked: Array.from(state.parked.values()),
|
|
224
|
+
pending: Array.from(s.rows.values()).filter(stored => Option.match(state.cursor, {
|
|
225
|
+
onNone: () => stored.encoded !== undefined,
|
|
226
|
+
onSome: cursor => stored.row.seq > cursor.seq
|
|
227
|
+
})).length
|
|
228
|
+
}));
|
|
229
|
+
return {
|
|
230
|
+
head: FeedSeq.make(head),
|
|
231
|
+
sinks
|
|
232
|
+
};
|
|
233
|
+
}));
|
|
234
|
+
const removeSink = id => Effect.flatMap(requireFeed(id.feed), () => Effect.flatten(Ref.modify(state, s => {
|
|
235
|
+
const current = s.sinks.get(id.sink);
|
|
236
|
+
if (current === undefined) {
|
|
237
|
+
return [Effect.succeed(false), s];
|
|
238
|
+
}
|
|
239
|
+
if (Option.isSome(current.holder)) {
|
|
240
|
+
return [Effect.fail(new LeaseHeld(id)), s];
|
|
241
|
+
}
|
|
242
|
+
const sinks = new Map(s.sinks);
|
|
243
|
+
sinks.delete(id.sink);
|
|
244
|
+
return [Effect.succeed(true), {
|
|
245
|
+
...s,
|
|
246
|
+
sinks
|
|
247
|
+
}];
|
|
248
|
+
})));
|
|
249
|
+
const syncStore = {
|
|
250
|
+
acquireLease,
|
|
251
|
+
readCursor: id => Effect.flatMap(requireFeed(id.feed), () => Effect.map(Ref.get(state), s => sinkOf(s, id.sink).cursor)),
|
|
252
|
+
casCursor,
|
|
253
|
+
readParked: id => Effect.flatMap(requireFeed(id.feed), () => Effect.map(Ref.get(state), s => Array.from(sinkOf(s, id.sink).parked.values()))),
|
|
254
|
+
retryParked,
|
|
255
|
+
collectTombstones,
|
|
256
|
+
removeSink,
|
|
257
|
+
describe
|
|
258
|
+
};
|
|
259
|
+
const result = {
|
|
260
|
+
feed,
|
|
261
|
+
write,
|
|
262
|
+
source,
|
|
263
|
+
syncStore
|
|
264
|
+
};
|
|
265
|
+
internals.set(result, {
|
|
266
|
+
bind: key => Effect.flatMap(Ref.modify(state, s => Option.isNone(s.boundTo) ? [Option.none(), {
|
|
267
|
+
...s,
|
|
268
|
+
boundTo: Option.some(key)
|
|
269
|
+
}] : [s.boundTo, s]), already => Option.isSome(already) && !sameKey(already.value, key) ? Effect.die(new Error(`withInMemoryFeed: feed '${feed}' is already bound to projection '${already.value.projection}' (partition '${already.value.partition}'), and one feed serves one materialisation`)) : Effect.void),
|
|
270
|
+
reset: Effect.andThen(Ref.update(state, s => ({
|
|
271
|
+
...s,
|
|
272
|
+
rows: new Map(),
|
|
273
|
+
generation: Generation.make(s.generation + 1n)
|
|
274
|
+
})), PubSub.publish(wake, undefined))
|
|
275
|
+
});
|
|
276
|
+
return result;
|
|
277
|
+
});
|
|
278
|
+
const sameKey = (left, right) => left.projection === right.projection && left.partition === right.partition;
|
|
279
|
+
const moveParked = (parked, change) => {
|
|
280
|
+
const next = change._tag === 'Replace' ? new Map() : new Map(parked);
|
|
281
|
+
if (change._tag === 'Amend') {
|
|
282
|
+
for (const key of change.unpark) {
|
|
283
|
+
next.delete(key);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
for (const record of change.park) {
|
|
287
|
+
next.set(record.key, record);
|
|
288
|
+
}
|
|
289
|
+
return next;
|
|
290
|
+
};
|
|
291
|
+
/**
|
|
292
|
+
* `store`, with `resetCheckpoint` for `key` replaced by one that also empties
|
|
293
|
+
* `feed` and moves its generation on, so every sink resyncs once the read model is
|
|
294
|
+
* replayed. Every other key passes through untouched, and `readCheckpoint` and
|
|
295
|
+
* `commit` are the store's own, so the result works through `forKey` exactly as
|
|
296
|
+
* the store does.
|
|
297
|
+
*
|
|
298
|
+
* Binding is checked here, at construction: a feed serves ONE materialisation,
|
|
299
|
+
* because one writer is what keeps its sequence ascending in commit order, so a
|
|
300
|
+
* feed already bound to another checkpoint key is refused, as is a durable `store`
|
|
301
|
+
* (see the module doc). Both refusals are defects.
|
|
302
|
+
*/
|
|
303
|
+
export const withInMemoryFeed = (store, feed, key) => Effect.gen(function* () {
|
|
304
|
+
const inner = internals.get(feed);
|
|
305
|
+
if (inner === undefined) {
|
|
306
|
+
return yield* Effect.die(new Error(`withInMemoryFeed: feed '${feed.feed}' was not made by makeInMemoryFeed`));
|
|
307
|
+
}
|
|
308
|
+
if (store.durability === 'durable') {
|
|
309
|
+
return yield* Effect.die(new Error(`withInMemoryFeed: feed '${feed.feed}' is ephemeral and the store is durable; the view would outlive the feed`));
|
|
310
|
+
}
|
|
311
|
+
yield* inner.bind(key);
|
|
312
|
+
const decorated = {
|
|
313
|
+
durability: store.durability,
|
|
314
|
+
readCheckpoint: store.readCheckpoint,
|
|
315
|
+
commit: store.commit,
|
|
316
|
+
resetCheckpoint: reset => sameKey(reset, key) ? Effect.andThen(store.resetCheckpoint(reset), Effect.uninterruptible(inner.reset)) : store.resetCheckpoint(reset)
|
|
317
|
+
};
|
|
318
|
+
return decorated;
|
|
319
|
+
});
|
|
320
|
+
//# sourceMappingURL=inMemoryFeed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inMemoryFeed.js","names":["DateTime","Effect","Option","PubSub","Random","Ref","Schema","Stream","CursorSuperseded","LeaseHeld","sameCursor","FEED_ORIGIN","FeedSeq","Generation","LeaseHolder","EMPTY_SINK","cursor","none","lastAdvance","holder","parked","Map","canonicalJson","value","JSON","stringify","_key","held","Array","isArray","Object","fromEntries","entries","sort","left","right","internals","WeakMap","makeInMemoryFeed","options","gen","feed","initial","nextIntBetween","Number","MAX_SAFE_INTEGER","state","make","generation","BigInt","lastSeq","rows","sinks","boundTo","wake","sliding","encode","encodeEffect","doc","requireFeed","name","void","die","Error","sinkOf","s","sink","get","withSink","f","set","write","at","body","buffer","key","sync","delete","result","upsert","some","remove","staged","encoded","isSome","orDie","undefined","push","wrote","modify","stored","unchanged","row","seq","eventPosition","publish","changesAfter","after","limit","map","from","values","filter","slice","source","listen","fromPubSub","acquireLease","id","take","flatMap","taken","fail","release","update","current","acquireRelease","lease","ensureHeld","casCursor","args","zip","now","fenced","expected","next","moveParked","landed","retryParked","retried","has","unparked","collectTombstones","lowest","isNone","collected","describe","head","expiresAt","pending","match","onNone","onSome","length","removeSink","flatten","succeed","syncStore","readCursor","readParked","bind","already","sameKey","projection","partition","reset","andThen","change","_tag","unpark","record","park","withInMemoryFeed","store","inner","durability","decorated","readCheckpoint","commit","resetCheckpoint","uninterruptible"],"sources":["../../src/inMemoryFeed.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
|