@kairos-es/feed-postgres 0.0.0-stage → 1.0.0-alpha.6

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,330 @@
1
+ /**
2
+ * The sync worker's lease on Postgres, in the two kinds `FeedSyncStore`'s doc
3
+ * describes, chosen in config to suit the infrastructure in front of the
4
+ * database.
5
+ *
6
+ * ## Why there are two
7
+ *
8
+ * A worker lease is what keeps a feed's sends to a sink in order: a sink has no
9
+ * transaction, so a rival worker's stale send lands before any compare-and-set can
10
+ * catch it (ADR-0019 §5). The two kinds trade the same two things against each
11
+ * other — what the connection must be, and how long a lapsed worker can go on
12
+ * sending — and neither wins on both.
13
+ *
14
+ * - **`leaseRow`, the default.** The holder and an expiry live on the sink's
15
+ * cursor row, taken with a guarded upsert (only when free, expired, or held by
16
+ * an advisory worker whose session is gone; see below) and renewed by a
17
+ * heartbeat forked into the lease's scope. Every statement is an ordinary short
18
+ * one, and the take one short transaction, so it works through a TRANSACTION
19
+ * POOLER, which drops session state and so rules out a session lock, and it
20
+ * needs no reserved connection. Its cost is a window. `ensureHeld` fails a
21
+ * MARGIN before the stored expiry: once the last successful renewal is
22
+ * `ttl - margin` old by the worker's own `Clock`, that renewal's expiry, which is
23
+ * when a rival may take the row, is at most `margin` away. So a call to the sink
24
+ * that `ensureHeld` let through, and that lands within `margin` of that check,
25
+ * pauses included, lands before any rival can hold the lease, however its
26
+ * renewals have been failing meanwhile, as long as the two workers' clocks
27
+ * agree. A call that outlasts the margin, whether it is slow or its worker
28
+ * pauses (a long GC pause, a network stall), can land after a rival took over:
29
+ * ONE stale call, since the next `ensureHeld` fails; `FeedSyncStore.ts` owns what
30
+ * that call costs. The margin is `ttl / 3` unless configured, and must leave the
31
+ * heartbeat room under the lease (`heartbeat + margin < ttl`), or `ensureHeld`
32
+ * would fail between two renewals that both landed at once. Each renewal is
33
+ * bounded by the heartbeat and the next starts on the next tick, and the age
34
+ * `ensureHeld` judges runs from when a renewal STARTED. So a renewal that lands
35
+ * is at most `heartbeat` late, and the worst age `ensureHeld` meets is
36
+ * `heartbeat` plus that latency with every renewal landing, and `2 * heartbeat`
37
+ * plus it with one lost. A worker therefore goes on sending, without a single
38
+ * failed `ensureHeld`, through renewals that are slow when
39
+ * `2 * heartbeat + margin <= ttl`, and through one that fails or hangs when
40
+ * `3 * heartbeat + margin <= ttl`. The defaults satisfy the second; the
41
+ * construction check enforces only the first's zero-latency form, so a tighter
42
+ * config is accepted and trades those guarantees for a shorter `ttl`.
43
+ * - **`advisoryLock`.** A session `pg_try_advisory_lock` on a connection RESERVED
44
+ * from a client the consumer dedicates to it, keyed by a hash of the prefix, the
45
+ * feed and the sink. The lock goes when the session goes, and `ensureHeld` asks
46
+ * that same session, before every call that changes the sink, whether it still
47
+ * holds the lock; any failure to answer is a lost lease. So a process pause
48
+ * opens no stale window, since the lock survives it. What a check cannot see is
49
+ * the session dying AFTER it: the call it let through is already under way and
50
+ * runs to its end, so it can land as late as its whole duration after the session
51
+ * died, and after a rival has taken the lock and sent. That is still ONE stale
52
+ * call, since the next `ensureHeld` asks a session that is gone, but a sink call
53
+ * can be long — a resync's `publish` most of all. The price is a connection per
54
+ * running worker, and a connection that is genuinely a SESSION: a transaction
55
+ * pooler would hand the lock's session to somebody else between statements. A
56
+ * client this package can recognise as one is refused at construction; see
57
+ * {@link pooledConnectionFault} for what it can and cannot recognise.
58
+ *
59
+ * ## Fencing is the same for both
60
+ *
61
+ * Both kinds record the holder on the cursor row, and the cursor compare-and-set
62
+ * requires it, so a worker whose lease has gone cannot move the cursor whichever
63
+ * kind it held. Mixing the two kinds on one feed and sink fails safe, because
64
+ * neither takes the row from a LIVE holder of the other. A lease-row worker takes
65
+ * over an advisory holder (one with no expiry to lapse) only while it holds that
66
+ * sink's advisory lock for its own transaction, which a live advisory session
67
+ * refuses; so a crashed advisory worker, whose scope never closed to clear the row,
68
+ * does not leave the sink unleasable by every lease-row worker after it. An
69
+ * advisory worker that wins the lock but finds a lease row unexpired on the row lets
70
+ * the lock go and fails `LeaseHeld`. Whichever took the row first keeps it while it
71
+ * lives. The advisory worker likewise takes over an ADVISORY holder recorded on the
72
+ * row, because holding the lock proves that session is gone; it records itself
73
+ * from the session that holds the lock, and only while it still does, so a session
74
+ * that died after its try-lock cannot overwrite the successor that took the lock
75
+ * from it.
76
+ */
77
+ import { LeaseHeld, LeaseHolder, LeaseLost } from '@kairos-es/feed';
78
+ import { Clock, Duration, Effect, Option, Random, Redacted, Ref } from 'effect';
79
+ import { unreadable, writeFailed } from "./internal/context.js";
80
+ /** The default lease length. */
81
+ export const DEFAULT_LEASE_TTL = /*#__PURE__*/Duration.seconds(30);
82
+ /**
83
+ * The default renewal interval: a fifth of the lease. With the default margin, a
84
+ * third, `3 * heartbeat + margin` is under the `ttl`, so a worker goes on sending,
85
+ * with no failed `ensureHeld`, through one renewal that fails or hangs. However
86
+ * many fail, it stops sending a margin before its lease could lapse. The module
87
+ * doc has the arithmetic.
88
+ */
89
+ export const DEFAULT_LEASE_HEARTBEAT = /*#__PURE__*/Duration.seconds(6);
90
+ /** A lease row, the default; see the module doc. */
91
+ export const leaseRow = (options = {}) => ({
92
+ _tag: 'LeaseRow',
93
+ ttl: options.ttl ?? DEFAULT_LEASE_TTL,
94
+ heartbeat: options.heartbeat ?? DEFAULT_LEASE_HEARTBEAT,
95
+ ...(options.margin === undefined ? {} : {
96
+ margin: options.margin
97
+ })
98
+ });
99
+ /** A session advisory lock on a dedicated connection; see the module doc. */
100
+ export const advisoryLock = options => ({
101
+ _tag: 'AdvisoryLock',
102
+ connection: options.connection
103
+ });
104
+ /**
105
+ * Why `client` cannot carry a session lock, when this package can tell, or
106
+ * `undefined`.
107
+ *
108
+ * What it can tell is limited to what a client says about where it connects,
109
+ * which `@effect/sql-pg`'s client exposes as `config` and a generic `SqlClient`
110
+ * does not. It recognises the two pooler conventions that show in an address: a
111
+ * Neon pooled endpoint (a host containing `-pooler`), and PgBouncer's default
112
+ * port, 6432, or a `pgbouncer=true` URL parameter. A pooler on any other address,
113
+ * or behind a client that exposes nothing, passes; that is a wiring the consumer
114
+ * owns. The URL is read only to judge it and is never repeated in the message.
115
+ */
116
+ export const pooledConnectionFault = client => {
117
+ const config = client.config;
118
+ if (config === null || typeof config !== 'object') {
119
+ return undefined;
120
+ }
121
+ const {
122
+ host,
123
+ port,
124
+ url
125
+ } = config;
126
+ const hosts = typeof host === 'string' ? [host] : [];
127
+ const ports = typeof port === 'number' ? [port] : [];
128
+ let pgbouncer = false;
129
+ const rawUrl = Redacted.isRedacted(url) ? Redacted.value(url) : typeof url === 'string' ? url : undefined;
130
+ if (typeof rawUrl === 'string') {
131
+ try {
132
+ const parsed = new URL(rawUrl);
133
+ hosts.push(parsed.hostname);
134
+ if (parsed.port !== '') {
135
+ ports.push(Number(parsed.port));
136
+ }
137
+ pgbouncer = parsed.searchParams.get('pgbouncer') === 'true';
138
+ } catch {
139
+ // Not a URL this check can read; the driver will say so itself.
140
+ }
141
+ }
142
+ 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'] : [])];
143
+ 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`;
144
+ };
145
+ /** A positive, finite duration, or a construction-time throw naming it. */
146
+ const positive = (label, input) => {
147
+ const decoded = Duration.fromInputUnsafe(input);
148
+ if (!Duration.isFinite(decoded) || Duration.toMillis(decoded) <= 0) {
149
+ throw new Error(`feed-postgres: ${label} must be a positive, finite duration`);
150
+ }
151
+ return decoded;
152
+ };
153
+ /** A fresh holder id, from `Random`, never the host. */
154
+ const newHolder = kind => Effect.gen(function* () {
155
+ const high = yield* Random.nextIntBetween(0, 2 ** 31);
156
+ const low = yield* Random.nextIntBetween(0, 2 ** 31);
157
+ return LeaseHolder.make(`pg-${kind}:${high.toString(16)}${low.toString(16).padStart(8, '0')}`);
158
+ });
159
+ /**
160
+ * The advisory lock's name for one feed and sink, hashed server-side into its
161
+ * key. Package-internal, and the one home of the name: `removeSink` tries the
162
+ * same lock to tell whether an advisory worker is live, so the two must agree.
163
+ *
164
+ * @internal
165
+ */
166
+ export const advisoryLockName = (context, id) => JSON.stringify(['kairos-es/feed', context.names.schema, context.names.tablePrefix, id.feed, id.sink]);
167
+ /**
168
+ * The lease-row kind; see the module doc. Throws on a heartbeat not under its ttl,
169
+ * or a margin that leaves the heartbeat no room under it.
170
+ */
171
+ const leaseRowAcquirer = (config, sql, context) => {
172
+ const {
173
+ statements
174
+ } = context;
175
+ const ttl = Duration.toMillis(positive('lease ttl', config.ttl));
176
+ const heartbeat = positive('lease heartbeat', config.heartbeat);
177
+ if (Duration.toMillis(heartbeat) >= ttl) {
178
+ throw new Error('feed-postgres: the lease heartbeat must be shorter than its ttl, or the lease lapses between renewals');
179
+ }
180
+ const margin = config.margin === undefined ? ttl / 3 : Duration.toMillis(positive('lease margin', config.margin));
181
+ if (Duration.toMillis(heartbeat) + margin >= ttl) {
182
+ 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');
183
+ }
184
+ // `ensureHeld` fails once the last renewal is this old: its stored expiry is
185
+ // then at most `margin` away.
186
+ const usable = ttl - margin;
187
+ return id => Effect.gen(function* () {
188
+ const holder = yield* newHolder('row');
189
+ const now = yield* Clock.currentTimeMillis;
190
+ const taken = yield* sql.withTransaction(Effect.gen(function* () {
191
+ // Held to this transaction's end, so an ADVISORY holder on the row can
192
+ // be taken over only while no advisory session holds its lock, and no
193
+ // advisory worker can take the lock before this commits.
194
+ const lock = yield* sql.unsafe(statements.tryAdvisoryXactLock, [advisoryLockName(context, id)]);
195
+ return yield* sql.unsafe(statements.takeLeaseRow, [id.feed, id.sink, holder, String(now + ttl), String(now), lock[0]?.taken === true]);
196
+ })).pipe(Effect.mapError(writeFailed('taking the feed lease')));
197
+ if (taken.length === 0) {
198
+ return yield* new LeaseHeld(id);
199
+ }
200
+ const state = yield* Ref.make({
201
+ renewedAt: now,
202
+ lost: false
203
+ });
204
+ yield* Effect.addFinalizer(() => Effect.ignore(sql.unsafe(statements.releaseLease, [id.feed, id.sink, holder])));
205
+ const renew = Effect.gen(function* () {
206
+ const at = yield* Clock.currentTimeMillis;
207
+ const renewed = yield* sql.unsafe(statements.renewLeaseRow, [id.feed, id.sink, holder, String(at + ttl)]);
208
+ if (renewed.length === 0) {
209
+ yield* Ref.update(state, s => ({
210
+ ...s,
211
+ lost: true
212
+ }));
213
+ } else {
214
+ yield* Ref.update(state, s => ({
215
+ ...s,
216
+ renewedAt: at
217
+ }));
218
+ }
219
+ }).pipe(
220
+ // Bounded by the heartbeat, so a renewal that hangs is abandoned in time
221
+ // for the next tick rather than holding every later one behind it. An
222
+ // abandoned renewal whose cancel comes too late can still land: it touches
223
+ // only this holder's own row, never moves its expiry back
224
+ // (`renewLeaseRow`), and leaves `renewedAt` at the last renewal this fibre
225
+ // saw land, so `ensureHeld` errs early, never late.
226
+ Effect.timeoutOption(heartbeat), Effect.flatMap(done => Option.isSome(done) ? Effect.void : Effect.logWarning('feed lease renewal timed out', {
227
+ feed: id.feed,
228
+ sink: id.sink
229
+ })),
230
+ // A renewal that could not be made is not yet a lost lease: the age
231
+ // check in `ensureHeld` decides that, a margin before the lease lapses.
232
+ Effect.catchTag('SqlError', cause => Effect.logWarning('feed lease renewal failed', {
233
+ feed: id.feed,
234
+ sink: id.sink,
235
+ reason: cause.message
236
+ })));
237
+ // On a fixed cadence from the take: a renewal never outlasts the interval,
238
+ // so each one starts on its own tick whatever became of the one before. A
239
+ // worker that falls behind (a pause) renews once at once, and does not
240
+ // replay the ticks it missed.
241
+ const interval = Duration.toMillis(heartbeat);
242
+ yield* Effect.forkScoped(Effect.gen(function* () {
243
+ let next = now + interval;
244
+ while (true) {
245
+ const at = yield* Clock.currentTimeMillis;
246
+ if (next > at) {
247
+ yield* Effect.sleep(Duration.millis(next - at));
248
+ }
249
+ yield* renew;
250
+ next = Math.max(next + interval, yield* Clock.currentTimeMillis);
251
+ }
252
+ }));
253
+ const lease = {
254
+ holder,
255
+ ensureHeld: Effect.gen(function* () {
256
+ const s = yield* Ref.get(state);
257
+ const at = yield* Clock.currentTimeMillis;
258
+ if (s.lost || at - s.renewedAt >= usable) {
259
+ return yield* new LeaseLost(id);
260
+ }
261
+ })
262
+ };
263
+ return lease;
264
+ });
265
+ };
266
+ /** The advisory-lock kind; see the module doc. Throws on a recognisably pooled connection. */
267
+ const advisoryLockAcquirer = (config, sql, context) => {
268
+ const fault = pooledConnectionFault(config.connection);
269
+ if (fault !== undefined) {
270
+ throw new Error(`feed-postgres: ${fault}`);
271
+ }
272
+ const {
273
+ connection
274
+ } = config;
275
+ const {
276
+ statements
277
+ } = context;
278
+ return id => Effect.gen(function* () {
279
+ const lockName = advisoryLockName(context, id);
280
+ const session = yield* connection.reserve.pipe(Effect.mapError(unreadable('reserving the feed lease connection')));
281
+ const taken = yield* session.execute(statements.tryAdvisoryLock, [lockName], undefined).pipe(Effect.mapError(unreadable('taking the feed advisory lock')));
282
+ if (taken[0]?.taken !== true) {
283
+ return yield* new LeaseHeld(id);
284
+ }
285
+ yield* Effect.addFinalizer(() => Effect.ignore(session.execute(statements.advisoryUnlock, [lockName], undefined)));
286
+ const holder = yield* newHolder('advisory');
287
+ const now = yield* Clock.currentTimeMillis;
288
+ // On the lock's own session, and conditional on it still holding the lock:
289
+ // a session that died since the try-lock cannot run it, and one that lost
290
+ // the lock lands nothing, so neither overwrites a successor's claim.
291
+ const claimed = yield* session.execute(statements.claimAdvisoryHolder, [id.feed, id.sink, holder, String(now), lockName], undefined).pipe(Effect.mapError(writeFailed('recording the feed lease holder')));
292
+ if (claimed.length === 0) {
293
+ // A live lease row holds the sink, or this session no longer holds the
294
+ // lock, so this worker must not send. The lock
295
+ // guards nothing now, so it is let go at once rather than when the
296
+ // caller's scope closes, and another advisory worker may try the moment
297
+ // that lease row lapses. The finalizer's second unlock then finds nothing
298
+ // held, which is harmless.
299
+ yield* Effect.ignore(session.execute(statements.advisoryUnlock, [lockName], undefined));
300
+ return yield* new LeaseHeld(id);
301
+ }
302
+ yield* Effect.addFinalizer(() => Effect.ignore(sql.unsafe(statements.releaseLease, [id.feed, id.sink, holder])));
303
+ const held = session.execute(statements.holdsAdvisoryLock, [lockName], undefined).pipe(Effect.map(rows => rows[0]?.held === true), Effect.catchTag('SqlError', () => Effect.succeed(false)));
304
+ const lease = {
305
+ holder,
306
+ // Asked of the session that holds the lock, so a dead session cannot
307
+ // answer, and an unanswered question is a lost lease.
308
+ ensureHeld: Effect.flatMap(held, yes => yes ? Effect.void : Effect.fail(new LeaseLost(id)))
309
+ };
310
+ return lease;
311
+ });
312
+ };
313
+ /**
314
+ * Build the acquirer for `config`, checking it first: a `heartbeat` that is not
315
+ * shorter than its `ttl`, a `margin` that leaves the heartbeat no room under the
316
+ * `ttl`, or an `advisoryLock` connection recognisably pooled,
317
+ * throws here, which the store's factory turns into a defect at construction.
318
+ * The one place the lease kind is dispatched on.
319
+ *
320
+ * @internal
321
+ */
322
+ export const makeAcquireLease = (config, sql, context) => {
323
+ switch (config._tag) {
324
+ case 'LeaseRow':
325
+ return leaseRowAcquirer(config, sql, context);
326
+ case 'AdvisoryLock':
327
+ return advisoryLockAcquirer(config, sql, context);
328
+ }
329
+ };
330
+ //# sourceMappingURL=lease.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lease.js","names":["LeaseHeld","LeaseHolder","LeaseLost","Clock","Duration","Effect","Option","Random","Redacted","Ref","unreadable","writeFailed","DEFAULT_LEASE_TTL","seconds","DEFAULT_LEASE_HEARTBEAT","leaseRow","options","_tag","ttl","heartbeat","margin","undefined","advisoryLock","connection","pooledConnectionFault","client","config","host","port","url","hosts","ports","pgbouncer","rawUrl","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","gen","high","nextIntBetween","low","make","toString","padStart","advisoryLockName","context","id","JSON","stringify","names","schema","tablePrefix","feed","sink","leaseRowAcquirer","sql","statements","usable","holder","now","currentTimeMillis","taken","withTransaction","lock","unsafe","tryAdvisoryXactLock","takeLeaseRow","String","pipe","mapError","state","renewedAt","lost","addFinalizer","ignore","releaseLease","renew","at","renewed","renewLeaseRow","update","s","timeoutOption","flatMap","done","isSome","void","logWarning","catchTag","cause","reason","message","interval","forkScoped","next","sleep","millis","Math","max","lease","ensureHeld","advisoryLockAcquirer","fault","lockName","session","reserve","execute","tryAdvisoryLock","advisoryUnlock","claimed","claimAdvisoryHolder","held","holdsAdvisoryLock","map","rows","succeed","yes","fail","makeAcquireLease"],"sources":["../../src/lease.ts"],"sourcesContent":[null],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA,SAKEA,SAAS,EACTC,WAAW,EACXC,SAAS,QACJ,iBAAiB;AACxB,SACEC,KAAK,EACLC,QAAQ,EACRC,MAAM,EACNC,MAAM,EACNC,MAAM,EACNC,QAAQ,EACRC,GAAG,QAEE,QAAQ;AAEf,SAA2BC,UAAU,EAAEC,WAAW,QAAQ,uBAAoB;AA4B9E;AACA,OAAO,MAAMC,iBAAiB,gBAAsBR,QAAQ,CAACS,OAAO,CAAC,EAAE,CAAC;AAExE;;;;;;;AAOA,OAAO,MAAMC,uBAAuB,gBAAsBV,QAAQ,CAACS,OAAO,CAAC,CAAC,CAAC;AAE7E;AACA,OAAO,MAAME,QAAQ,GAAGA,CACtBC,OAAA,GAII,EAAE,MACe;EACrBC,IAAI,EAAE,UAAU;EAChBC,GAAG,EAAEF,OAAO,CAACE,GAAG,IAAIN,iBAAiB;EACrCO,SAAS,EAAEH,OAAO,CAACG,SAAS,IAAIL,uBAAuB;EACvD,IAAIE,OAAO,CAACI,MAAM,KAAKC,SAAS,GAAG,EAAE,GAAG;IAAED,MAAM,EAAEJ,OAAO,CAACI;EAAM,CAAE;CACnE,CAAC;AAEF;AACA,OAAO,MAAME,YAAY,GAAIN,OAE5B,KAAuB;EACtBC,IAAI,EAAE,cAAc;EACpBM,UAAU,EAAEP,OAAO,CAACO;CACrB,CAAC;AAEF;;;;;;;;;;;;AAYA,OAAO,MAAMC,qBAAqB,GAChCC,MAA2B,IACL;EACtB,MAAMC,MAAM,GAAID,MAAmD,CAACC,MAAM;EAC1E,IAAIA,MAAM,KAAK,IAAI,IAAI,OAAOA,MAAM,KAAK,QAAQ,EAAE;IACjD,OAAOL,SAAS;EAClB;EACA,MAAM;IAAEM,IAAI;IAAEC,IAAI;IAAEC;EAAG,CAAE,GAAGH,MAI3B;EACD,MAAMI,KAAK,GAAkB,OAAOH,IAAI,KAAK,QAAQ,GAAG,CAACA,IAAI,CAAC,GAAG,EAAE;EACnE,MAAMI,KAAK,GAAkB,OAAOH,IAAI,KAAK,QAAQ,GAAG,CAACA,IAAI,CAAC,GAAG,EAAE;EACnE,IAAII,SAAS,GAAG,KAAK;EACrB,MAAMC,MAAM,GAAGzB,QAAQ,CAAC0B,UAAU,CAACL,GAAG,CAAC,GACnCrB,QAAQ,CAAC2B,KAAK,CAACN,GAAG,CAAC,GACnB,OAAOA,GAAG,KAAK,QAAQ,GACrBA,GAAG,GACHR,SAAS;EACf,IAAI,OAAOY,MAAM,KAAK,QAAQ,EAAE;IAC9B,IAAI;MACF,MAAMG,MAAM,GAAG,IAAIC,GAAG,CAACJ,MAAM,CAAC;MAC9BH,KAAK,CAACQ,IAAI,CAACF,MAAM,CAACG,QAAQ,CAAC;MAC3B,IAAIH,MAAM,CAACR,IAAI,KAAK,EAAE,EAAE;QACtBG,KAAK,CAACO,IAAI,CAACE,MAAM,CAACJ,MAAM,CAACR,IAAI,CAAC,CAAC;MACjC;MACAI,SAAS,GAAGI,MAAM,CAACK,YAAY,CAACC,GAAG,CAAC,WAAW,CAAC,KAAK,MAAM;IAC7D,CAAC,CAAC,MAAM;MACN;IAAA;EAEJ;EACA,MAAMC,KAAK,GAAG,CACZ,IAAIb,KAAK,CAACc,IAAI,CAAEC,CAAC,IAAKA,CAAC,CAACC,QAAQ,CAAC,SAAS,CAAC,CAAC,GACxC,CAAC,8CAA8C,CAAC,GAChD,EAAE,CAAC,EACP,IAAIf,KAAK,CAACe,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,gCAAgC,CAAC,GAAG,EAAE,CAAC,EACnE,IAAId,SAAS,GAAG,CAAC,4BAA4B,CAAC,GAAG,EAAE,CAAC,CACrD;EACD,OAAOW,KAAK,CAACI,MAAM,KAAK,CAAC,GACrB1B,SAAS,GACT,gEAAgEsB,KAAK,CAACK,IAAI,CAAC,IAAI,CAAC,8IAA8I;AACpO,CAAC;AAWD;AACA,MAAMC,QAAQ,GAAGA,CAACC,KAAa,EAAEC,KAAqB,KAAuB;EAC3E,MAAMC,OAAO,GAAGhD,QAAQ,CAACiD,eAAe,CAACF,KAAK,CAAC;EAC/C,IAAI,CAAC/C,QAAQ,CAACkD,QAAQ,CAACF,OAAO,CAAC,IAAIhD,QAAQ,CAACmD,QAAQ,CAACH,OAAO,CAAC,IAAI,CAAC,EAAE;IAClE,MAAM,IAAII,KAAK,CACb,kBAAkBN,KAAK,sCAAsC,CAC9D;EACH;EACA,OAAOE,OAAO;AAChB,CAAC;AAED;AACA,MAAMK,SAAS,GAAIC,IAAY,IAC7BrD,MAAM,CAACsD,GAAG,CAAC,aAAS;EAClB,MAAMC,IAAI,GAAG,OAAOrD,MAAM,CAACsD,cAAc,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;EACrD,MAAMC,GAAG,GAAG,OAAOvD,MAAM,CAACsD,cAAc,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;EACpD,OAAO5D,WAAW,CAAC8D,IAAI,CACrB,MAAML,IAAI,IAAIE,IAAI,CAACI,QAAQ,CAAC,EAAE,CAAC,GAAGF,GAAG,CAACE,QAAQ,CAAC,EAAE,CAAC,CAACC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CACtE;AACH,CAAC,CAAC;AAEJ;;;;;;;AAOA,OAAO,MAAMC,gBAAgB,GAAGA,CAC9BC,OAAoB,EACpBC,EAAc,KAEdC,IAAI,CAACC,SAAS,CAAC,CACb,gBAAgB,EAChBH,OAAO,CAACI,KAAK,CAACC,MAAM,EACpBL,OAAO,CAACI,KAAK,CAACE,WAAW,EACzBL,EAAE,CAACM,IAAI,EACPN,EAAE,CAACO,IAAI,CACR,CAAC;AAQJ;;;;AAIA,MAAMC,gBAAgB,GAAGA,CACvBlD,MAAsB,EACtBmD,GAAwB,EACxBV,OAAoB,KACJ;EAChB,MAAM;IAAEW;EAAU,CAAE,GAAGX,OAAO;EAC9B,MAAMjD,GAAG,GAAGd,QAAQ,CAACmD,QAAQ,CAACN,QAAQ,CAAC,WAAW,EAAEvB,MAAM,CAACR,GAAG,CAAC,CAAC;EAChE,MAAMC,SAAS,GAAG8B,QAAQ,CAAC,iBAAiB,EAAEvB,MAAM,CAACP,SAAS,CAAC;EAC/D,IAAIf,QAAQ,CAACmD,QAAQ,CAACpC,SAAS,CAAC,IAAID,GAAG,EAAE;IACvC,MAAM,IAAIsC,KAAK,CACb,uGAAuG,CACxG;EACH;EACA,MAAMpC,MAAM,GACVM,MAAM,CAACN,MAAM,KAAKC,SAAS,GACvBH,GAAG,GAAG,CAAC,GACPd,QAAQ,CAACmD,QAAQ,CAACN,QAAQ,CAAC,cAAc,EAAEvB,MAAM,CAACN,MAAM,CAAC,CAAC;EAChE,IAAIhB,QAAQ,CAACmD,QAAQ,CAACpC,SAAS,CAAC,GAAGC,MAAM,IAAIF,GAAG,EAAE;IAChD,MAAM,IAAIsC,KAAK,CACb,4IAA4I,CAC7I;EACH;EACA;EACA;EACA,MAAMuB,MAAM,GAAG7D,GAAG,GAAGE,MAAM;EAC3B,OAAQgD,EAAE,IACR/D,MAAM,CAACsD,GAAG,CAAC,aAAS;IAClB,MAAMqB,MAAM,GAAG,OAAOvB,SAAS,CAAC,KAAK,CAAC;IACtC,MAAMwB,GAAG,GAAG,OAAO9E,KAAK,CAAC+E,iBAAiB;IAC1C,MAAMC,KAAK,GAAG,OAAON,GAAG,CACrBO,eAAe,CACd/E,MAAM,CAACsD,GAAG,CAAC,aAAS;MAClB;MACA;MACA;MACA,MAAM0B,IAAI,GAAG,OAAOR,GAAG,CAACS,MAAM,CAC5BR,UAAU,CAACS,mBAAmB,EAC9B,CAACrB,gBAAgB,CAACC,OAAO,EAAEC,EAAE,CAAC,CAAC,CAChC;MACD,OAAO,OAAOS,GAAG,CAACS,MAAM,CAACR,UAAU,CAACU,YAAY,EAAE,CAChDpB,EAAE,CAACM,IAAI,EACPN,EAAE,CAACO,IAAI,EACPK,MAAM,EACNS,MAAM,CAACR,GAAG,GAAG/D,GAAG,CAAC,EACjBuE,MAAM,CAACR,GAAG,CAAC,EACXI,IAAI,CAAC,CAAC,CAAC,EAAEF,KAAK,KAAK,IAAI,CACxB,CAAC;IACJ,CAAC,CAAC,CACH,CACAO,IAAI,CAACrF,MAAM,CAACsF,QAAQ,CAAChF,WAAW,CAAC,uBAAuB,CAAC,CAAC,CAAC;IAC9D,IAAIwE,KAAK,CAACpC,MAAM,KAAK,CAAC,EAAE;MACtB,OAAO,OAAO,IAAI/C,SAAS,CAACoE,EAAE,CAAC;IACjC;IACA,MAAMwB,KAAK,GAAG,OAAOnF,GAAG,CAACsD,IAAI,CAAC;MAAE8B,SAAS,EAAEZ,GAAG;MAAEa,IAAI,EAAE;IAAK,CAAE,CAAC;IAC9D,OAAOzF,MAAM,CAAC0F,YAAY,CAAC,MACzB1F,MAAM,CAAC2F,MAAM,CACXnB,GAAG,CAACS,MAAM,CAACR,UAAU,CAACmB,YAAY,EAAE,CAAC7B,EAAE,CAACM,IAAI,EAAEN,EAAE,CAACO,IAAI,EAAEK,MAAM,CAAC,CAAC,CAChE,CACF;IACD,MAAMkB,KAAK,GAAG7F,MAAM,CAACsD,GAAG,CAAC,aAAS;MAChC,MAAMwC,EAAE,GAAG,OAAOhG,KAAK,CAAC+E,iBAAiB;MACzC,MAAMkB,OAAO,GAAG,OAAOvB,GAAG,CAACS,MAAM,CAACR,UAAU,CAACuB,aAAa,EAAE,CAC1DjC,EAAE,CAACM,IAAI,EACPN,EAAE,CAACO,IAAI,EACPK,MAAM,EACNS,MAAM,CAACU,EAAE,GAAGjF,GAAG,CAAC,CACjB,CAAC;MACF,IAAIkF,OAAO,CAACrD,MAAM,KAAK,CAAC,EAAE;QACxB,OAAOtC,GAAG,CAAC6F,MAAM,CAACV,KAAK,EAAGW,CAAC,KAAM;UAAE,GAAGA,CAAC;UAAET,IAAI,EAAE;QAAI,CAAE,CAAC,CAAC;MACzD,CAAC,MAAM;QACL,OAAOrF,GAAG,CAAC6F,MAAM,CAACV,KAAK,EAAGW,CAAC,KAAM;UAAE,GAAGA,CAAC;UAAEV,SAAS,EAAEM;QAAE,CAAE,CAAC,CAAC;MAC5D;IACF,CAAC,CAAC,CAACT,IAAI;IACL;IACA;IACA;IACA;IACA;IACA;IACArF,MAAM,CAACmG,aAAa,CAACrF,SAAS,CAAC,EAC/Bd,MAAM,CAACoG,OAAO,CAAEC,IAAI,IAClBpG,MAAM,CAACqG,MAAM,CAACD,IAAI,CAAC,GACfrG,MAAM,CAACuG,IAAI,GACXvG,MAAM,CAACwG,UAAU,CAAC,8BAA8B,EAAE;MAChDnC,IAAI,EAAEN,EAAE,CAACM,IAAI;MACbC,IAAI,EAAEP,EAAE,CAACO;KACV,CAAC,CACP;IACD;IACA;IACAtE,MAAM,CAACyG,QAAQ,CAAC,UAAU,EAAGC,KAAK,IAChC1G,MAAM,CAACwG,UAAU,CAAC,2BAA2B,EAAE;MAC7CnC,IAAI,EAAEN,EAAE,CAACM,IAAI;MACbC,IAAI,EAAEP,EAAE,CAACO,IAAI;MACbqC,MAAM,EAAED,KAAK,CAACE;KACf,CAAC,CACH,CACF;IACD;IACA;IACA;IACA;IACA,MAAMC,QAAQ,GAAG9G,QAAQ,CAACmD,QAAQ,CAACpC,SAAS,CAAC;IAC7C,OAAOd,MAAM,CAAC8G,UAAU,CACtB9G,MAAM,CAACsD,GAAG,CAAC,aAAS;MAClB,IAAIyD,IAAI,GAAGnC,GAAG,GAAGiC,QAAQ;MACzB,OAAO,IAAI,EAAE;QACX,MAAMf,EAAE,GAAG,OAAOhG,KAAK,CAAC+E,iBAAiB;QACzC,IAAIkC,IAAI,GAAGjB,EAAE,EAAE;UACb,OAAO9F,MAAM,CAACgH,KAAK,CAACjH,QAAQ,CAACkH,MAAM,CAACF,IAAI,GAAGjB,EAAE,CAAC,CAAC;QACjD;QACA,OAAOD,KAAK;QACZkB,IAAI,GAAGG,IAAI,CAACC,GAAG,CAACJ,IAAI,GAAGF,QAAQ,EAAE,OAAO/G,KAAK,CAAC+E,iBAAiB,CAAC;MAClE;IACF,CAAC,CAAC,CACH;IACD,MAAMuC,KAAK,GAAc;MACvBzC,MAAM;MACN0C,UAAU,EAAErH,MAAM,CAACsD,GAAG,CAAC,aAAS;QAC9B,MAAM4C,CAAC,GAAG,OAAO9F,GAAG,CAACiC,GAAG,CAACkD,KAAK,CAAC;QAC/B,MAAMO,EAAE,GAAG,OAAOhG,KAAK,CAAC+E,iBAAiB;QACzC,IAAIqB,CAAC,CAACT,IAAI,IAAIK,EAAE,GAAGI,CAAC,CAACV,SAAS,IAAId,MAAM,EAAE;UACxC,OAAO,OAAO,IAAI7E,SAAS,CAACkE,EAAE,CAAC;QACjC;MACF,CAAC;KACF;IACD,OAAOqD,KAAK;EACd,CAAC,CAAC;AACN,CAAC;AAED;AACA,MAAME,oBAAoB,GAAGA,CAC3BjG,MAA0B,EAC1BmD,GAAwB,EACxBV,OAAoB,KACJ;EAChB,MAAMyD,KAAK,GAAGpG,qBAAqB,CAACE,MAAM,CAACH,UAAU,CAAC;EACtD,IAAIqG,KAAK,KAAKvG,SAAS,EAAE;IACvB,MAAM,IAAImC,KAAK,CAAC,kBAAkBoE,KAAK,EAAE,CAAC;EAC5C;EACA,MAAM;IAAErG;EAAU,CAAE,GAAGG,MAAM;EAC7B,MAAM;IAAEoD;EAAU,CAAE,GAAGX,OAAO;EAC9B,OAAQC,EAAE,IACR/D,MAAM,CAACsD,GAAG,CAAC,aAAS;IAClB,MAAMkE,QAAQ,GAAG3D,gBAAgB,CAACC,OAAO,EAAEC,EAAE,CAAC;IAC9C,MAAM0D,OAAO,GAAG,OAAOvG,UAAU,CAACwG,OAAO,CAACrC,IAAI,CAC5CrF,MAAM,CAACsF,QAAQ,CAACjF,UAAU,CAAC,qCAAqC,CAAC,CAAC,CACnE;IACD,MAAMyE,KAAK,GAAgD,OAAO2C,OAAO,CACtEE,OAAO,CAAClD,UAAU,CAACmD,eAAe,EAAE,CAACJ,QAAQ,CAAC,EAAExG,SAAS,CAAC,CAC1DqE,IAAI,CAACrF,MAAM,CAACsF,QAAQ,CAACjF,UAAU,CAAC,+BAA+B,CAAC,CAAC,CAAC;IACrE,IAAIyE,KAAK,CAAC,CAAC,CAAC,EAAEA,KAAK,KAAK,IAAI,EAAE;MAC5B,OAAO,OAAO,IAAInF,SAAS,CAACoE,EAAE,CAAC;IACjC;IACA,OAAO/D,MAAM,CAAC0F,YAAY,CAAC,MACzB1F,MAAM,CAAC2F,MAAM,CACX8B,OAAO,CAACE,OAAO,CAAClD,UAAU,CAACoD,cAAc,EAAE,CAACL,QAAQ,CAAC,EAAExG,SAAS,CAAC,CAClE,CACF;IACD,MAAM2D,MAAM,GAAG,OAAOvB,SAAS,CAAC,UAAU,CAAC;IAC3C,MAAMwB,GAAG,GAAG,OAAO9E,KAAK,CAAC+E,iBAAiB;IAC1C;IACA;IACA;IACA,MAAMiD,OAAO,GAA2B,OAAOL,OAAO,CACnDE,OAAO,CACNlD,UAAU,CAACsD,mBAAmB,EAC9B,CAAChE,EAAE,CAACM,IAAI,EAAEN,EAAE,CAACO,IAAI,EAAEK,MAAM,EAAES,MAAM,CAACR,GAAG,CAAC,EAAE4C,QAAQ,CAAC,EACjDxG,SAAS,CACV,CACAqE,IAAI,CAACrF,MAAM,CAACsF,QAAQ,CAAChF,WAAW,CAAC,iCAAiC,CAAC,CAAC,CAAC;IACxE,IAAIwH,OAAO,CAACpF,MAAM,KAAK,CAAC,EAAE;MACxB;MACA;MACA;MACA;MACA;MACA;MACA,OAAO1C,MAAM,CAAC2F,MAAM,CAClB8B,OAAO,CAACE,OAAO,CAAClD,UAAU,CAACoD,cAAc,EAAE,CAACL,QAAQ,CAAC,EAAExG,SAAS,CAAC,CAClE;MACD,OAAO,OAAO,IAAIrB,SAAS,CAACoE,EAAE,CAAC;IACjC;IACA,OAAO/D,MAAM,CAAC0F,YAAY,CAAC,MACzB1F,MAAM,CAAC2F,MAAM,CACXnB,GAAG,CAACS,MAAM,CAACR,UAAU,CAACmB,YAAY,EAAE,CAAC7B,EAAE,CAACM,IAAI,EAAEN,EAAE,CAACO,IAAI,EAAEK,MAAM,CAAC,CAAC,CAChE,CACF;IACD,MAAMqD,IAAI,GAA2BP,OAAO,CACzCE,OAAO,CAAClD,UAAU,CAACwD,iBAAiB,EAAE,CAACT,QAAQ,CAAC,EAAExG,SAAS,CAAC,CAC5DqE,IAAI,CACHrF,MAAM,CAACkI,GAAG,CACPC,IAAgD,IAC/CA,IAAI,CAAC,CAAC,CAAC,EAAEH,IAAI,KAAK,IAAI,CACzB,EACDhI,MAAM,CAACyG,QAAQ,CAAC,UAAU,EAAE,MAAMzG,MAAM,CAACoI,OAAO,CAAC,KAAK,CAAC,CAAC,CACzD;IACH,MAAMhB,KAAK,GAAc;MACvBzC,MAAM;MACN;MACA;MACA0C,UAAU,EAAErH,MAAM,CAACoG,OAAO,CAAC4B,IAAI,EAAGK,GAAG,IACnCA,GAAG,GAAGrI,MAAM,CAACuG,IAAI,GAAGvG,MAAM,CAACsI,IAAI,CAAC,IAAIzI,SAAS,CAACkE,EAAE,CAAC,CAAC;KAErD;IACD,OAAOqD,KAAK;EACd,CAAC,CAAC;AACN,CAAC;AAED;;;;;;;;;AASA,OAAO,MAAMmB,gBAAgB,GAAGA,CAC9BlH,MAAuB,EACvBmD,GAAwB,EACxBV,OAAoB,KACJ;EAChB,QAAQzC,MAAM,CAACT,IAAI;IACjB,KAAK,UAAU;MACb,OAAO2D,gBAAgB,CAAClD,MAAM,EAAEmD,GAAG,EAAEV,OAAO,CAAC;IAC/C,KAAK,cAAc;MACjB,OAAOwD,oBAAoB,CAACjG,MAAM,EAAEmD,GAAG,EAAEV,OAAO,CAAC;EACrD;AACF,CAAC","ignoreList":[]}
@@ -0,0 +1,4 @@
1
+ {
2
+ "type": "module",
3
+ "sideEffects": []
4
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * The Postgres `FeedSyncStore`: every sink's cursor, its parked list and its
3
+ * worker lease, kept in the source database beside the feed rows (ADR-0019 §10).
4
+ *
5
+ * `@kairos-es/feed`'s `FeedSyncStore.ts` owns the contract, and the in-memory feed
6
+ * is the oracle this store is held to; what is here is how each atomic step the
7
+ * contract names becomes one transaction or one statement.
8
+ *
9
+ * - `casCursor` is ONE transaction: the guarded, holder-fenced cursor update,
10
+ * stamped with its `lastAdvance` from this process's `Clock`, and the parked
11
+ * list moved with it. A guard that loses fails `CursorSuperseded` inside the
12
+ * transaction, which rolls back the parked changes with it, so a park can never
13
+ * outlive a refused advance.
14
+ * - `retryParked` is one transaction that takes the feed lock exclusively before it
15
+ * re-stamps the row, so its new `seq` cannot commit out of order with the
16
+ * materialisation's own writes (`internal/statements.ts` owns that argument).
17
+ * - `collectTombstones` is one statement that reads every registered sink's cursor
18
+ * and deletes below the lowest, or nothing while any of them has no cursor, so
19
+ * no cursor can move between the two.
20
+ * - `removeSink` is one transaction that first takes the sink's advisory lock for
21
+ * its own length, which an advisory worker's session lock refuses, then deletes
22
+ * the cursor row only when no live lease row holds it, in the same statement as
23
+ * that test, and the parks after it. Both tests are therefore made whichever
24
+ * lease kind this store was built with: a sink can be leased by either kind
25
+ * (`lease.ts` owns how the two coexist), and the row alone cannot say whether
26
+ * an advisory holder's session is alive.
27
+ * - `describe` is three reads, each its own snapshot, so a figure in it can be a
28
+ * batch older or newer than the next. It is a report for an operator, and no
29
+ * decision is taken on it.
30
+ *
31
+ * One store serves every feed in its prefix: unlike the in-memory feed, whose
32
+ * store belongs to one feed, a SQL store's state is keyed by feed name in shared
33
+ * tables, so a single store value is what every worker over the prefix takes.
34
+ *
35
+ * Infrastructure faults are `FeedUnreadable` on a read and `FeedWriteFailed` on a
36
+ * write, never defects, so the driver's supervision backs off and tries again.
37
+ */
38
+ import { CursorSuperseded, FeedKey, FeedSeq, Generation, LeaseHeld, LeaseHolder, SinkName } from '@kairos-es/feed';
39
+ import { Clock, DateTime, Effect, Option } from 'effect';
40
+ import { SqlClient } from 'effect/sql';
41
+ import { bigintOf, feedContext, unreadable, writeFailed } from "./internal/context.js";
42
+ import { advisoryLockName, leaseRow, makeAcquireLease } from "./lease.js";
43
+ /** A timestamp column read back as epoch milliseconds, or `None` when NULL. */
44
+ const instantOf = ms => ms === null ? Option.none() : Option.some(DateTime.makeUnsafe(Number(ms)));
45
+ const cursorOf = row => row === undefined || row.generation === null || row.seq === null ? Option.none() : Option.some({
46
+ generation: Generation.make(bigintOf(row.generation)),
47
+ seq: FeedSeq.make(bigintOf(row.seq))
48
+ });
49
+ const parkedOf = row => ({
50
+ key: FeedKey.make(row.key),
51
+ seq: FeedSeq.make(bigintOf(row.seq)),
52
+ reason: row.reason,
53
+ parkedAt: DateTime.makeUnsafe(Number(row.parked_at_ms))
54
+ });
55
+ /** Park entries as the `jsonb` parameter the park statement takes; last per key wins. */
56
+ const parkJson = park => {
57
+ const byKey = new Map();
58
+ for (const record of park) {
59
+ byKey.delete(record.key);
60
+ byKey.set(record.key, record);
61
+ }
62
+ return JSON.stringify(Array.from(byKey.values(), record => ({
63
+ key: record.key,
64
+ seq: record.seq.toString(),
65
+ reason: record.reason,
66
+ parked_at_ms: String(DateTime.toEpochMillis(record.parkedAt))
67
+ })));
68
+ };
69
+ /**
70
+ * Build the sync store over the ambient `SqlClient`. Dies at construction on a
71
+ * malformed config, a lease whose heartbeat is not shorter than its ttl or leaves
72
+ * no room for its margin, or an `advisoryLock` connection recognisably pooled.
73
+ * `ensureFeedSchema` must have run.
74
+ */
75
+ export const makePostgresFeedSyncStore = (options = {}) => Effect.gen(function* () {
76
+ const sql = (yield* SqlClient.SqlClient).withoutTransforms();
77
+ const context = feedContext(options.config ?? {});
78
+ const acquireLease = makeAcquireLease(options.lease ?? leaseRow(), sql, context);
79
+ const {
80
+ statements
81
+ } = context;
82
+ const readCursor = id => sql.unsafe(statements.selectCursor, [id.feed, id.sink]).pipe(Effect.mapError(unreadable(`reading the cursor of sink '${id.sink}'`)), Effect.map(rows => cursorOf(rows[0])));
83
+ const casCursor = args => sql.withTransaction(Effect.gen(function* () {
84
+ const expected = Option.getOrUndefined(args.expected);
85
+ const now = yield* DateTime.now;
86
+ 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(DateTime.toEpochMillis(now))]);
87
+ if (landed.length === 0) {
88
+ return yield* new CursorSuperseded({
89
+ feed: args.feed,
90
+ sink: args.sink
91
+ });
92
+ }
93
+ const change = args.parked;
94
+ if (change._tag === 'Replace') {
95
+ yield* sql.unsafe(statements.clearParked, [args.feed, args.sink]);
96
+ } else if (change.unpark.length > 0) {
97
+ yield* sql.unsafe(statements.unparkKeys, [args.feed, args.sink, JSON.stringify(change.unpark)]);
98
+ }
99
+ if (change.park.length > 0) {
100
+ yield* sql.unsafe(statements.parkKeys, [args.feed, args.sink, parkJson(change.park)]);
101
+ }
102
+ })).pipe(Effect.catchTag('SqlError', cause => Effect.fail(writeFailed(`moving the cursor of sink '${args.sink}'`)(cause))));
103
+ const readParked = id => sql.unsafe(statements.selectParked, [id.feed, id.sink]).pipe(Effect.mapError(unreadable(`reading the parked records of sink '${id.sink}'`)), Effect.map(rows => rows.map(parkedOf)));
104
+ const retryParked = args => sql.withTransaction(Effect.gen(function* () {
105
+ yield* sql.unsafe(statements.lockFeedExclusive, [args.feed]);
106
+ const unparked = yield* sql.unsafe(statements.unparkOne, [args.feed, args.sink, args.key]);
107
+ if (unparked.length === 0) {
108
+ return false;
109
+ }
110
+ yield* sql.unsafe(statements.restampRow, [args.feed, args.key]);
111
+ return true;
112
+ })).pipe(Effect.mapError(writeFailed(`retrying parked record '${args.key}' of sink '${args.sink}'`)));
113
+ const collectTombstones = feed => sql.unsafe(statements.collectTombstones, [feed]).pipe(Effect.mapError(writeFailed(`collecting the tombstones of feed '${feed}'`)), Effect.map(rows => rows.length));
114
+ const removeSink = id => sql.withTransaction(Effect.gen(function* () {
115
+ const lock = yield* sql.unsafe(statements.tryAdvisoryXactLock, [advisoryLockName(context, id)]);
116
+ if (lock[0]?.taken !== true) {
117
+ return yield* new LeaseHeld(id);
118
+ }
119
+ const now = yield* Clock.currentTimeMillis;
120
+ const cursor = yield* sql.unsafe(statements.deleteCursorUnlessLeased, [id.feed, id.sink, String(now)]);
121
+ if (cursor.length === 0) {
122
+ const left = yield* sql.unsafe(statements.cursorExists, [id.feed, id.sink]);
123
+ if (left.length > 0) {
124
+ return yield* new LeaseHeld(id);
125
+ }
126
+ }
127
+ const parked = yield* sql.unsafe(statements.deleteSinkParked, [id.feed, id.sink]);
128
+ return cursor.length > 0 || parked.length > 0;
129
+ })).pipe(Effect.catchTag('SqlError', cause => Effect.fail(writeFailed(`removing sink '${id.sink}'`)(cause))));
130
+ const describe = feed => Effect.gen(function* () {
131
+ const head = yield* sql.unsafe(statements.selectHead, [feed]);
132
+ const sinks = yield* sql.unsafe(statements.describeSinks, [feed]);
133
+ const parked = yield* sql.unsafe(statements.describeParked, [feed]);
134
+ const parkedBySink = new Map();
135
+ for (const record of parked) {
136
+ const list = parkedBySink.get(record.sink);
137
+ if (list === undefined) {
138
+ parkedBySink.set(record.sink, [parkedOf(record)]);
139
+ } else {
140
+ list.push(parkedOf(record));
141
+ }
142
+ }
143
+ const description = {
144
+ head: FeedSeq.make(bigintOf(head[0]?.head ?? '0')),
145
+ sinks: sinks.map(row => ({
146
+ sink: SinkName.make(row.sink),
147
+ cursor: cursorOf(row),
148
+ lastAdvance: instantOf(row.last_advance_at_ms),
149
+ lease: row.lease_holder === null ? Option.none() : Option.some({
150
+ holder: LeaseHolder.make(row.lease_holder),
151
+ expiresAt: instantOf(row.lease_expires_at_ms)
152
+ }),
153
+ parked: parkedBySink.get(row.sink) ?? [],
154
+ pending: Number(row.pending)
155
+ }))
156
+ };
157
+ return description;
158
+ }).pipe(Effect.mapError(unreadable(`describing feed '${feed}'`)));
159
+ const store = {
160
+ acquireLease,
161
+ readCursor,
162
+ casCursor,
163
+ readParked,
164
+ retryParked,
165
+ collectTombstones,
166
+ removeSink,
167
+ describe
168
+ };
169
+ return store;
170
+ });
171
+ //# sourceMappingURL=syncStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syncStore.js","names":["CursorSuperseded","FeedKey","FeedSeq","Generation","LeaseHeld","LeaseHolder","SinkName","Clock","DateTime","Effect","Option","SqlClient","bigintOf","feedContext","unreadable","writeFailed","advisoryLockName","leaseRow","makeAcquireLease","instantOf","ms","none","some","makeUnsafe","Number","cursorOf","row","undefined","generation","seq","make","parkedOf","key","reason","parkedAt","parked_at_ms","parkJson","park","byKey","Map","record","delete","set","JSON","stringify","Array","from","values","toString","String","toEpochMillis","makePostgresFeedSyncStore","options","gen","sql","withoutTransforms","context","config","acquireLease","lease","statements","readCursor","id","unsafe","selectCursor","feed","sink","pipe","mapError","map","rows","casCursor","args","withTransaction","expected","getOrUndefined","now","landed","holder","next","length","change","parked","_tag","clearParked","unpark","unparkKeys","parkKeys","catchTag","cause","fail","readParked","selectParked","retryParked","lockFeedExclusive","unparked","unparkOne","restampRow","collectTombstones","removeSink","lock","tryAdvisoryXactLock","taken","currentTimeMillis","cursor","deleteCursorUnlessLeased","left","cursorExists","deleteSinkParked","describe","head","selectHead","sinks","describeSinks","describeParked","parkedBySink","list","get","push","description","lastAdvance","last_advance_at_ms","lease_holder","expiresAt","lease_expires_at_ms","pending","store"],"sources":["../../src/syncStore.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,44 @@
1
+ import { ProjectionStoreError } from '@kairos-es/read';
2
+ import { Effect } from 'effect';
3
+ import { SqlClient } from 'effect/sql';
4
+ import { contextOf } from "./feed.js";
5
+ import { writeFailed } from "./internal/context.js";
6
+ const sameKey = (left, right) => left.projection === right.projection && left.partition === right.partition;
7
+ /**
8
+ * `store`, with `resetCheckpoint` for `key` also emptying `feed` and moving its
9
+ * generation on; see the module doc. Every other key passes through untouched.
10
+ */
11
+ export const withFeed = (store, feed, key) => Effect.gen(function* () {
12
+ const context = contextOf(feed);
13
+ if (context === undefined) {
14
+ return yield* Effect.die(new Error(`withFeed: feed '${feed.feed}' was not made by makePostgresFeed`));
15
+ }
16
+ if (store.durability !== 'durable') {
17
+ return yield* Effect.die(new Error(`withFeed: feed '${feed.feed}' is written inside the store's commit transaction, and an ephemeral store opens none`));
18
+ }
19
+ const {
20
+ statements
21
+ } = context;
22
+ const sql = (yield* SqlClient.SqlClient).withoutTransforms();
23
+ const bound = yield* sql.unsafe(statements.bindFeed, [feed.feed, key.projection, key.partition]).pipe(Effect.mapError(writeFailed(`binding feed '${feed.feed}'`)));
24
+ const binding = bound[0];
25
+ if (binding !== undefined && (binding.projection !== key.projection || binding.partition !== key.partition)) {
26
+ return yield* Effect.die(new Error(`withFeed: feed '${feed.feed}' is already bound to projection '${binding.projection}' (partition '${binding.partition}'), and one feed serves one materialisation`));
27
+ }
28
+ const resetBound = reset => sql.withTransaction(Effect.gen(function* () {
29
+ yield* sql.unsafe(statements.bumpGeneration, [feed.feed]);
30
+ yield* store.resetCheckpoint(reset);
31
+ yield* sql.unsafe(statements.deleteFeedRows, [feed.feed]);
32
+ })).pipe(Effect.catchTag('SqlError', cause => Effect.fail(new ProjectionStoreError({
33
+ reason: `resetting projection '${reset.projection}' partition '${reset.partition}' and its feed '${feed.feed}' failed: ${cause.message}`,
34
+ cause
35
+ }))));
36
+ const decorated = {
37
+ durability: store.durability,
38
+ readCheckpoint: store.readCheckpoint,
39
+ commit: store.commit,
40
+ resetCheckpoint: reset => sameKey(reset, key) ? resetBound(reset) : store.resetCheckpoint(reset)
41
+ };
42
+ return decorated;
43
+ });
44
+ //# sourceMappingURL=withFeed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withFeed.js","names":["ProjectionStoreError","Effect","SqlClient","contextOf","writeFailed","sameKey","left","right","projection","partition","withFeed","store","feed","key","gen","context","undefined","die","Error","durability","statements","sql","withoutTransforms","bound","unsafe","bindFeed","pipe","mapError","binding","resetBound","reset","withTransaction","bumpGeneration","resetCheckpoint","deleteFeedRows","catchTag","cause","fail","reason","message","decorated","readCheckpoint","commit"],"sources":["../../src/withFeed.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}