@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.
Files changed (132) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +109 -2
  3. package/dist/cjs/Feed.js +6 -0
  4. package/dist/cjs/Feed.js.map +1 -0
  5. package/dist/cjs/FeedSink.js +54 -0
  6. package/dist/cjs/FeedSink.js.map +1 -0
  7. package/dist/cjs/FeedSyncStore.js +10 -0
  8. package/dist/cjs/FeedSyncStore.js.map +1 -0
  9. package/dist/cjs/deliver.js +86 -0
  10. package/dist/cjs/deliver.js.map +1 -0
  11. package/dist/cjs/errors.js +114 -0
  12. package/dist/cjs/errors.js.map +1 -0
  13. package/dist/cjs/identifiers.js +63 -0
  14. package/dist/cjs/identifiers.js.map +1 -0
  15. package/dist/cjs/inMemoryFeed.js +328 -0
  16. package/dist/cjs/inMemoryFeed.js.map +1 -0
  17. package/dist/cjs/inMemorySink.js +142 -0
  18. package/dist/cjs/inMemorySink.js.map +1 -0
  19. package/dist/cjs/index.js +184 -0
  20. package/dist/cjs/index.js.map +1 -0
  21. package/dist/cjs/inspect.js +68 -0
  22. package/dist/cjs/inspect.js.map +1 -0
  23. package/dist/cjs/readiness.js +100 -0
  24. package/dist/cjs/readiness.js.map +1 -0
  25. package/dist/cjs/reconcile.js +164 -0
  26. package/dist/cjs/reconcile.js.map +1 -0
  27. package/dist/cjs/runFeedSync.js +603 -0
  28. package/dist/cjs/runFeedSync.js.map +1 -0
  29. package/dist/cjs/tuning.js +60 -0
  30. package/dist/cjs/tuning.js.map +1 -0
  31. package/dist/dts-cjs/Feed.d.ts +99 -0
  32. package/dist/dts-cjs/Feed.d.ts.map +1 -0
  33. package/dist/dts-cjs/FeedSink.d.ts +189 -0
  34. package/dist/dts-cjs/FeedSink.d.ts.map +1 -0
  35. package/dist/dts-cjs/FeedSyncStore.d.ts +281 -0
  36. package/dist/dts-cjs/FeedSyncStore.d.ts.map +1 -0
  37. package/dist/dts-cjs/deliver.d.ts +16 -0
  38. package/dist/dts-cjs/deliver.d.ts.map +1 -0
  39. package/dist/dts-cjs/errors.d.ts +131 -0
  40. package/dist/dts-cjs/errors.d.ts.map +1 -0
  41. package/dist/dts-cjs/identifiers.d.ts +67 -0
  42. package/dist/dts-cjs/identifiers.d.ts.map +1 -0
  43. package/dist/dts-cjs/inMemoryFeed.d.ts +32 -0
  44. package/dist/dts-cjs/inMemoryFeed.d.ts.map +1 -0
  45. package/dist/dts-cjs/inMemorySink.d.ts +61 -0
  46. package/dist/dts-cjs/inMemorySink.d.ts.map +1 -0
  47. package/dist/dts-cjs/index.d.ts +63 -0
  48. package/dist/dts-cjs/index.d.ts.map +1 -0
  49. package/dist/dts-cjs/inspect.d.ts +71 -0
  50. package/dist/dts-cjs/inspect.d.ts.map +1 -0
  51. package/dist/dts-cjs/package.json +3 -0
  52. package/dist/dts-cjs/readiness.d.ts +84 -0
  53. package/dist/dts-cjs/readiness.d.ts.map +1 -0
  54. package/dist/dts-cjs/reconcile.d.ts +68 -0
  55. package/dist/dts-cjs/reconcile.d.ts.map +1 -0
  56. package/dist/dts-cjs/runFeedSync.d.ts +220 -0
  57. package/dist/dts-cjs/runFeedSync.d.ts.map +1 -0
  58. package/dist/dts-cjs/tuning.d.ts +40 -0
  59. package/dist/dts-cjs/tuning.d.ts.map +1 -0
  60. package/dist/dts-esm/Feed.d.ts +99 -0
  61. package/dist/dts-esm/Feed.d.ts.map +1 -0
  62. package/dist/dts-esm/FeedSink.d.ts +189 -0
  63. package/dist/dts-esm/FeedSink.d.ts.map +1 -0
  64. package/dist/dts-esm/FeedSyncStore.d.ts +281 -0
  65. package/dist/dts-esm/FeedSyncStore.d.ts.map +1 -0
  66. package/dist/dts-esm/deliver.d.ts +16 -0
  67. package/dist/dts-esm/deliver.d.ts.map +1 -0
  68. package/dist/dts-esm/errors.d.ts +131 -0
  69. package/dist/dts-esm/errors.d.ts.map +1 -0
  70. package/dist/dts-esm/identifiers.d.ts +67 -0
  71. package/dist/dts-esm/identifiers.d.ts.map +1 -0
  72. package/dist/dts-esm/inMemoryFeed.d.ts +32 -0
  73. package/dist/dts-esm/inMemoryFeed.d.ts.map +1 -0
  74. package/dist/dts-esm/inMemorySink.d.ts +61 -0
  75. package/dist/dts-esm/inMemorySink.d.ts.map +1 -0
  76. package/dist/dts-esm/index.d.ts +63 -0
  77. package/dist/dts-esm/index.d.ts.map +1 -0
  78. package/dist/dts-esm/inspect.d.ts +71 -0
  79. package/dist/dts-esm/inspect.d.ts.map +1 -0
  80. package/dist/dts-esm/package.json +3 -0
  81. package/dist/dts-esm/readiness.d.ts +84 -0
  82. package/dist/dts-esm/readiness.d.ts.map +1 -0
  83. package/dist/dts-esm/reconcile.d.ts +68 -0
  84. package/dist/dts-esm/reconcile.d.ts.map +1 -0
  85. package/dist/dts-esm/runFeedSync.d.ts +220 -0
  86. package/dist/dts-esm/runFeedSync.d.ts.map +1 -0
  87. package/dist/dts-esm/tuning.d.ts +40 -0
  88. package/dist/dts-esm/tuning.d.ts.map +1 -0
  89. package/dist/esm/Feed.js +2 -0
  90. package/dist/esm/Feed.js.map +1 -0
  91. package/dist/esm/FeedSink.js +46 -0
  92. package/dist/esm/FeedSink.js.map +1 -0
  93. package/dist/esm/FeedSyncStore.js +3 -0
  94. package/dist/esm/FeedSyncStore.js.map +1 -0
  95. package/dist/esm/deliver.js +78 -0
  96. package/dist/esm/deliver.js.map +1 -0
  97. package/dist/esm/errors.js +98 -0
  98. package/dist/esm/errors.js.map +1 -0
  99. package/dist/esm/identifiers.js +56 -0
  100. package/dist/esm/identifiers.js.map +1 -0
  101. package/dist/esm/inMemoryFeed.js +320 -0
  102. package/dist/esm/inMemoryFeed.js.map +1 -0
  103. package/dist/esm/inMemorySink.js +134 -0
  104. package/dist/esm/inMemorySink.js.map +1 -0
  105. package/dist/esm/index.js +62 -0
  106. package/dist/esm/index.js.map +1 -0
  107. package/dist/esm/inspect.js +58 -0
  108. package/dist/esm/inspect.js.map +1 -0
  109. package/dist/esm/package.json +4 -0
  110. package/dist/esm/readiness.js +92 -0
  111. package/dist/esm/readiness.js.map +1 -0
  112. package/dist/esm/reconcile.js +153 -0
  113. package/dist/esm/reconcile.js.map +1 -0
  114. package/dist/esm/runFeedSync.js +595 -0
  115. package/dist/esm/runFeedSync.js.map +1 -0
  116. package/dist/esm/tuning.js +51 -0
  117. package/dist/esm/tuning.js.map +1 -0
  118. package/package.json +34 -3
  119. package/src/Feed.ts +111 -0
  120. package/src/FeedSink.ts +130 -0
  121. package/src/FeedSyncStore.ts +326 -0
  122. package/src/deliver.ts +108 -0
  123. package/src/errors.ts +134 -0
  124. package/src/identifiers.ts +92 -0
  125. package/src/inMemoryFeed.ts +582 -0
  126. package/src/inMemorySink.ts +215 -0
  127. package/src/index.ts +136 -0
  128. package/src/inspect.ts +114 -0
  129. package/src/readiness.ts +137 -0
  130. package/src/reconcile.ts +224 -0
  131. package/src/runFeedSync.ts +882 -0
  132. package/src/tuning.ts +65 -0
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "CursorSuperseded", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _errors.CursorSuperseded;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "FEED_ORIGIN", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _identifiers.FEED_ORIGIN;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "FeedChange", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _FeedSink.FeedChange;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "FeedKey", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _identifiers.FeedKey;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "FeedName", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _identifiers.FeedName;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "FeedSeq", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _identifiers.FeedSeq;
40
+ }
41
+ });
42
+ Object.defineProperty(exports, "FeedSyncStalled", {
43
+ enumerable: true,
44
+ get: function () {
45
+ return _errors.FeedSyncStalled;
46
+ }
47
+ });
48
+ Object.defineProperty(exports, "FeedUnreadable", {
49
+ enumerable: true,
50
+ get: function () {
51
+ return _errors.FeedUnreadable;
52
+ }
53
+ });
54
+ Object.defineProperty(exports, "FeedWriteFailed", {
55
+ enumerable: true,
56
+ get: function () {
57
+ return _errors.FeedWriteFailed;
58
+ }
59
+ });
60
+ Object.defineProperty(exports, "Generation", {
61
+ enumerable: true,
62
+ get: function () {
63
+ return _identifiers.Generation;
64
+ }
65
+ });
66
+ Object.defineProperty(exports, "LeaseHeld", {
67
+ enumerable: true,
68
+ get: function () {
69
+ return _errors.LeaseHeld;
70
+ }
71
+ });
72
+ Object.defineProperty(exports, "LeaseHolder", {
73
+ enumerable: true,
74
+ get: function () {
75
+ return _identifiers.LeaseHolder;
76
+ }
77
+ });
78
+ Object.defineProperty(exports, "LeaseLost", {
79
+ enumerable: true,
80
+ get: function () {
81
+ return _errors.LeaseLost;
82
+ }
83
+ });
84
+ Object.defineProperty(exports, "SinkHalted", {
85
+ enumerable: true,
86
+ get: function () {
87
+ return _errors.SinkHalted;
88
+ }
89
+ });
90
+ Object.defineProperty(exports, "SinkName", {
91
+ enumerable: true,
92
+ get: function () {
93
+ return _identifiers.SinkName;
94
+ }
95
+ });
96
+ Object.defineProperty(exports, "SinkRejected", {
97
+ enumerable: true,
98
+ get: function () {
99
+ return _errors.SinkRejected;
100
+ }
101
+ });
102
+ Object.defineProperty(exports, "SinkRetryable", {
103
+ enumerable: true,
104
+ get: function () {
105
+ return _errors.SinkRetryable;
106
+ }
107
+ });
108
+ Object.defineProperty(exports, "changeOf", {
109
+ enumerable: true,
110
+ get: function () {
111
+ return _FeedSink.changeOf;
112
+ }
113
+ });
114
+ Object.defineProperty(exports, "inspectFeed", {
115
+ enumerable: true,
116
+ get: function () {
117
+ return _inspect.inspectFeed;
118
+ }
119
+ });
120
+ Object.defineProperty(exports, "makeInMemoryFeed", {
121
+ enumerable: true,
122
+ get: function () {
123
+ return _inMemoryFeed.makeInMemoryFeed;
124
+ }
125
+ });
126
+ Object.defineProperty(exports, "makeInMemorySink", {
127
+ enumerable: true,
128
+ get: function () {
129
+ return _inMemorySink.makeInMemorySink;
130
+ }
131
+ });
132
+ Object.defineProperty(exports, "materialisationCaughtUp", {
133
+ enumerable: true,
134
+ get: function () {
135
+ return _readiness.materialisationCaughtUp;
136
+ }
137
+ });
138
+ Object.defineProperty(exports, "reconcileFeed", {
139
+ enumerable: true,
140
+ get: function () {
141
+ return _reconcile.reconcileFeed;
142
+ }
143
+ });
144
+ Object.defineProperty(exports, "removeSink", {
145
+ enumerable: true,
146
+ get: function () {
147
+ return _inspect.removeSink;
148
+ }
149
+ });
150
+ Object.defineProperty(exports, "retryParked", {
151
+ enumerable: true,
152
+ get: function () {
153
+ return _inspect.retryParked;
154
+ }
155
+ });
156
+ Object.defineProperty(exports, "runFeedSync", {
157
+ enumerable: true,
158
+ get: function () {
159
+ return _runFeedSync.runFeedSync;
160
+ }
161
+ });
162
+ Object.defineProperty(exports, "sameCursor", {
163
+ enumerable: true,
164
+ get: function () {
165
+ return _FeedSyncStore.sameCursor;
166
+ }
167
+ });
168
+ Object.defineProperty(exports, "withInMemoryFeed", {
169
+ enumerable: true,
170
+ get: function () {
171
+ return _inMemoryFeed.withInMemoryFeed;
172
+ }
173
+ });
174
+ var _errors = require("./errors.js");
175
+ var _FeedSink = require("./FeedSink.js");
176
+ var _FeedSyncStore = require("./FeedSyncStore.js");
177
+ var _identifiers = require("./identifiers.js");
178
+ var _inMemoryFeed = require("./inMemoryFeed.js");
179
+ var _inMemorySink = require("./inMemorySink.js");
180
+ var _inspect = require("./inspect.js");
181
+ var _readiness = require("./readiness.js");
182
+ var _reconcile = require("./reconcile.js");
183
+ var _runFeedSync = require("./runFeedSync.js");
184
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["_errors","require","_FeedSink","_FeedSyncStore","_identifiers","_inMemoryFeed","_inMemorySink","_inspect","_readiness","_reconcile","_runFeedSync"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,IAAAA,OAAA,GAAAC,OAAA;AAiBA,IAAAC,SAAA,GAAAD,OAAA;AASA,IAAAE,cAAA,GAAAF,OAAA;AAWA,IAAAG,YAAA,GAAAH,OAAA;AAUA,IAAAI,aAAA,GAAAJ,OAAA;AAMA,IAAAK,aAAA,GAAAL,OAAA;AAOA,IAAAM,QAAA,GAAAN,OAAA;AAOA,IAAAO,UAAA,GAAAP,OAAA;AAMA,IAAAQ,UAAA,GAAAR,OAAA;AAKA,IAAAS,YAAA,GAAAT,OAAA","ignoreList":[]}
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.retryParked = exports.removeSink = exports.inspectFeed = void 0;
7
+ var _effect = require("effect");
8
+ /**
9
+ * `inspectFeed`, `retryParked` and `removeSink`: an operator's view of a feed's
10
+ * sinks, and the two repairs an operator makes by hand.
11
+ *
12
+ * None of them needs the sink's client, because everything they read or change
13
+ * lives in the source database beside the feed: an operator can see a sink's lag,
14
+ * its lease, its parked records and when it last advanced, send a parked record
15
+ * again, and retire a sink, without the credentials of the index concerned. The
16
+ * same choice `@kairos-es/automation` made for its own `inspect`. There is no CLI
17
+ * or UI over any of them; they are effects an application wires into whatever it
18
+ * already has.
19
+ *
20
+ * `lastAdvance` and `removeSink` are a pair. Every sink registered on a feed holds
21
+ * its tombstone collection back, so a sink the application stopped running and
22
+ * never removed lets tombstones pile up for good; `lastAdvance` is how such a sink
23
+ * shows — still, while the head moves — and `removeSink` is what lets collection
24
+ * proceed without it. `FeedSyncStore.ts` owns both rules.
25
+ */
26
+
27
+ /** Every sink of `source`'s feed: its cursor, lag, lease and parked records. */
28
+ const inspectFeed = (source, store) => _effect.Effect.gen(function* () {
29
+ const generation = yield* source.generation;
30
+ const description = yield* store.describe(source.feed);
31
+ return {
32
+ feed: source.feed,
33
+ generation,
34
+ head: description.head,
35
+ sinks: description.sinks.map(state => ({
36
+ sink: state.sink,
37
+ generation: _effect.Option.map(state.cursor, c => c.generation),
38
+ cursor: state.cursor,
39
+ lastAdvance: state.lastAdvance,
40
+ resyncDue: _effect.Option.match(state.cursor, {
41
+ onNone: () => true,
42
+ onSome: c => c.generation !== generation
43
+ }),
44
+ lag: state.pending,
45
+ lease: state.lease,
46
+ parked: state.parked
47
+ }))
48
+ };
49
+ });
50
+ /**
51
+ * Send one parked record again: delete its park and re-stamp its row's `seq`, so
52
+ * the next pass of every sink on the feed sends it. Succeeds with whether the key
53
+ * was parked at all. Every sink on the feed receives the record again, not only
54
+ * `sink`; an idempotent sink absorbs that.
55
+ */
56
+ exports.inspectFeed = inspectFeed;
57
+ const retryParked = (store, args) => store.retryParked(args);
58
+ /**
59
+ * Retire `sink` from `feed`: delete its cursor, its lease record and its parked
60
+ * records, so it no longer holds the feed's tombstone collection back. Succeeds
61
+ * with whether the store held anything for it. Fails `LeaseHeld`, removing
62
+ * nothing, while a worker for the sink is running: stop the worker first. A worker
63
+ * started for the sink later registers it again from scratch, and resyncs.
64
+ */
65
+ exports.retryParked = retryParked;
66
+ const removeSink = (store, args) => store.removeSink(args);
67
+ exports.removeSink = removeSink;
68
+ //# sourceMappingURL=inspect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect.js","names":["_effect","require","inspectFeed","source","store","Effect","gen","generation","description","describe","feed","head","sinks","map","state","sink","Option","cursor","c","lastAdvance","resyncDue","match","onNone","onSome","lag","pending","lease","parked","exports","retryParked","args","removeSink"],"sources":["../../src/inspect.ts"],"sourcesContent":[null],"mappings":";;;;;;AAkBA,IAAAA,OAAA,GAAAC,OAAA;AAlBA;;;;;;;;;;;;;;;;;;;AA4DA;AACO,MAAMC,WAAW,GAAGA,CACzBC,MAAqB,EACrBC,KAAoB,KAEpBC,cAAM,CAACC,GAAG,CAAC,aAAS;EAClB,MAAMC,UAAU,GAAG,OAAOJ,MAAM,CAACI,UAAU;EAC3C,MAAMC,WAAW,GAAG,OAAOJ,KAAK,CAACK,QAAQ,CAACN,MAAM,CAACO,IAAI,CAAC;EACtD,OAAO;IACLA,IAAI,EAAEP,MAAM,CAACO,IAAI;IACjBH,UAAU;IACVI,IAAI,EAAEH,WAAW,CAACG,IAAI;IACtBC,KAAK,EAAEJ,WAAW,CAACI,KAAK,CAACC,GAAG,CAAEC,KAAK,KAAM;MACvCC,IAAI,EAAED,KAAK,CAACC,IAAI;MAChBR,UAAU,EAAES,cAAM,CAACH,GAAG,CAACC,KAAK,CAACG,MAAM,EAAGC,CAAC,IAAKA,CAAC,CAACX,UAAU,CAAC;MACzDU,MAAM,EAAEH,KAAK,CAACG,MAAM;MACpBE,WAAW,EAAEL,KAAK,CAACK,WAAW;MAC9BC,SAAS,EAAEJ,cAAM,CAACK,KAAK,CAACP,KAAK,CAACG,MAAM,EAAE;QACpCK,MAAM,EAAEA,CAAA,KAAM,IAAI;QAClBC,MAAM,EAAGL,CAAC,IAAKA,CAAC,CAACX,UAAU,KAAKA;OACjC,CAAC;MACFiB,GAAG,EAAEV,KAAK,CAACW,OAAO;MAClBC,KAAK,EAAEZ,KAAK,CAACY,KAAK;MAClBC,MAAM,EAAEb,KAAK,CAACa;KACf,CAAC;GACH;AACH,CAAC,CAAC;AAEJ;;;;;;AAAAC,OAAA,CAAA1B,WAAA,GAAAA,WAAA;AAMO,MAAM2B,WAAW,GAAGA,CACzBzB,KAAoB,EACpB0B,IAIC,KAC2C1B,KAAK,CAACyB,WAAW,CAACC,IAAI,CAAC;AAErE;;;;;;;AAAAF,OAAA,CAAAC,WAAA,GAAAA,WAAA;AAOO,MAAME,UAAU,GAAGA,CACxB3B,KAAoB,EACpB0B,IAA0D,KACF1B,KAAK,CAAC2B,UAAU,CAACD,IAAI,CAAC;AAAAF,OAAA,CAAAG,UAAA,GAAAA,UAAA","ignoreList":[]}
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.materialisationCaughtUp = void 0;
7
+ var _core = require("@kairos-es/core");
8
+ var _effect = require("effect");
9
+ var _errors = require("./errors.js");
10
+ /**
11
+ * `FeedReadiness` — when a rebuilt copy may go live — and
12
+ * `materialisationCaughtUp`, the readiness of the common case.
13
+ *
14
+ * ## Why a rebuild has anything to wait for
15
+ *
16
+ * A reset empties the feed and moves its generation on, and the materialisation
17
+ * then replays the log, writing the feed afresh as it goes. The sync worker sees
18
+ * the new generation within a poll interval and starts its resync at once, but the
19
+ * feed it pages through is only as complete as the replay is far along. Reaching
20
+ * the feed's last row says nothing about the replay: it is wherever the replay had
21
+ * got to. So a resync that publishes at the feed's end swaps a complete live copy
22
+ * for whatever fraction of the rebuild the replay had written, and search serves
23
+ * that partial copy until the tail fills the rest in.
24
+ *
25
+ * Only the materialisation knows how far its replay has got, and the feed's ports
26
+ * deliberately name no materialisation, so the driver is TOLD, through an optional
27
+ * readiness signal on `runFeedSync`, and holds the step that makes the rebuild
28
+ * final until the signal says yes. With no signal the resync publishes at the
29
+ * feed's end, as it always did, and a rebuild can serve a partial copy for as long
30
+ * as the replay takes.
31
+ *
32
+ * ## Why it is a function returning an effect
33
+ *
34
+ * "Caught up" has to be relative to a fixed point: the log as it stood when the
35
+ * rebuild began. Judged against the log as it stands at each ask, a read model
36
+ * under steady writes is always a batch or two behind, and the publish would wait
37
+ * for a quiet moment that may never come. So the signal is asked ONCE per resync
38
+ * attempt, as the attempt starts, for the generation that attempt rebuilds, and
39
+ * returns the PROBE the worker then polls — by which time whatever point the probe
40
+ * judges by is fixed. An attempt starts after the reset that moved the generation,
41
+ * so a point taken then is at or after the log as it stood at the reset: the error
42
+ * is towards waiting slightly longer, never towards publishing early.
43
+ *
44
+ * Both halves fail `FeedUnreadable`, the feed's own "a read could not be made":
45
+ * the worker cannot tell a probe that could not read the materialisation's
46
+ * progress from a feed it could not read, and needs to do the same thing about
47
+ * either — end the session and try again after a back-off.
48
+ */
49
+
50
+ /**
51
+ * The readiness of the common case: the materialisation's checkpoint has reached
52
+ * the last event its query selects, as the log stood when the resync attempt
53
+ * started. Pass the very read model whose `apply` writes the feed, so its query
54
+ * and its checkpoint are the ones the replay moves.
55
+ *
56
+ * The point is the last event the read model's QUERY selects, and not the log's
57
+ * head, because the runner advances its checkpoint to the positions it is
58
+ * delivered and is never delivered an event its query does not select: a
59
+ * checkpoint judged against a head an unrelated event moved would never arrive.
60
+ * The store hands no cheaper way to that position than reading up to it, so each
61
+ * attempt starts with one paged read of the query's events past the checkpoint —
62
+ * the backlog the replay itself is about to read, a page at a time and never held
63
+ * whole — and each probe after it is one checkpoint read.
64
+ *
65
+ * Generation-blind, deliberately: the read model has one checkpoint whatever the
66
+ * feed's generation, and an attempt whose generation moves on while it waits is
67
+ * abandoned by the worker's own generation check, so the probe never has to know.
68
+ */
69
+ const materialisationCaughtUp = readModel => _effect.Effect.gen(function* () {
70
+ const log = yield* _core.DcbEventStore;
71
+ const query = (0, _core.composeProjections)(readModel.slices).query;
72
+ const limit = _core.ReadLimit.make(_core.DEFAULT_CATCH_UP_PAGE_SIZE);
73
+ const checkpoint = _effect.Effect.mapError(readModel.store.readCheckpoint, cause => new _errors.FeedUnreadable({
74
+ reason: `the checkpoint of projection '${readModel.store.key.projection}' could not be read`,
75
+ cause
76
+ }));
77
+ // The last position the query selects after `from`, or `from` itself when it
78
+ // selects none: page by page, so the backlog is never held in memory at once.
79
+ const lastSelectedAfter = from => _effect.Effect.gen(function* () {
80
+ let last = from;
81
+ for (;;) {
82
+ const response = yield* log.read(query, {
83
+ after: last,
84
+ limit
85
+ });
86
+ const page = yield* _effect.Stream.runCollect(response.events);
87
+ const final = _effect.Array.last(page);
88
+ if (_effect.Option.isSome(final)) {
89
+ last = final.value.position;
90
+ }
91
+ if (page.length < limit) {
92
+ return last;
93
+ }
94
+ }
95
+ });
96
+ const readiness = () => _effect.Effect.map(_effect.Effect.flatMap(checkpoint, lastSelectedAfter), target => _effect.Effect.map(checkpoint, reached => reached >= target));
97
+ return readiness;
98
+ });
99
+ exports.materialisationCaughtUp = materialisationCaughtUp;
100
+ //# sourceMappingURL=readiness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readiness.js","names":["_core","require","_effect","_errors","materialisationCaughtUp","readModel","Effect","gen","log","DcbEventStore","query","composeProjections","slices","limit","ReadLimit","make","DEFAULT_CATCH_UP_PAGE_SIZE","checkpoint","mapError","store","readCheckpoint","cause","FeedUnreadable","reason","key","projection","lastSelectedAfter","from","last","response","read","after","page","Stream","runCollect","events","final","Arr","Option","isSome","value","position","length","readiness","map","flatMap","target","reached","exports"],"sources":["../../src/readiness.ts"],"sourcesContent":[null],"mappings":";;;;;;AAuCA,IAAAA,KAAA,GAAAC,OAAA;AASA,IAAAC,OAAA,GAAAD,OAAA;AACA,IAAAE,OAAA,GAAAF,OAAA;AAjDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA;;;;;;;;;;;;;;;;;;;AAmBO,MAAMG,uBAAuB,GAClCC,SAA4B,IAE5BC,cAAM,CAACC,GAAG,CAAC,aAAS;EAClB,MAAMC,GAAG,GAAG,OAAOC,mBAAa;EAChC,MAAMC,KAAK,GAAG,IAAAC,wBAAkB,EAACN,SAAS,CAACO,MAAM,CAAC,CAACF,KAAK;EACxD,MAAMG,KAAK,GAAGC,eAAS,CAACC,IAAI,CAACC,gCAA0B,CAAC;EACxD,MAAMC,UAAU,GAAGX,cAAM,CAACY,QAAQ,CAChCb,SAAS,CAACc,KAAK,CAACC,cAAc,EAC7BC,KAAK,IACJ,IAAIC,sBAAc,CAAC;IACjBC,MAAM,EAAE,iCAAiClB,SAAS,CAACc,KAAK,CAACK,GAAG,CAACC,UAAU,qBAAqB;IAC5FJ;GACD,CAAC,CACL;EAED;EACA;EACA,MAAMK,iBAAiB,GAAIC,IAAc,IACvCrB,cAAM,CAACC,GAAG,CAAC,aAAS;IAClB,IAAIqB,IAAI,GAAGD,IAAI;IACf,SAAS;MACP,MAAME,QAAQ,GAAG,OAAOrB,GAAG,CAACsB,IAAI,CAACpB,KAAK,EAAE;QAAEqB,KAAK,EAAEH,IAAI;QAAEf;MAAK,CAAE,CAAC;MAC/D,MAAMmB,IAAI,GAAG,OAAOC,cAAM,CAACC,UAAU,CAACL,QAAQ,CAACM,MAAM,CAAC;MACtD,MAAMC,KAAK,GAAGC,aAAG,CAACT,IAAI,CAACI,IAAI,CAAC;MAC5B,IAAIM,cAAM,CAACC,MAAM,CAACH,KAAK,CAAC,EAAE;QACxBR,IAAI,GAAGQ,KAAK,CAACI,KAAK,CAACC,QAAQ;MAC7B;MACA,IAAIT,IAAI,CAACU,MAAM,GAAG7B,KAAK,EAAE;QACvB,OAAOe,IAAI;MACb;IACF;EACF,CAAC,CAAC;EAEJ,MAAMe,SAAS,GAAkBA,CAAA,KAC/BrC,cAAM,CAACsC,GAAG,CACRtC,cAAM,CAACuC,OAAO,CAAC5B,UAAU,EAAES,iBAAiB,CAAC,EAC5CoB,MAAM,IACLxC,cAAM,CAACsC,GAAG,CAAC3B,UAAU,EAAG8B,OAAO,IAAKA,OAAO,IAAID,MAAM,CAAC,CACzD;EACH,OAAOH,SAAS;AAClB,CAAC,CAAC;AAAAK,OAAA,CAAA5C,uBAAA,GAAAA,uBAAA","ignoreList":[]}
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.unparkGone = exports.reconcileWith = exports.reconcileFeed = exports.liveKeys = void 0;
7
+ var _effect = require("effect");
8
+ var _deliver = require("./deliver.js");
9
+ var _FeedSink = require("./FeedSink.js");
10
+ var _identifiers = require("./identifiers.js");
11
+ var _tuning = require("./tuning.js");
12
+ /**
13
+ * Reconcile: delete what a sink holds that the feed no longer has.
14
+ *
15
+ * ## Why a feed needs it at all
16
+ *
17
+ * The steady state deletes through tombstones, and a tombstone lives only until
18
+ * every sink registered on the feed has passed it. A record can still be left
19
+ * behind in a sink: a sink restored from a backup, a key written into the index by
20
+ * hand, or a record deleted before a reset, whose tombstone the reset cleared with
21
+ * every other row, followed by an in-place resync that, having no atomic swap,
22
+ * sends only the rows the new generation holds and so never deletes it. Reconcile is the one operation that compares the sink with the feed
23
+ * rather than replaying the feed into the sink, and so the only one that finds
24
+ * those orphans.
25
+ *
26
+ * ## The order of the two reads, and why it is safe
27
+ *
28
+ * The sink's keys are listed FIRST and the feed's live keys read SECOND. A key the
29
+ * sink lists that has a live row by the time the feed is read is kept, so a record
30
+ * written while reconcile runs is never deleted by it. A key whose row turns into
31
+ * a tombstone after the feed is read is kept too, and its tombstone deletes it on
32
+ * the next pass. And a key whose row comes back to life after the feed is read is
33
+ * deleted here and re-sent by the next pass, because its new `seq` is above the
34
+ * cursor. Reconcile runs inside the worker that holds the lease, between batches,
35
+ * so nothing else is sending to the sink while it does (ADR-0019 §8).
36
+ *
37
+ * ## What it costs
38
+ *
39
+ * It holds every key the sink lists and every live key of the feed in memory at
40
+ * once, and reads every live document to find the keys. It is a periodic repair,
41
+ * not a steady-state path: `reconcileEvery` should be measured in hours.
42
+ */
43
+
44
+ /** Every key of `source` whose row is live, read page by page from the origin. */
45
+ const liveKeys = (source, pageSize) => _effect.Effect.gen(function* () {
46
+ const live = new Set();
47
+ const limit = (0, _tuning.readLimit)(pageSize);
48
+ let after = _identifiers.FEED_ORIGIN;
49
+ for (;;) {
50
+ const rows = yield* source.changesAfter(after, limit);
51
+ for (const row of rows) {
52
+ if (_effect.Option.isSome(row.doc)) {
53
+ live.add(row.key);
54
+ } else {
55
+ live.delete(row.key);
56
+ }
57
+ }
58
+ const last = rows.at(-1);
59
+ if (last === undefined || rows.length < pageSize) {
60
+ return live;
61
+ }
62
+ after = last.seq;
63
+ }
64
+ });
65
+ /**
66
+ * Reconcile under a lease the caller already holds. Package-internal: the driver
67
+ * calls it between batches and in an in-place resync, and `reconcileFeed` calls it
68
+ * after taking the lease itself.
69
+ *
70
+ * @internal
71
+ */
72
+ exports.liveKeys = liveKeys;
73
+ const reconcileWith = args => _effect.Effect.gen(function* () {
74
+ const listed = yield* _effect.Stream.runCollect(args.reconcile.listKeys);
75
+ const live = yield* liveKeys(args.source, args.pageSize);
76
+ const orphans = Array.from(new Set(listed)).filter(key => !live.has(key));
77
+ const deleted = [];
78
+ const refused = [];
79
+ for (const chunk of _effect.Array.chunksOf(orphans, args.pageSize)) {
80
+ const pending = _effect.Array.map(chunk, key => ({
81
+ change: _FeedSink.FeedChange.Delete({
82
+ key
83
+ }),
84
+ seq: _identifiers.FEED_ORIGIN
85
+ }));
86
+ const delivered = yield* (0, _deliver.deliver)({
87
+ send: args.sink.apply,
88
+ confirm: args.sink.confirm,
89
+ lease: args.lease,
90
+ pending
91
+ });
92
+ deleted.push(...delivered.sent);
93
+ refused.push(...delivered.parked.map(p => ({
94
+ key: p.key,
95
+ reason: p.reason
96
+ })));
97
+ }
98
+ const listedKeys = new Set(listed);
99
+ const deletedKeys = new Set(deleted);
100
+ return {
101
+ report: {
102
+ listed: listedKeys.size,
103
+ deleted,
104
+ refused
105
+ },
106
+ isGone: key => !live.has(key) && (deletedKeys.has(key) || !listedKeys.has(key))
107
+ };
108
+ });
109
+ /**
110
+ * Unpark every parked key a reconcile found gone from the sink for good (see
111
+ * {@link Reconciled}): a park reporting a refusal for such a key — an in-place
112
+ * rebuild's orphan, or a tombstone whose delete the tail was refused — reports a
113
+ * record that is not there. `retryParked` unparks a key with no row and re-stamps
114
+ * one that has a row, which for a tombstone sends every sink one idempotent delete.
115
+ *
116
+ * It judges by what the sink holds, not by what this reconcile deleted, so a park
117
+ * an earlier unpark failed to clear goes on the next reconcile. That is why a
118
+ * failure here is a warning and not an error: it costs a stale entry in
119
+ * `inspectFeed` until then, and failing would end a worker's session, or discard
120
+ * an operator's report, over a record that is already gone.
121
+ *
122
+ * @internal
123
+ */
124
+ exports.reconcileWith = reconcileWith;
125
+ const unparkGone = (store, id, isGone) => _effect.Effect.gen(function* () {
126
+ const parked = yield* store.readParked(id);
127
+ yield* _effect.Effect.forEach(parked.filter(p => isGone(p.key)), p => store.retryParked({
128
+ ...id,
129
+ key: p.key
130
+ }), {
131
+ discard: true
132
+ });
133
+ }).pipe(_effect.Effect.catch(cause => _effect.Effect.logWarning('feed reconcile could not unpark a record it found gone; the next reconcile will', {
134
+ feed: id.feed,
135
+ sink: id.sink,
136
+ reason: cause.message
137
+ })));
138
+ /**
139
+ * Reconcile `sink` against `source` once, now. It takes the worker lease itself,
140
+ * so it fails `LeaseHeld` while a sync worker for the same feed and sink runs —
141
+ * reconcile through that worker's `reconcileEvery` instead, or stop it first.
142
+ */
143
+ exports.unparkGone = unparkGone;
144
+ const reconcileFeed = options => _effect.Effect.scoped(_effect.Effect.gen(function* () {
145
+ const id = {
146
+ feed: options.source.feed,
147
+ sink: options.sink.name
148
+ };
149
+ const lease = yield* options.store.acquireLease(id);
150
+ const {
151
+ report,
152
+ isGone
153
+ } = yield* reconcileWith({
154
+ source: options.source,
155
+ sink: options.sink,
156
+ reconcile: options.sink.reconcile,
157
+ lease,
158
+ pageSize: options.pageSize ?? _tuning.DEFAULT_FEED_PAGE_SIZE
159
+ });
160
+ yield* unparkGone(options.store, id, isGone);
161
+ return report;
162
+ }));
163
+ exports.reconcileFeed = reconcileFeed;
164
+ //# sourceMappingURL=reconcile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.js","names":["_effect","require","_deliver","_FeedSink","_identifiers","_tuning","liveKeys","source","pageSize","Effect","gen","live","Set","limit","readLimit","after","FEED_ORIGIN","rows","changesAfter","row","Option","isSome","doc","add","key","delete","last","at","undefined","length","seq","exports","reconcileWith","args","listed","Stream","runCollect","reconcile","listKeys","orphans","Array","from","filter","has","deleted","refused","chunk","Arr","chunksOf","pending","map","change","FeedChange","Delete","delivered","deliver","send","sink","apply","confirm","lease","push","sent","parked","p","reason","listedKeys","deletedKeys","report","size","isGone","unparkGone","store","id","readParked","forEach","retryParked","discard","pipe","catch","cause","logWarning","feed","message","reconcileFeed","options","scoped","name","acquireLease","DEFAULT_FEED_PAGE_SIZE"],"sources":["../../src/reconcile.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}