@kairos-es/feed-postgres 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 (96) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +84 -2
  3. package/dist/cjs/config.js +46 -0
  4. package/dist/cjs/config.js.map +1 -0
  5. package/dist/cjs/ensureFeedSchema.js +39 -0
  6. package/dist/cjs/ensureFeedSchema.js.map +1 -0
  7. package/dist/cjs/feed.js +176 -0
  8. package/dist/cjs/feed.js.map +1 -0
  9. package/dist/cjs/index.js +84 -0
  10. package/dist/cjs/index.js.map +1 -0
  11. package/dist/cjs/internal/context.js +45 -0
  12. package/dist/cjs/internal/context.js.map +1 -0
  13. package/dist/cjs/internal/ddl.js +149 -0
  14. package/dist/cjs/internal/ddl.js.map +1 -0
  15. package/dist/cjs/internal/statements.js +270 -0
  16. package/dist/cjs/internal/statements.js.map +1 -0
  17. package/dist/cjs/lease.js +342 -0
  18. package/dist/cjs/lease.js.map +1 -0
  19. package/dist/cjs/syncStore.js +179 -0
  20. package/dist/cjs/syncStore.js.map +1 -0
  21. package/dist/cjs/withFeed.js +51 -0
  22. package/dist/cjs/withFeed.js.map +1 -0
  23. package/dist/dts-cjs/config.d.ts +37 -0
  24. package/dist/dts-cjs/config.d.ts.map +1 -0
  25. package/dist/dts-cjs/ensureFeedSchema.d.ts +24 -0
  26. package/dist/dts-cjs/ensureFeedSchema.d.ts.map +1 -0
  27. package/dist/dts-cjs/feed.d.ts +81 -0
  28. package/dist/dts-cjs/feed.d.ts.map +1 -0
  29. package/dist/dts-cjs/index.d.ts +50 -0
  30. package/dist/dts-cjs/index.d.ts.map +1 -0
  31. package/dist/dts-cjs/internal/context.d.ts +28 -0
  32. package/dist/dts-cjs/internal/context.d.ts.map +1 -0
  33. package/dist/dts-cjs/internal/ddl.d.ts +99 -0
  34. package/dist/dts-cjs/internal/ddl.d.ts.map +1 -0
  35. package/dist/dts-cjs/internal/statements.d.ts +127 -0
  36. package/dist/dts-cjs/internal/statements.d.ts.map +1 -0
  37. package/dist/dts-cjs/lease.d.ts +138 -0
  38. package/dist/dts-cjs/lease.d.ts.map +1 -0
  39. package/dist/dts-cjs/package.json +3 -0
  40. package/dist/dts-cjs/syncStore.d.ts +57 -0
  41. package/dist/dts-cjs/syncStore.d.ts.map +1 -0
  42. package/dist/dts-cjs/withFeed.d.ts +69 -0
  43. package/dist/dts-cjs/withFeed.d.ts.map +1 -0
  44. package/dist/dts-esm/config.d.ts +37 -0
  45. package/dist/dts-esm/config.d.ts.map +1 -0
  46. package/dist/dts-esm/ensureFeedSchema.d.ts +24 -0
  47. package/dist/dts-esm/ensureFeedSchema.d.ts.map +1 -0
  48. package/dist/dts-esm/feed.d.ts +81 -0
  49. package/dist/dts-esm/feed.d.ts.map +1 -0
  50. package/dist/dts-esm/index.d.ts +50 -0
  51. package/dist/dts-esm/index.d.ts.map +1 -0
  52. package/dist/dts-esm/internal/context.d.ts +28 -0
  53. package/dist/dts-esm/internal/context.d.ts.map +1 -0
  54. package/dist/dts-esm/internal/ddl.d.ts +99 -0
  55. package/dist/dts-esm/internal/ddl.d.ts.map +1 -0
  56. package/dist/dts-esm/internal/statements.d.ts +127 -0
  57. package/dist/dts-esm/internal/statements.d.ts.map +1 -0
  58. package/dist/dts-esm/lease.d.ts +138 -0
  59. package/dist/dts-esm/lease.d.ts.map +1 -0
  60. package/dist/dts-esm/package.json +3 -0
  61. package/dist/dts-esm/syncStore.d.ts +57 -0
  62. package/dist/dts-esm/syncStore.d.ts.map +1 -0
  63. package/dist/dts-esm/withFeed.d.ts +69 -0
  64. package/dist/dts-esm/withFeed.d.ts.map +1 -0
  65. package/dist/esm/config.js +39 -0
  66. package/dist/esm/config.js.map +1 -0
  67. package/dist/esm/ensureFeedSchema.js +31 -0
  68. package/dist/esm/ensureFeedSchema.js.map +1 -0
  69. package/dist/esm/feed.js +166 -0
  70. package/dist/esm/feed.js.map +1 -0
  71. package/dist/esm/index.js +50 -0
  72. package/dist/esm/index.js.map +1 -0
  73. package/dist/esm/internal/context.js +34 -0
  74. package/dist/esm/internal/context.js.map +1 -0
  75. package/dist/esm/internal/ddl.js +136 -0
  76. package/dist/esm/internal/ddl.js.map +1 -0
  77. package/dist/esm/internal/statements.js +262 -0
  78. package/dist/esm/internal/statements.js.map +1 -0
  79. package/dist/esm/lease.js +330 -0
  80. package/dist/esm/lease.js.map +1 -0
  81. package/dist/esm/package.json +4 -0
  82. package/dist/esm/syncStore.js +171 -0
  83. package/dist/esm/syncStore.js.map +1 -0
  84. package/dist/esm/withFeed.js +44 -0
  85. package/dist/esm/withFeed.js.map +1 -0
  86. package/package.json +35 -3
  87. package/src/config.ts +46 -0
  88. package/src/ensureFeedSchema.ts +32 -0
  89. package/src/feed.ts +284 -0
  90. package/src/index.ts +67 -0
  91. package/src/internal/context.ts +44 -0
  92. package/src/internal/ddl.ts +233 -0
  93. package/src/internal/statements.ts +387 -0
  94. package/src/lease.ts +503 -0
  95. package/src/syncStore.ts +340 -0
  96. package/src/withFeed.ts +148 -0
@@ -0,0 +1,342 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.pooledConnectionFault = exports.makeAcquireLease = exports.leaseRow = exports.advisoryLockName = exports.advisoryLock = exports.DEFAULT_LEASE_TTL = exports.DEFAULT_LEASE_HEARTBEAT = void 0;
7
+ var _feed = require("@kairos-es/feed");
8
+ var _effect = require("effect");
9
+ var _context = require("./internal/context.js");
10
+ /**
11
+ * The sync worker's lease on Postgres, in the two kinds `FeedSyncStore`'s doc
12
+ * describes, chosen in config to suit the infrastructure in front of the
13
+ * database.
14
+ *
15
+ * ## Why there are two
16
+ *
17
+ * A worker lease is what keeps a feed's sends to a sink in order: a sink has no
18
+ * transaction, so a rival worker's stale send lands before any compare-and-set can
19
+ * catch it (ADR-0019 §5). The two kinds trade the same two things against each
20
+ * other — what the connection must be, and how long a lapsed worker can go on
21
+ * sending — and neither wins on both.
22
+ *
23
+ * - **`leaseRow`, the default.** The holder and an expiry live on the sink's
24
+ * cursor row, taken with a guarded upsert (only when free, expired, or held by
25
+ * an advisory worker whose session is gone; see below) and renewed by a
26
+ * heartbeat forked into the lease's scope. Every statement is an ordinary short
27
+ * one, and the take one short transaction, so it works through a TRANSACTION
28
+ * POOLER, which drops session state and so rules out a session lock, and it
29
+ * needs no reserved connection. Its cost is a window. `ensureHeld` fails a
30
+ * MARGIN before the stored expiry: once the last successful renewal is
31
+ * `ttl - margin` old by the worker's own `Clock`, that renewal's expiry, which is
32
+ * when a rival may take the row, is at most `margin` away. So a call to the sink
33
+ * that `ensureHeld` let through, and that lands within `margin` of that check,
34
+ * pauses included, lands before any rival can hold the lease, however its
35
+ * renewals have been failing meanwhile, as long as the two workers' clocks
36
+ * agree. A call that outlasts the margin, whether it is slow or its worker
37
+ * pauses (a long GC pause, a network stall), can land after a rival took over:
38
+ * ONE stale call, since the next `ensureHeld` fails; `FeedSyncStore.ts` owns what
39
+ * that call costs. The margin is `ttl / 3` unless configured, and must leave the
40
+ * heartbeat room under the lease (`heartbeat + margin < ttl`), or `ensureHeld`
41
+ * would fail between two renewals that both landed at once. Each renewal is
42
+ * bounded by the heartbeat and the next starts on the next tick, and the age
43
+ * `ensureHeld` judges runs from when a renewal STARTED. So a renewal that lands
44
+ * is at most `heartbeat` late, and the worst age `ensureHeld` meets is
45
+ * `heartbeat` plus that latency with every renewal landing, and `2 * heartbeat`
46
+ * plus it with one lost. A worker therefore goes on sending, without a single
47
+ * failed `ensureHeld`, through renewals that are slow when
48
+ * `2 * heartbeat + margin <= ttl`, and through one that fails or hangs when
49
+ * `3 * heartbeat + margin <= ttl`. The defaults satisfy the second; the
50
+ * construction check enforces only the first's zero-latency form, so a tighter
51
+ * config is accepted and trades those guarantees for a shorter `ttl`.
52
+ * - **`advisoryLock`.** A session `pg_try_advisory_lock` on a connection RESERVED
53
+ * from a client the consumer dedicates to it, keyed by a hash of the prefix, the
54
+ * feed and the sink. The lock goes when the session goes, and `ensureHeld` asks
55
+ * that same session, before every call that changes the sink, whether it still
56
+ * holds the lock; any failure to answer is a lost lease. So a process pause
57
+ * opens no stale window, since the lock survives it. What a check cannot see is
58
+ * the session dying AFTER it: the call it let through is already under way and
59
+ * runs to its end, so it can land as late as its whole duration after the session
60
+ * died, and after a rival has taken the lock and sent. That is still ONE stale
61
+ * call, since the next `ensureHeld` asks a session that is gone, but a sink call
62
+ * can be long — a resync's `publish` most of all. The price is a connection per
63
+ * running worker, and a connection that is genuinely a SESSION: a transaction
64
+ * pooler would hand the lock's session to somebody else between statements. A
65
+ * client this package can recognise as one is refused at construction; see
66
+ * {@link pooledConnectionFault} for what it can and cannot recognise.
67
+ *
68
+ * ## Fencing is the same for both
69
+ *
70
+ * Both kinds record the holder on the cursor row, and the cursor compare-and-set
71
+ * requires it, so a worker whose lease has gone cannot move the cursor whichever
72
+ * kind it held. Mixing the two kinds on one feed and sink fails safe, because
73
+ * neither takes the row from a LIVE holder of the other. A lease-row worker takes
74
+ * over an advisory holder (one with no expiry to lapse) only while it holds that
75
+ * sink's advisory lock for its own transaction, which a live advisory session
76
+ * refuses; so a crashed advisory worker, whose scope never closed to clear the row,
77
+ * does not leave the sink unleasable by every lease-row worker after it. An
78
+ * advisory worker that wins the lock but finds a lease row unexpired on the row lets
79
+ * the lock go and fails `LeaseHeld`. Whichever took the row first keeps it while it
80
+ * lives. The advisory worker likewise takes over an ADVISORY holder recorded on the
81
+ * row, because holding the lock proves that session is gone; it records itself
82
+ * from the session that holds the lock, and only while it still does, so a session
83
+ * that died after its try-lock cannot overwrite the successor that took the lock
84
+ * from it.
85
+ */
86
+
87
+ /** The default lease length. */
88
+ const DEFAULT_LEASE_TTL = exports.DEFAULT_LEASE_TTL = /*#__PURE__*/_effect.Duration.seconds(30);
89
+ /**
90
+ * The default renewal interval: a fifth of the lease. With the default margin, a
91
+ * third, `3 * heartbeat + margin` is under the `ttl`, so a worker goes on sending,
92
+ * with no failed `ensureHeld`, through one renewal that fails or hangs. However
93
+ * many fail, it stops sending a margin before its lease could lapse. The module
94
+ * doc has the arithmetic.
95
+ */
96
+ const DEFAULT_LEASE_HEARTBEAT = exports.DEFAULT_LEASE_HEARTBEAT = /*#__PURE__*/_effect.Duration.seconds(6);
97
+ /** A lease row, the default; see the module doc. */
98
+ const leaseRow = (options = {}) => ({
99
+ _tag: 'LeaseRow',
100
+ ttl: options.ttl ?? DEFAULT_LEASE_TTL,
101
+ heartbeat: options.heartbeat ?? DEFAULT_LEASE_HEARTBEAT,
102
+ ...(options.margin === undefined ? {} : {
103
+ margin: options.margin
104
+ })
105
+ });
106
+ /** A session advisory lock on a dedicated connection; see the module doc. */
107
+ exports.leaseRow = leaseRow;
108
+ const advisoryLock = options => ({
109
+ _tag: 'AdvisoryLock',
110
+ connection: options.connection
111
+ });
112
+ /**
113
+ * Why `client` cannot carry a session lock, when this package can tell, or
114
+ * `undefined`.
115
+ *
116
+ * What it can tell is limited to what a client says about where it connects,
117
+ * which `@effect/sql-pg`'s client exposes as `config` and a generic `SqlClient`
118
+ * does not. It recognises the two pooler conventions that show in an address: a
119
+ * Neon pooled endpoint (a host containing `-pooler`), and PgBouncer's default
120
+ * port, 6432, or a `pgbouncer=true` URL parameter. A pooler on any other address,
121
+ * or behind a client that exposes nothing, passes; that is a wiring the consumer
122
+ * owns. The URL is read only to judge it and is never repeated in the message.
123
+ */
124
+ exports.advisoryLock = advisoryLock;
125
+ const pooledConnectionFault = client => {
126
+ const config = client.config;
127
+ if (config === null || typeof config !== 'object') {
128
+ return undefined;
129
+ }
130
+ const {
131
+ host,
132
+ port,
133
+ url
134
+ } = config;
135
+ const hosts = typeof host === 'string' ? [host] : [];
136
+ const ports = typeof port === 'number' ? [port] : [];
137
+ let pgbouncer = false;
138
+ const rawUrl = _effect.Redacted.isRedacted(url) ? _effect.Redacted.value(url) : typeof url === 'string' ? url : undefined;
139
+ if (typeof rawUrl === 'string') {
140
+ try {
141
+ const parsed = new URL(rawUrl);
142
+ hosts.push(parsed.hostname);
143
+ if (parsed.port !== '') {
144
+ ports.push(Number(parsed.port));
145
+ }
146
+ pgbouncer = parsed.searchParams.get('pgbouncer') === 'true';
147
+ } catch {
148
+ // Not a URL this check can read; the driver will say so itself.
149
+ }
150
+ }
151
+ const signs = [...(hosts.some(h => h.includes('-pooler')) ? ['a "-pooler" host, which is a pooled endpoint'] : []), ...(ports.includes(6432) ? ["port 6432, PgBouncer's default"] : []), ...(pgbouncer ? ['a pgbouncer=true parameter'] : [])];
152
+ return signs.length === 0 ? undefined : `the advisoryLock connection looks like a transaction pooler (${signs.join('; ')}), which hands a session to other clients between statements and so cannot hold a session lock; give it a direct connection, or use leaseRow`;
153
+ };
154
+ /** A positive, finite duration, or a construction-time throw naming it. */
155
+ exports.pooledConnectionFault = pooledConnectionFault;
156
+ const positive = (label, input) => {
157
+ const decoded = _effect.Duration.fromInputUnsafe(input);
158
+ if (!_effect.Duration.isFinite(decoded) || _effect.Duration.toMillis(decoded) <= 0) {
159
+ throw new Error(`feed-postgres: ${label} must be a positive, finite duration`);
160
+ }
161
+ return decoded;
162
+ };
163
+ /** A fresh holder id, from `Random`, never the host. */
164
+ const newHolder = kind => _effect.Effect.gen(function* () {
165
+ const high = yield* _effect.Random.nextIntBetween(0, 2 ** 31);
166
+ const low = yield* _effect.Random.nextIntBetween(0, 2 ** 31);
167
+ return _feed.LeaseHolder.make(`pg-${kind}:${high.toString(16)}${low.toString(16).padStart(8, '0')}`);
168
+ });
169
+ /**
170
+ * The advisory lock's name for one feed and sink, hashed server-side into its
171
+ * key. Package-internal, and the one home of the name: `removeSink` tries the
172
+ * same lock to tell whether an advisory worker is live, so the two must agree.
173
+ *
174
+ * @internal
175
+ */
176
+ const advisoryLockName = (context, id) => JSON.stringify(['kairos-es/feed', context.names.schema, context.names.tablePrefix, id.feed, id.sink]);
177
+ /**
178
+ * The lease-row kind; see the module doc. Throws on a heartbeat not under its ttl,
179
+ * or a margin that leaves the heartbeat no room under it.
180
+ */
181
+ exports.advisoryLockName = advisoryLockName;
182
+ const leaseRowAcquirer = (config, sql, context) => {
183
+ const {
184
+ statements
185
+ } = context;
186
+ const ttl = _effect.Duration.toMillis(positive('lease ttl', config.ttl));
187
+ const heartbeat = positive('lease heartbeat', config.heartbeat);
188
+ if (_effect.Duration.toMillis(heartbeat) >= ttl) {
189
+ throw new Error('feed-postgres: the lease heartbeat must be shorter than its ttl, or the lease lapses between renewals');
190
+ }
191
+ const margin = config.margin === undefined ? ttl / 3 : _effect.Duration.toMillis(positive('lease margin', config.margin));
192
+ if (_effect.Duration.toMillis(heartbeat) + margin >= ttl) {
193
+ throw new Error('feed-postgres: the lease heartbeat plus its margin must be shorter than its ttl, or ensureHeld fails between two renewals that both landed');
194
+ }
195
+ // `ensureHeld` fails once the last renewal is this old: its stored expiry is
196
+ // then at most `margin` away.
197
+ const usable = ttl - margin;
198
+ return id => _effect.Effect.gen(function* () {
199
+ const holder = yield* newHolder('row');
200
+ const now = yield* _effect.Clock.currentTimeMillis;
201
+ const taken = yield* sql.withTransaction(_effect.Effect.gen(function* () {
202
+ // Held to this transaction's end, so an ADVISORY holder on the row can
203
+ // be taken over only while no advisory session holds its lock, and no
204
+ // advisory worker can take the lock before this commits.
205
+ const lock = yield* sql.unsafe(statements.tryAdvisoryXactLock, [advisoryLockName(context, id)]);
206
+ return yield* sql.unsafe(statements.takeLeaseRow, [id.feed, id.sink, holder, String(now + ttl), String(now), lock[0]?.taken === true]);
207
+ })).pipe(_effect.Effect.mapError((0, _context.writeFailed)('taking the feed lease')));
208
+ if (taken.length === 0) {
209
+ return yield* new _feed.LeaseHeld(id);
210
+ }
211
+ const state = yield* _effect.Ref.make({
212
+ renewedAt: now,
213
+ lost: false
214
+ });
215
+ yield* _effect.Effect.addFinalizer(() => _effect.Effect.ignore(sql.unsafe(statements.releaseLease, [id.feed, id.sink, holder])));
216
+ const renew = _effect.Effect.gen(function* () {
217
+ const at = yield* _effect.Clock.currentTimeMillis;
218
+ const renewed = yield* sql.unsafe(statements.renewLeaseRow, [id.feed, id.sink, holder, String(at + ttl)]);
219
+ if (renewed.length === 0) {
220
+ yield* _effect.Ref.update(state, s => ({
221
+ ...s,
222
+ lost: true
223
+ }));
224
+ } else {
225
+ yield* _effect.Ref.update(state, s => ({
226
+ ...s,
227
+ renewedAt: at
228
+ }));
229
+ }
230
+ }).pipe(
231
+ // Bounded by the heartbeat, so a renewal that hangs is abandoned in time
232
+ // for the next tick rather than holding every later one behind it. An
233
+ // abandoned renewal whose cancel comes too late can still land: it touches
234
+ // only this holder's own row, never moves its expiry back
235
+ // (`renewLeaseRow`), and leaves `renewedAt` at the last renewal this fibre
236
+ // saw land, so `ensureHeld` errs early, never late.
237
+ _effect.Effect.timeoutOption(heartbeat), _effect.Effect.flatMap(done => _effect.Option.isSome(done) ? _effect.Effect.void : _effect.Effect.logWarning('feed lease renewal timed out', {
238
+ feed: id.feed,
239
+ sink: id.sink
240
+ })),
241
+ // A renewal that could not be made is not yet a lost lease: the age
242
+ // check in `ensureHeld` decides that, a margin before the lease lapses.
243
+ _effect.Effect.catchTag('SqlError', cause => _effect.Effect.logWarning('feed lease renewal failed', {
244
+ feed: id.feed,
245
+ sink: id.sink,
246
+ reason: cause.message
247
+ })));
248
+ // On a fixed cadence from the take: a renewal never outlasts the interval,
249
+ // so each one starts on its own tick whatever became of the one before. A
250
+ // worker that falls behind (a pause) renews once at once, and does not
251
+ // replay the ticks it missed.
252
+ const interval = _effect.Duration.toMillis(heartbeat);
253
+ yield* _effect.Effect.forkScoped(_effect.Effect.gen(function* () {
254
+ let next = now + interval;
255
+ while (true) {
256
+ const at = yield* _effect.Clock.currentTimeMillis;
257
+ if (next > at) {
258
+ yield* _effect.Effect.sleep(_effect.Duration.millis(next - at));
259
+ }
260
+ yield* renew;
261
+ next = Math.max(next + interval, yield* _effect.Clock.currentTimeMillis);
262
+ }
263
+ }));
264
+ const lease = {
265
+ holder,
266
+ ensureHeld: _effect.Effect.gen(function* () {
267
+ const s = yield* _effect.Ref.get(state);
268
+ const at = yield* _effect.Clock.currentTimeMillis;
269
+ if (s.lost || at - s.renewedAt >= usable) {
270
+ return yield* new _feed.LeaseLost(id);
271
+ }
272
+ })
273
+ };
274
+ return lease;
275
+ });
276
+ };
277
+ /** The advisory-lock kind; see the module doc. Throws on a recognisably pooled connection. */
278
+ const advisoryLockAcquirer = (config, sql, context) => {
279
+ const fault = pooledConnectionFault(config.connection);
280
+ if (fault !== undefined) {
281
+ throw new Error(`feed-postgres: ${fault}`);
282
+ }
283
+ const {
284
+ connection
285
+ } = config;
286
+ const {
287
+ statements
288
+ } = context;
289
+ return id => _effect.Effect.gen(function* () {
290
+ const lockName = advisoryLockName(context, id);
291
+ const session = yield* connection.reserve.pipe(_effect.Effect.mapError((0, _context.unreadable)('reserving the feed lease connection')));
292
+ const taken = yield* session.execute(statements.tryAdvisoryLock, [lockName], undefined).pipe(_effect.Effect.mapError((0, _context.unreadable)('taking the feed advisory lock')));
293
+ if (taken[0]?.taken !== true) {
294
+ return yield* new _feed.LeaseHeld(id);
295
+ }
296
+ yield* _effect.Effect.addFinalizer(() => _effect.Effect.ignore(session.execute(statements.advisoryUnlock, [lockName], undefined)));
297
+ const holder = yield* newHolder('advisory');
298
+ const now = yield* _effect.Clock.currentTimeMillis;
299
+ // On the lock's own session, and conditional on it still holding the lock:
300
+ // a session that died since the try-lock cannot run it, and one that lost
301
+ // the lock lands nothing, so neither overwrites a successor's claim.
302
+ const claimed = yield* session.execute(statements.claimAdvisoryHolder, [id.feed, id.sink, holder, String(now), lockName], undefined).pipe(_effect.Effect.mapError((0, _context.writeFailed)('recording the feed lease holder')));
303
+ if (claimed.length === 0) {
304
+ // A live lease row holds the sink, or this session no longer holds the
305
+ // lock, so this worker must not send. The lock
306
+ // guards nothing now, so it is let go at once rather than when the
307
+ // caller's scope closes, and another advisory worker may try the moment
308
+ // that lease row lapses. The finalizer's second unlock then finds nothing
309
+ // held, which is harmless.
310
+ yield* _effect.Effect.ignore(session.execute(statements.advisoryUnlock, [lockName], undefined));
311
+ return yield* new _feed.LeaseHeld(id);
312
+ }
313
+ yield* _effect.Effect.addFinalizer(() => _effect.Effect.ignore(sql.unsafe(statements.releaseLease, [id.feed, id.sink, holder])));
314
+ const held = session.execute(statements.holdsAdvisoryLock, [lockName], undefined).pipe(_effect.Effect.map(rows => rows[0]?.held === true), _effect.Effect.catchTag('SqlError', () => _effect.Effect.succeed(false)));
315
+ const lease = {
316
+ holder,
317
+ // Asked of the session that holds the lock, so a dead session cannot
318
+ // answer, and an unanswered question is a lost lease.
319
+ ensureHeld: _effect.Effect.flatMap(held, yes => yes ? _effect.Effect.void : _effect.Effect.fail(new _feed.LeaseLost(id)))
320
+ };
321
+ return lease;
322
+ });
323
+ };
324
+ /**
325
+ * Build the acquirer for `config`, checking it first: a `heartbeat` that is not
326
+ * shorter than its `ttl`, a `margin` that leaves the heartbeat no room under the
327
+ * `ttl`, or an `advisoryLock` connection recognisably pooled,
328
+ * throws here, which the store's factory turns into a defect at construction.
329
+ * The one place the lease kind is dispatched on.
330
+ *
331
+ * @internal
332
+ */
333
+ const makeAcquireLease = (config, sql, context) => {
334
+ switch (config._tag) {
335
+ case 'LeaseRow':
336
+ return leaseRowAcquirer(config, sql, context);
337
+ case 'AdvisoryLock':
338
+ return advisoryLockAcquirer(config, sql, context);
339
+ }
340
+ };
341
+ exports.makeAcquireLease = makeAcquireLease;
342
+ //# sourceMappingURL=lease.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lease.js","names":["_feed","require","_effect","_context","DEFAULT_LEASE_TTL","exports","Duration","seconds","DEFAULT_LEASE_HEARTBEAT","leaseRow","options","_tag","ttl","heartbeat","margin","undefined","advisoryLock","connection","pooledConnectionFault","client","config","host","port","url","hosts","ports","pgbouncer","rawUrl","Redacted","isRedacted","value","parsed","URL","push","hostname","Number","searchParams","get","signs","some","h","includes","length","join","positive","label","input","decoded","fromInputUnsafe","isFinite","toMillis","Error","newHolder","kind","Effect","gen","high","Random","nextIntBetween","low","LeaseHolder","make","toString","padStart","advisoryLockName","context","id","JSON","stringify","names","schema","tablePrefix","feed","sink","leaseRowAcquirer","sql","statements","usable","holder","now","Clock","currentTimeMillis","taken","withTransaction","lock","unsafe","tryAdvisoryXactLock","takeLeaseRow","String","pipe","mapError","writeFailed","LeaseHeld","state","Ref","renewedAt","lost","addFinalizer","ignore","releaseLease","renew","at","renewed","renewLeaseRow","update","s","timeoutOption","flatMap","done","Option","isSome","void","logWarning","catchTag","cause","reason","message","interval","forkScoped","next","sleep","millis","Math","max","lease","ensureHeld","LeaseLost","advisoryLockAcquirer","fault","lockName","session","reserve","unreadable","execute","tryAdvisoryLock","advisoryUnlock","claimed","claimAdvisoryHolder","held","holdsAdvisoryLock","map","rows","succeed","yes","fail","makeAcquireLease"],"sources":["../../src/lease.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.makePostgresFeedSyncStore = void 0;
7
+ var _feed = require("@kairos-es/feed");
8
+ var _effect = require("effect");
9
+ var _sql = require("effect/sql");
10
+ var _context = require("./internal/context.js");
11
+ var _lease = require("./lease.js");
12
+ /**
13
+ * The Postgres `FeedSyncStore`: every sink's cursor, its parked list and its
14
+ * worker lease, kept in the source database beside the feed rows (ADR-0019 §10).
15
+ *
16
+ * `@kairos-es/feed`'s `FeedSyncStore.ts` owns the contract, and the in-memory feed
17
+ * is the oracle this store is held to; what is here is how each atomic step the
18
+ * contract names becomes one transaction or one statement.
19
+ *
20
+ * - `casCursor` is ONE transaction: the guarded, holder-fenced cursor update,
21
+ * stamped with its `lastAdvance` from this process's `Clock`, and the parked
22
+ * list moved with it. A guard that loses fails `CursorSuperseded` inside the
23
+ * transaction, which rolls back the parked changes with it, so a park can never
24
+ * outlive a refused advance.
25
+ * - `retryParked` is one transaction that takes the feed lock exclusively before it
26
+ * re-stamps the row, so its new `seq` cannot commit out of order with the
27
+ * materialisation's own writes (`internal/statements.ts` owns that argument).
28
+ * - `collectTombstones` is one statement that reads every registered sink's cursor
29
+ * and deletes below the lowest, or nothing while any of them has no cursor, so
30
+ * no cursor can move between the two.
31
+ * - `removeSink` is one transaction that first takes the sink's advisory lock for
32
+ * its own length, which an advisory worker's session lock refuses, then deletes
33
+ * the cursor row only when no live lease row holds it, in the same statement as
34
+ * that test, and the parks after it. Both tests are therefore made whichever
35
+ * lease kind this store was built with: a sink can be leased by either kind
36
+ * (`lease.ts` owns how the two coexist), and the row alone cannot say whether
37
+ * an advisory holder's session is alive.
38
+ * - `describe` is three reads, each its own snapshot, so a figure in it can be a
39
+ * batch older or newer than the next. It is a report for an operator, and no
40
+ * decision is taken on it.
41
+ *
42
+ * One store serves every feed in its prefix: unlike the in-memory feed, whose
43
+ * store belongs to one feed, a SQL store's state is keyed by feed name in shared
44
+ * tables, so a single store value is what every worker over the prefix takes.
45
+ *
46
+ * Infrastructure faults are `FeedUnreadable` on a read and `FeedWriteFailed` on a
47
+ * write, never defects, so the driver's supervision backs off and tries again.
48
+ */
49
+
50
+ /** A timestamp column read back as epoch milliseconds, or `None` when NULL. */
51
+ const instantOf = ms => ms === null ? _effect.Option.none() : _effect.Option.some(_effect.DateTime.makeUnsafe(Number(ms)));
52
+ const cursorOf = row => row === undefined || row.generation === null || row.seq === null ? _effect.Option.none() : _effect.Option.some({
53
+ generation: _feed.Generation.make((0, _context.bigintOf)(row.generation)),
54
+ seq: _feed.FeedSeq.make((0, _context.bigintOf)(row.seq))
55
+ });
56
+ const parkedOf = row => ({
57
+ key: _feed.FeedKey.make(row.key),
58
+ seq: _feed.FeedSeq.make((0, _context.bigintOf)(row.seq)),
59
+ reason: row.reason,
60
+ parkedAt: _effect.DateTime.makeUnsafe(Number(row.parked_at_ms))
61
+ });
62
+ /** Park entries as the `jsonb` parameter the park statement takes; last per key wins. */
63
+ const parkJson = park => {
64
+ const byKey = new Map();
65
+ for (const record of park) {
66
+ byKey.delete(record.key);
67
+ byKey.set(record.key, record);
68
+ }
69
+ return JSON.stringify(Array.from(byKey.values(), record => ({
70
+ key: record.key,
71
+ seq: record.seq.toString(),
72
+ reason: record.reason,
73
+ parked_at_ms: String(_effect.DateTime.toEpochMillis(record.parkedAt))
74
+ })));
75
+ };
76
+ /**
77
+ * Build the sync store over the ambient `SqlClient`. Dies at construction on a
78
+ * malformed config, a lease whose heartbeat is not shorter than its ttl or leaves
79
+ * no room for its margin, or an `advisoryLock` connection recognisably pooled.
80
+ * `ensureFeedSchema` must have run.
81
+ */
82
+ const makePostgresFeedSyncStore = (options = {}) => _effect.Effect.gen(function* () {
83
+ const sql = (yield* _sql.SqlClient.SqlClient).withoutTransforms();
84
+ const context = (0, _context.feedContext)(options.config ?? {});
85
+ const acquireLease = (0, _lease.makeAcquireLease)(options.lease ?? (0, _lease.leaseRow)(), sql, context);
86
+ const {
87
+ statements
88
+ } = context;
89
+ const readCursor = id => sql.unsafe(statements.selectCursor, [id.feed, id.sink]).pipe(_effect.Effect.mapError((0, _context.unreadable)(`reading the cursor of sink '${id.sink}'`)), _effect.Effect.map(rows => cursorOf(rows[0])));
90
+ const casCursor = args => sql.withTransaction(_effect.Effect.gen(function* () {
91
+ const expected = _effect.Option.getOrUndefined(args.expected);
92
+ const now = yield* _effect.DateTime.now;
93
+ const landed = yield* sql.unsafe(statements.casCursor, [args.feed, args.sink, args.holder, args.next.generation.toString(), args.next.seq.toString(), expected === undefined ? null : expected.generation.toString(), expected === undefined ? null : expected.seq.toString(), String(_effect.DateTime.toEpochMillis(now))]);
94
+ if (landed.length === 0) {
95
+ return yield* new _feed.CursorSuperseded({
96
+ feed: args.feed,
97
+ sink: args.sink
98
+ });
99
+ }
100
+ const change = args.parked;
101
+ if (change._tag === 'Replace') {
102
+ yield* sql.unsafe(statements.clearParked, [args.feed, args.sink]);
103
+ } else if (change.unpark.length > 0) {
104
+ yield* sql.unsafe(statements.unparkKeys, [args.feed, args.sink, JSON.stringify(change.unpark)]);
105
+ }
106
+ if (change.park.length > 0) {
107
+ yield* sql.unsafe(statements.parkKeys, [args.feed, args.sink, parkJson(change.park)]);
108
+ }
109
+ })).pipe(_effect.Effect.catchTag('SqlError', cause => _effect.Effect.fail((0, _context.writeFailed)(`moving the cursor of sink '${args.sink}'`)(cause))));
110
+ const readParked = id => sql.unsafe(statements.selectParked, [id.feed, id.sink]).pipe(_effect.Effect.mapError((0, _context.unreadable)(`reading the parked records of sink '${id.sink}'`)), _effect.Effect.map(rows => rows.map(parkedOf)));
111
+ const retryParked = args => sql.withTransaction(_effect.Effect.gen(function* () {
112
+ yield* sql.unsafe(statements.lockFeedExclusive, [args.feed]);
113
+ const unparked = yield* sql.unsafe(statements.unparkOne, [args.feed, args.sink, args.key]);
114
+ if (unparked.length === 0) {
115
+ return false;
116
+ }
117
+ yield* sql.unsafe(statements.restampRow, [args.feed, args.key]);
118
+ return true;
119
+ })).pipe(_effect.Effect.mapError((0, _context.writeFailed)(`retrying parked record '${args.key}' of sink '${args.sink}'`)));
120
+ const collectTombstones = feed => sql.unsafe(statements.collectTombstones, [feed]).pipe(_effect.Effect.mapError((0, _context.writeFailed)(`collecting the tombstones of feed '${feed}'`)), _effect.Effect.map(rows => rows.length));
121
+ const removeSink = id => sql.withTransaction(_effect.Effect.gen(function* () {
122
+ const lock = yield* sql.unsafe(statements.tryAdvisoryXactLock, [(0, _lease.advisoryLockName)(context, id)]);
123
+ if (lock[0]?.taken !== true) {
124
+ return yield* new _feed.LeaseHeld(id);
125
+ }
126
+ const now = yield* _effect.Clock.currentTimeMillis;
127
+ const cursor = yield* sql.unsafe(statements.deleteCursorUnlessLeased, [id.feed, id.sink, String(now)]);
128
+ if (cursor.length === 0) {
129
+ const left = yield* sql.unsafe(statements.cursorExists, [id.feed, id.sink]);
130
+ if (left.length > 0) {
131
+ return yield* new _feed.LeaseHeld(id);
132
+ }
133
+ }
134
+ const parked = yield* sql.unsafe(statements.deleteSinkParked, [id.feed, id.sink]);
135
+ return cursor.length > 0 || parked.length > 0;
136
+ })).pipe(_effect.Effect.catchTag('SqlError', cause => _effect.Effect.fail((0, _context.writeFailed)(`removing sink '${id.sink}'`)(cause))));
137
+ const describe = feed => _effect.Effect.gen(function* () {
138
+ const head = yield* sql.unsafe(statements.selectHead, [feed]);
139
+ const sinks = yield* sql.unsafe(statements.describeSinks, [feed]);
140
+ const parked = yield* sql.unsafe(statements.describeParked, [feed]);
141
+ const parkedBySink = new Map();
142
+ for (const record of parked) {
143
+ const list = parkedBySink.get(record.sink);
144
+ if (list === undefined) {
145
+ parkedBySink.set(record.sink, [parkedOf(record)]);
146
+ } else {
147
+ list.push(parkedOf(record));
148
+ }
149
+ }
150
+ const description = {
151
+ head: _feed.FeedSeq.make((0, _context.bigintOf)(head[0]?.head ?? '0')),
152
+ sinks: sinks.map(row => ({
153
+ sink: _feed.SinkName.make(row.sink),
154
+ cursor: cursorOf(row),
155
+ lastAdvance: instantOf(row.last_advance_at_ms),
156
+ lease: row.lease_holder === null ? _effect.Option.none() : _effect.Option.some({
157
+ holder: _feed.LeaseHolder.make(row.lease_holder),
158
+ expiresAt: instantOf(row.lease_expires_at_ms)
159
+ }),
160
+ parked: parkedBySink.get(row.sink) ?? [],
161
+ pending: Number(row.pending)
162
+ }))
163
+ };
164
+ return description;
165
+ }).pipe(_effect.Effect.mapError((0, _context.unreadable)(`describing feed '${feed}'`)));
166
+ const store = {
167
+ acquireLease,
168
+ readCursor,
169
+ casCursor,
170
+ readParked,
171
+ retryParked,
172
+ collectTombstones,
173
+ removeSink,
174
+ describe
175
+ };
176
+ return store;
177
+ });
178
+ exports.makePostgresFeedSyncStore = makePostgresFeedSyncStore;
179
+ //# sourceMappingURL=syncStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syncStore.js","names":["_feed","require","_effect","_sql","_context","_lease","instantOf","ms","Option","none","some","DateTime","makeUnsafe","Number","cursorOf","row","undefined","generation","seq","Generation","make","bigintOf","FeedSeq","parkedOf","key","FeedKey","reason","parkedAt","parked_at_ms","parkJson","park","byKey","Map","record","delete","set","JSON","stringify","Array","from","values","toString","String","toEpochMillis","makePostgresFeedSyncStore","options","Effect","gen","sql","SqlClient","withoutTransforms","context","feedContext","config","acquireLease","makeAcquireLease","lease","leaseRow","statements","readCursor","id","unsafe","selectCursor","feed","sink","pipe","mapError","unreadable","map","rows","casCursor","args","withTransaction","expected","getOrUndefined","now","landed","holder","next","length","CursorSuperseded","change","parked","_tag","clearParked","unpark","unparkKeys","parkKeys","catchTag","cause","fail","writeFailed","readParked","selectParked","retryParked","lockFeedExclusive","unparked","unparkOne","restampRow","collectTombstones","removeSink","lock","tryAdvisoryXactLock","advisoryLockName","taken","LeaseHeld","Clock","currentTimeMillis","cursor","deleteCursorUnlessLeased","left","cursorExists","deleteSinkParked","describe","head","selectHead","sinks","describeSinks","describeParked","parkedBySink","list","get","push","description","SinkName","lastAdvance","last_advance_at_ms","lease_holder","LeaseHolder","expiresAt","lease_expires_at_ms","pending","store","exports"],"sources":["../../src/syncStore.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.withFeed = void 0;
7
+ var _read = require("@kairos-es/read");
8
+ var _effect = require("effect");
9
+ var _sql = require("effect/sql");
10
+ var _feed = require("./feed.js");
11
+ var _context = require("./internal/context.js");
12
+ const sameKey = (left, right) => left.projection === right.projection && left.partition === right.partition;
13
+ /**
14
+ * `store`, with `resetCheckpoint` for `key` also emptying `feed` and moving its
15
+ * generation on; see the module doc. Every other key passes through untouched.
16
+ */
17
+ const withFeed = (store, feed, key) => _effect.Effect.gen(function* () {
18
+ const context = (0, _feed.contextOf)(feed);
19
+ if (context === undefined) {
20
+ return yield* _effect.Effect.die(new Error(`withFeed: feed '${feed.feed}' was not made by makePostgresFeed`));
21
+ }
22
+ if (store.durability !== 'durable') {
23
+ return yield* _effect.Effect.die(new Error(`withFeed: feed '${feed.feed}' is written inside the store's commit transaction, and an ephemeral store opens none`));
24
+ }
25
+ const {
26
+ statements
27
+ } = context;
28
+ const sql = (yield* _sql.SqlClient.SqlClient).withoutTransforms();
29
+ const bound = yield* sql.unsafe(statements.bindFeed, [feed.feed, key.projection, key.partition]).pipe(_effect.Effect.mapError((0, _context.writeFailed)(`binding feed '${feed.feed}'`)));
30
+ const binding = bound[0];
31
+ if (binding !== undefined && (binding.projection !== key.projection || binding.partition !== key.partition)) {
32
+ return yield* _effect.Effect.die(new Error(`withFeed: feed '${feed.feed}' is already bound to projection '${binding.projection}' (partition '${binding.partition}'), and one feed serves one materialisation`));
33
+ }
34
+ const resetBound = reset => sql.withTransaction(_effect.Effect.gen(function* () {
35
+ yield* sql.unsafe(statements.bumpGeneration, [feed.feed]);
36
+ yield* store.resetCheckpoint(reset);
37
+ yield* sql.unsafe(statements.deleteFeedRows, [feed.feed]);
38
+ })).pipe(_effect.Effect.catchTag('SqlError', cause => _effect.Effect.fail(new _read.ProjectionStoreError({
39
+ reason: `resetting projection '${reset.projection}' partition '${reset.partition}' and its feed '${feed.feed}' failed: ${cause.message}`,
40
+ cause
41
+ }))));
42
+ const decorated = {
43
+ durability: store.durability,
44
+ readCheckpoint: store.readCheckpoint,
45
+ commit: store.commit,
46
+ resetCheckpoint: reset => sameKey(reset, key) ? resetBound(reset) : store.resetCheckpoint(reset)
47
+ };
48
+ return decorated;
49
+ });
50
+ exports.withFeed = withFeed;
51
+ //# sourceMappingURL=withFeed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withFeed.js","names":["_read","require","_effect","_sql","_feed","_context","sameKey","left","right","projection","partition","withFeed","store","feed","key","Effect","gen","context","contextOf","undefined","die","Error","durability","statements","sql","SqlClient","withoutTransforms","bound","unsafe","bindFeed","pipe","mapError","writeFailed","binding","resetBound","reset","withTransaction","bumpGeneration","resetCheckpoint","deleteFeedRows","catchTag","cause","fail","ProjectionStoreError","reason","message","decorated","readCheckpoint","commit","exports"],"sources":["../../src/withFeed.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}