@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
package/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Neverbland
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md CHANGED
@@ -1,3 +1,85 @@
1
- # Temporary Holding Version
1
+ # @kairos-es/feed-postgres
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The Postgres change feed for kairos-es: the feed tables and an idempotent `ensureFeedSchema` for your own migrator, the `FeedWriter` that writes inside a read model's own commit, the `FeedSource` a sync worker tails, the `FeedSyncStore` with its worker lease, and `withFeed`, the store decorator that moves the feed's generation on a reset (ADR-0019).
4
+
5
+ A feed row exists if and only if the view write and the checkpoint advance that produced it landed. That is the whole reason to write the feed in Postgres beside the view: a search index or cache downstream then receives every change in commit order, with none lost to a crash between the view and the sink. A crash can make the worker send a batch twice, never skip one.
6
+
7
+ ## Installation
8
+
9
+ `@kairos-es/core`, `@kairos-es/read`, `@kairos-es/feed` and `effect` are peer dependencies. `@kairos-es/codec` is on the line because `@kairos-es/read` peers it:
10
+
11
+ ```sh
12
+ pnpm add @kairos-es/feed-postgres@alpha @kairos-es/feed@alpha @kairos-es/read@alpha \
13
+ @kairos-es/codec@alpha @kairos-es/core@alpha effect@^4.0.5
14
+ ```
15
+
16
+ The package needs only the generic `SqlClient` from `effect/sql`, so the driver stays your choice. You will normally use it beside `@kairos-es/read-postgres`, whose `ProjectionStore` opens the transaction the feed writes join.
17
+
18
+ ## Schema
19
+
20
+ `ensureFeedSchema(config)` is an idempotent `Effect<void, SqlError, SqlClient>`; every statement is `IF NOT EXISTS`. Register it in your migrator beside the event store's `ensureSchema` and the read side's `ensureCheckpointSchema`, with the same `{ schema, tablePrefix }` pair:
21
+
22
+ ```ts
23
+ const migrations = Migrator.fromRecord({
24
+ '0001_kairos_store_dcb_events': ensureSchema(config),
25
+ '0002_kairos_checkpoints': ensureCheckpointSchema(config),
26
+ '0003_kairos_feed': ensureFeedSchema(config),
27
+ })
28
+ ```
29
+
30
+ It creates `<prefix>_feed_rows` (one row per feed and key, the newest document or a tombstone), the `<prefix>_feed_seq` sequence every feed in the prefix shares, `<prefix>_feed_generations`, `<prefix>_feed_cursors` (one cursor and lease per feed and sink) and `<prefix>_feed_parked`.
31
+
32
+ ## Wiring
33
+
34
+ Build the projection store, the feed and `withFeed` under ONE `SqlClient`. The feed's writes, and `withFeed`'s reset, join the store's transaction only through the same client instance; a feed written through any other client is not rolled back with the view.
35
+
36
+ ```ts
37
+ const config = { schema: 'public', tablePrefix: 'dcb_events' }
38
+ const key = checkpointKey(ProjectionId.make('course-search'))
39
+
40
+ const feed = yield* makePostgresFeed({
41
+ config,
42
+ feed: FeedName.make('course-search'),
43
+ doc: CourseDoc,
44
+ })
45
+ const store = yield* withFeed(yield* makeSqlProjectionStore(config), feed, key)
46
+
47
+ // In the read model's apply, inside the commit:
48
+ const apply = (batch) =>
49
+ feed.write(Arr.lastNonEmpty(batch).sequenced.position, (writer) =>
50
+ Effect.forEach(touchedCourses(batch), (course) =>
51
+ writer.upsert(FeedKey.make(course.id), toDoc(course)),
52
+ ),
53
+ )
54
+
55
+ // Beside the projections, one worker per feed and sink:
56
+ const syncStore = yield* makePostgresFeedSyncStore({ config })
57
+ yield* Effect.forkScoped(runFeedSync({ source: feed.source, sink, store: syncStore }))
58
+ ```
59
+
60
+ Things to know:
61
+
62
+ - **One feed per materialisation.** A feed is bound to one checkpoint key the first time `withFeed` decorates a store for it, and that binding is stored in the database: decorating it for a second key dies at construction, in any process.
63
+ - **A reset rebuilds the sinks, not your view.** `resetCheckpoint` on the decorated store deletes the checkpoint, deletes the feed's rows and moves its generation on, in one transaction. Every sink then resyncs from what the replay writes. Your view's own rows are not touched, exactly as a plain `resetCheckpoint` leaves them, so rebuilding a durable view means emptying it yourself before the replay. Otherwise a row the replay never writes again stays in the view, and an `apply` that reads the view back builds on the old rows and the feed sends that to every sink.
64
+ - **Unchanged documents cost nothing.** An upsert of a document equal to the stored one (compared as `jsonb`, so key order does not matter) writes nothing, and the sink is not sent it.
65
+ - **A document that fails to encode through its `Schema` is a defect**, not a sink rejection.
66
+ - **The wake is optional.** The worker polls. `eventStoreWake(query)` gives it the event store's `subscribe` as a wake; it fires when an event is appended, before the projection has written the feed, so it shortens the wait and never replaces the poll.
67
+
68
+ ## The worker lease
69
+
70
+ One sync worker per feed and sink holds a lease, because a sink has no transaction and two workers could send out of order. Choose the kind in `makePostgresFeedSyncStore({ config, lease })`:
71
+
72
+ - **`leaseRow({ ttl, heartbeat, margin })`, the default** (30 seconds, renewed every 6, margin a third of `ttl`). Ordinary short statements, so it works through a transaction pooler and needs no reserved connection. The worker stops sending `margin` before its lease could lapse, so a call to the sink that finishes within `margin` of the check before it lands before any other worker can take the lease, even while its renewals are failing. A call that outlasts the margin, because it is slow or because its worker pauses (a long GC pause, a network stall), can land after another worker took over: one stale call, since the next check fails. In the steady state that is one batch; during a rebuild possibly the swap of its own rebuilt copy, which can put back an older copy than the new holder had already served, until those records change again or the next rebuild. So set `margin` above your longest sink call; `heartbeat + margin` must be under `ttl`, which is checked at construction. Each renewal is abandoned at the heartbeat. To keep sending, without a pause, through one renewal that fails or hangs, keep `3 × heartbeat + margin` within `ttl`, as the defaults do; a tighter setting is accepted, and then one lost renewal can stop the worker briefly and restart its session. Expiry is judged by each worker's clock, so keep your hosts' clocks within a small fraction of `ttl` of each other.
73
+ - **`advisoryLock({ connection })`.** A session advisory lock on a connection reserved from a client you dedicate to it. The lock goes only when the session goes, and the worker asks that session before every call that changes the sink, so a long process pause cannot let a stale call through. What it cannot catch is the session dying after that check: the call it let through runs to its end, so it can land as late as its whole duration after the session died, after another worker has taken over and sent. That is still one stale call, but a sink call can be long, a rebuild's swap most of all. The client must be a DIRECT connection: a transaction pooler would hand the session to someone else between statements. A client this package can recognise as a pooler (a `-pooler` host, port 6432, or `pgbouncer=true`) is refused at construction; any other pooler is not detected, so do not give it one.
74
+
75
+ Either way the cursor's compare-and-set checks the holder, so a worker that has lost its lease cannot move the cursor. The two kinds can meet on one sink, for example during a switch from one to the other, and neither takes it from a live holder of the other. An advisory worker that crashed without closing its scope leaves its name on the sink; a `leaseRow` worker takes the sink over once that worker's session, and so its lock, is gone.
76
+
77
+ If your driver is `@effect/sql-pg` 4 behind a transaction-mode pooler, build the main client with `prepare: false`, as `@kairos-es/read-postgres`'s README explains.
78
+
79
+ ## Operating a feed
80
+
81
+ `inspectFeed(feed.source, syncStore)` and `retryParked(syncStore, { feed, sink, key })` from `@kairos-es/feed` read and repair everything here without the sink's credentials: each sink's generation, cursor, when the cursor last moved (`lastAdvance`), lag, lease and parked records, and sending one parked record again.
82
+
83
+ `lastAdvance` is stamped by the worker's own clock, not the database's, like the lease expiry. A sink whose `lastAdvance` stays still while the feed's head moves is a sink nothing runs, and it holds tombstone collection back for the whole feed: tombstones are kept until every sink with a cursor row has passed them.
84
+
85
+ `removeSink(syncStore, { feed, sink })` retires such a sink: it deletes its cursor row and its parked records in one transaction, so collection can go on without it. It fails `LeaseHeld` and deletes nothing while a worker holds the sink's lease, of either kind: a lease row whose expiry has not passed, or an advisory lock held by a live session. Stop the worker first. A worker started for the sink later simply registers it again and resyncs.
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.decodeFeedPostgresConfig = exports.FeedPostgresConfig = void 0;
7
+ var _effect = require("effect");
8
+ var _ddl = require("./internal/ddl.js");
9
+ /**
10
+ * Public configuration for the Postgres change feed.
11
+ *
12
+ * A feed backend is defined by its `{ schema, tablePrefix }` naming pair alone:
13
+ * every object it owns is DERIVED from it (`internal/ddl.ts`), so one config
14
+ * value is one isolated set of feed tables, cohabiting per prefix beside the
15
+ * event store's objects and `@kairos-es/read-postgres`'s checkpoint table. Pass
16
+ * the same pair to all three and one prefix names one complete world: the log,
17
+ * the checkpoints, and the feeds the read models write.
18
+ *
19
+ * It is a `Schema` rather than a bare interface so that a malformed value fails
20
+ * LOUDLY at construction rather than obscurely at the first statement: an empty or
21
+ * dotted atom (a dot would break the qualified-name split), or a pair whose
22
+ * derived names would exceed Postgres's 63-byte identifier limit and be silently
23
+ * truncated. The shape and its checks are `@kairos-es/read-postgres`'s, written
24
+ * again here rather than imported, because each backend measures only the names it
25
+ * creates and a sibling backend's internals are not a public surface.
26
+ */
27
+
28
+ /** A single identifier atom: non-empty and dot-free. */
29
+ const IdentifierAtom = /*#__PURE__*/_effect.Schema.String.check(/*#__PURE__*/_effect.Schema.isNonEmpty(), /*#__PURE__*/_effect.Schema.isPattern(/^[^.]+$/, {
30
+ identifier: 'PostgresIdentifierAtom',
31
+ description: 'a non-empty identifier atom containing no "."'
32
+ }));
33
+ /**
34
+ * Configuration for the feed's tables.
35
+ *
36
+ * - `schema` — the Postgres schema that owns them (default `public`).
37
+ * - `tablePrefix` — the stem every name derives from (default `dcb_events`,
38
+ * giving `dcb_events_feed_rows` and its siblings).
39
+ */
40
+ const FeedPostgresConfig = exports.FeedPostgresConfig = /*#__PURE__*/_effect.Schema.Struct({
41
+ schema: _effect.Schema.optionalKey(IdentifierAtom),
42
+ tablePrefix: _effect.Schema.optionalKey(IdentifierAtom)
43
+ }).check(/*#__PURE__*/_effect.Schema.makeFilter(config => (0, _ddl.oversizedIdentifier)(config)));
44
+ /** Decode a config, throwing a `SchemaError` on any violation. */
45
+ const decodeFeedPostgresConfig = exports.decodeFeedPostgresConfig = /*#__PURE__*/_effect.Schema.decodeUnknownSync(FeedPostgresConfig);
46
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","names":["_effect","require","_ddl","IdentifierAtom","Schema","String","check","isNonEmpty","isPattern","identifier","description","FeedPostgresConfig","exports","Struct","schema","optionalKey","tablePrefix","makeFilter","config","oversizedIdentifier","decodeFeedPostgresConfig","decodeUnknownSync"],"sources":["../../src/config.ts"],"sourcesContent":[null],"mappings":";;;;;;AAkBA,IAAAA,OAAA,GAAAC,OAAA;AACA,IAAAC,IAAA,GAAAD,OAAA;AAnBA;;;;;;;;;;;;;;;;;;;AAqBA;AACA,MAAME,cAAc,gBAAGC,cAAM,CAACC,MAAM,CAACC,KAAK,cACxCF,cAAM,CAACG,UAAU,EAAE,eACnBH,cAAM,CAACI,SAAS,CAAC,SAAS,EAAE;EAC1BC,UAAU,EAAE,wBAAwB;EACpCC,WAAW,EAAE;CACd,CAAC,CACH;AAED;;;;;;;AAOO,MAAMC,kBAAkB,GAAAC,OAAA,CAAAD,kBAAA,gBAAGP,cAAM,CAACS,MAAM,CAAC;EAC9CC,MAAM,EAAEV,cAAM,CAACW,WAAW,CAACZ,cAAc,CAAC;EAC1Ca,WAAW,EAAEZ,cAAM,CAACW,WAAW,CAACZ,cAAc;CAC/C,CAAC,CAACG,KAAK,cAACF,cAAM,CAACa,UAAU,CAAEC,MAAM,IAAK,IAAAC,wBAAmB,EAACD,MAAM,CAAC,CAAC,CAAC;AAGpE;AACO,MAAME,wBAAwB,GAAAR,OAAA,CAAAQ,wBAAA,gBACnChB,cAAM,CAACiB,iBAAiB,CAACV,kBAAkB,CAAC","ignoreList":[]}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ensureFeedSchema = void 0;
7
+ var _effect = require("effect");
8
+ var _sql = require("effect/sql");
9
+ var _config = require("./config.js");
10
+ var _ddl = require("./internal/ddl.js");
11
+ /**
12
+ * `ensureFeedSchema` — the feed's idempotent DDL, as a payload for the consumer's
13
+ * migrator rather than a migrator of its own, exactly as `ensureCheckpointSchema`
14
+ * is for the read side and `ensureSchema` for the event store.
15
+ *
16
+ * It requires the GENERIC `SqlClient`, so it drops into an existing migrator:
17
+ *
18
+ * ```ts
19
+ * Migrator.fromRecord({
20
+ * '0003_kairos_feed': ensureFeedSchema(config),
21
+ * })
22
+ * ```
23
+ *
24
+ * or runs once at boot. Every statement is `IF NOT EXISTS`, so running it again
25
+ * against existing tables keeps every row. Nothing in this package performs DDL at
26
+ * runtime: a feed cannot conjure its own tables, so schema ownership stays with
27
+ * whoever owns the migrations.
28
+ */
29
+
30
+ /** The feed tables' migration payload for `config`. */
31
+ const ensureFeedSchema = (config = {}) => _effect.Effect.gen(function* () {
32
+ const sql = yield* _sql.SqlClient.SqlClient;
33
+ const names = (0, _ddl.resolveFeedNames)((0, _config.decodeFeedPostgresConfig)(config));
34
+ yield* _effect.Effect.all((0, _ddl.runFeedDdl)(sql, names), {
35
+ discard: true
36
+ });
37
+ });
38
+ exports.ensureFeedSchema = ensureFeedSchema;
39
+ //# sourceMappingURL=ensureFeedSchema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensureFeedSchema.js","names":["_effect","require","_sql","_config","_ddl","ensureFeedSchema","config","Effect","gen","sql","SqlClient","names","resolveFeedNames","decodeFeedPostgresConfig","all","runFeedDdl","discard","exports"],"sources":["../../src/ensureFeedSchema.ts"],"sourcesContent":[null],"mappings":";;;;;;AAkBA,IAAAA,OAAA,GAAAC,OAAA;AACA,IAAAC,IAAA,GAAAD,OAAA;AACA,IAAAE,OAAA,GAAAF,OAAA;AACA,IAAAG,IAAA,GAAAH,OAAA;AArBA;;;;;;;;;;;;;;;;;;;AAuBA;AACO,MAAMI,gBAAgB,GAAGA,CAC9BC,MAAA,GAA6B,EAAE,KAE/BC,cAAM,CAACC,GAAG,CAAC,aAAS;EAClB,MAAMC,GAAG,GAAG,OAAOC,cAAS,CAACA,SAAS;EACtC,MAAMC,KAAK,GAAG,IAAAC,qBAAgB,EAAC,IAAAC,gCAAwB,EAACP,MAAM,CAAC,CAAC;EAChE,OAAOC,cAAM,CAACO,GAAG,CAAC,IAAAC,eAAU,EAACN,GAAG,EAAEE,KAAK,CAAC,EAAE;IAAEK,OAAO,EAAE;EAAI,CAAE,CAAC;AAC9D,CAAC,CAAC;AAAAC,OAAA,CAAAZ,gBAAA,GAAAA,gBAAA","ignoreList":[]}
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.makePostgresFeed = exports.eventStoreWake = exports.contextOf = void 0;
7
+ var _core = require("@kairos-es/core");
8
+ var _feed = require("@kairos-es/feed");
9
+ var _effect = require("effect");
10
+ var _sql = require("effect/sql");
11
+ var _context = require("./internal/context.js");
12
+ /**
13
+ * The Postgres feed's two faces: the `BatchedFeedWriter` a read model's `apply`
14
+ * writes through, and the `FeedSource` a sync worker tails.
15
+ *
16
+ * ## The writer rides the read model's own client
17
+ *
18
+ * A feed row must exist if and only if the view write and the checkpoint advance
19
+ * that produced it landed (ADR-0019 §2). `@kairos-es/read-postgres`'s `commit` is
20
+ * one transaction over the client the store was built from, and `effect/sql`
21
+ * carries that transaction in the Effect CONTEXT under a key that belongs to that
22
+ * one client instance (ADR-0018 §3). So the writer does not capture a client: it
23
+ * reads `SqlClient` from the context AT WRITE TIME, which inside `commit`'s
24
+ * `viewWrites` is the read model's own, and its statements join the open
25
+ * transaction and roll back with it on a lost guard. That is why `SqlClient` sits
26
+ * in the writer's `RW` and not in the factory's requirements alone. A write
27
+ * through any OTHER client instance is not bracketed, and the exactly-once
28
+ * property does not extend to it.
29
+ *
30
+ * ## What one `write` sends
31
+ *
32
+ * The body's upserts and removes are buffered, last write per key winning, and
33
+ * land as at most three statements whatever the batch size: the feed lock (see
34
+ * `internal/statements.ts` for why the writer takes it), one upsert of every
35
+ * changed document and one tombstone of every removed key. The upsert writes
36
+ * nothing for a document equal to the stored one, compared as `jsonb`, so key
37
+ * order is no change; the tombstone writes nothing for a key that is absent or
38
+ * already a tombstone.
39
+ *
40
+ * Each document is encoded through its `Schema` before anything is sent. A
41
+ * document that fails to encode is a DEFECT in the read model, not a fault of the
42
+ * feed and not something a sink should ever be asked to refuse.
43
+ *
44
+ * ## The source
45
+ *
46
+ * `changesAfter` pages the feed's rows by `seq`, tombstones included, and decodes
47
+ * each document through the same `Schema`. A stored document that no longer
48
+ * decodes is a defect too: it means the schema changed without the feed being
49
+ * rebuilt, which retrying cannot mend.
50
+ *
51
+ * `listen` is optional and is a WAKE only. This package takes no reserved
52
+ * connection for a `LISTEN` of its own, as `@kairos-es/read-postgres` takes none,
53
+ * so the poll is the guarantee. {@link eventStoreWake} offers the event store's
54
+ * `subscribe` as a wake: it fires when an event is appended, which is BEFORE the
55
+ * projection has applied it and written the feed, so a woken read may find nothing
56
+ * new and the poll picks the row up. It shortens the wait; it never decides
57
+ * correctness.
58
+ */
59
+
60
+ /** The context a feed was built over, for `withFeed`. */
61
+ const contexts = /*#__PURE__*/new WeakMap();
62
+ /** @internal */
63
+ const contextOf = feed => contexts.get(feed);
64
+ /**
65
+ * Build a feed over the ambient `SqlClient`. Creates the feed's generations row
66
+ * if it is absent, at generation 0, so the writer's feed lock always has a row to
67
+ * take; that is the only write it makes. `ensureFeedSchema` must have run first.
68
+ */
69
+ exports.contextOf = contextOf;
70
+ const makePostgresFeed = options => _effect.Effect.gen(function* () {
71
+ const context = (0, _context.feedContext)(options.config ?? {});
72
+ const {
73
+ statements
74
+ } = context;
75
+ const feed = options.feed;
76
+ const sql = (yield* _sql.SqlClient.SqlClient).withoutTransforms();
77
+ const encode = _effect.Schema.encodeEffect(options.doc);
78
+ const decode = _effect.Schema.decodeUnknownEffect(options.doc);
79
+ yield* sql.unsafe(statements.ensureGeneration, [feed]).pipe(_effect.Effect.mapError((0, _context.writeFailed)(`creating the generation of feed '${feed}'`)));
80
+ const write = (at, body) => _effect.Effect.gen(function* () {
81
+ const buffer = new Map();
82
+ const set = (key, doc) => _effect.Effect.sync(() => {
83
+ buffer.delete(key);
84
+ buffer.set(key, doc);
85
+ });
86
+ const result = yield* body({
87
+ upsert: (key, doc) => set(key, _effect.Option.some(doc)),
88
+ remove: key => set(key, _effect.Option.none())
89
+ });
90
+ if (buffer.size === 0) {
91
+ return result;
92
+ }
93
+ const upserts = [];
94
+ const removes = [];
95
+ for (const [key, doc] of buffer) {
96
+ if (_effect.Option.isSome(doc)) {
97
+ upserts.push({
98
+ key,
99
+ doc: yield* _effect.Effect.orDie(encode(doc.value))
100
+ });
101
+ } else {
102
+ removes.push(key);
103
+ }
104
+ }
105
+ const upsertJson = upserts.length === 0 ? undefined : yield* _effect.Effect.try({
106
+ try: () => JSON.stringify(upserts),
107
+ catch: cause => new Error(`feed '${feed}': an encoded document is not JSON-serialisable`, {
108
+ cause
109
+ })
110
+ }).pipe(_effect.Effect.orDie);
111
+ // The read model's own client, from the context at write time; see the
112
+ // module doc. Transform-free, for the same reason the read side's store
113
+ // is: the copy keeps the original's transaction key.
114
+ const client = (yield* _sql.SqlClient.SqlClient).withoutTransforms();
115
+ const position = at.toString();
116
+ yield* client.withTransaction(_effect.Effect.gen(function* () {
117
+ yield* client.unsafe(statements.lockFeedShared, [feed]);
118
+ if (upsertJson !== undefined) {
119
+ yield* client.unsafe(statements.upsertRows, [feed, position, upsertJson]);
120
+ }
121
+ if (removes.length > 0) {
122
+ yield* client.unsafe(statements.tombstoneRows, [feed, position, JSON.stringify(removes)]);
123
+ }
124
+ })).pipe(_effect.Effect.mapError((0, _context.writeFailed)(`writing feed '${feed}'`)));
125
+ return result;
126
+ });
127
+ const decodeRow = row => _effect.Effect.gen(function* () {
128
+ // Bound to a `const` so the `null` check narrows inside the `try` below.
129
+ const stored = row.doc;
130
+ const doc = stored === null ? _effect.Option.none() : _effect.Option.some(yield* _effect.Effect.orDie(_effect.Effect.flatMap(_effect.Effect.try({
131
+ try: () => JSON.parse(stored),
132
+ catch: cause => new Error(`feed '${feed}': a stored document is not JSON`, {
133
+ cause
134
+ })
135
+ }), json => decode(json))));
136
+ return {
137
+ key: _feed.FeedKey.make(row.key),
138
+ doc,
139
+ seq: _feed.FeedSeq.make((0, _context.bigintOf)(row.seq)),
140
+ eventPosition: _core.Position.make((0, _context.bigintOf)(row.event_position))
141
+ };
142
+ });
143
+ const source = {
144
+ feed,
145
+ generation: sql.unsafe(statements.selectGeneration, [feed]).pipe(_effect.Effect.mapError((0, _context.unreadable)(`reading the generation of feed '${feed}'`)), _effect.Effect.map(rows => {
146
+ const stored = rows[0]?.generation;
147
+ return _feed.Generation.make(stored === undefined ? 0n : (0, _context.bigintOf)(stored));
148
+ })),
149
+ changesAfter: (after, limit) => sql.unsafe(statements.changesAfter, [feed, after.toString(), limit]).pipe(_effect.Effect.mapError((0, _context.unreadable)(`reading feed '${feed}'`)), _effect.Effect.flatMap(rows => _effect.Effect.forEach(rows, decodeRow))),
150
+ ...(options.listen === undefined ? {} : {
151
+ listen: options.listen
152
+ })
153
+ };
154
+ const result = {
155
+ feed,
156
+ write,
157
+ source
158
+ };
159
+ contexts.set(result, context);
160
+ return result;
161
+ });
162
+ /**
163
+ * A wake from the event store: one element per event matching `query` appended
164
+ * after the moment the stream is subscribed, and nothing else. Pass it as
165
+ * `listen`. Early by the projection's own lag, as the module doc says, so the
166
+ * worker's poll still bounds the latency; `query` should be the read model's own,
167
+ * so events it never applies wake nothing.
168
+ */
169
+ exports.makePostgresFeed = makePostgresFeed;
170
+ const eventStoreWake = query => _effect.Effect.gen(function* () {
171
+ const store = yield* _core.DcbEventStore;
172
+ const wake = _effect.Stream.unwrap(_effect.Effect.map(store.read(query), response => store.subscribe(query, response.head))).pipe(_effect.Stream.scoped, _effect.Stream.as(undefined));
173
+ return wake;
174
+ });
175
+ exports.eventStoreWake = eventStoreWake;
176
+ //# sourceMappingURL=feed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feed.js","names":["_core","require","_feed","_effect","_sql","_context","contexts","WeakMap","contextOf","feed","get","exports","makePostgresFeed","options","Effect","gen","context","feedContext","config","statements","sql","SqlClient","withoutTransforms","encode","Schema","encodeEffect","doc","decode","decodeUnknownEffect","unsafe","ensureGeneration","pipe","mapError","writeFailed","write","at","body","buffer","Map","set","key","sync","delete","result","upsert","Option","some","remove","none","size","upserts","removes","isSome","push","orDie","value","upsertJson","length","undefined","try","JSON","stringify","catch","cause","Error","client","position","toString","withTransaction","lockFeedShared","upsertRows","tombstoneRows","decodeRow","row","stored","flatMap","parse","json","FeedKey","make","seq","FeedSeq","bigintOf","eventPosition","Position","event_position","source","generation","selectGeneration","unreadable","map","rows","Generation","changesAfter","after","limit","forEach","listen","eventStoreWake","query","store","DcbEventStore","wake","Stream","unwrap","read","response","subscribe","head","scoped","as"],"sources":["../../src/feed.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "DEFAULT_LEASE_HEARTBEAT", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _lease.DEFAULT_LEASE_HEARTBEAT;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "DEFAULT_LEASE_TTL", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _lease.DEFAULT_LEASE_TTL;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "FeedPostgresConfig", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _config.FeedPostgresConfig;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "advisoryLock", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _lease.advisoryLock;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "decodeFeedPostgresConfig", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _config.decodeFeedPostgresConfig;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "ensureFeedSchema", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _ensureFeedSchema.ensureFeedSchema;
40
+ }
41
+ });
42
+ Object.defineProperty(exports, "eventStoreWake", {
43
+ enumerable: true,
44
+ get: function () {
45
+ return _feed.eventStoreWake;
46
+ }
47
+ });
48
+ Object.defineProperty(exports, "leaseRow", {
49
+ enumerable: true,
50
+ get: function () {
51
+ return _lease.leaseRow;
52
+ }
53
+ });
54
+ Object.defineProperty(exports, "makePostgresFeed", {
55
+ enumerable: true,
56
+ get: function () {
57
+ return _feed.makePostgresFeed;
58
+ }
59
+ });
60
+ Object.defineProperty(exports, "makePostgresFeedSyncStore", {
61
+ enumerable: true,
62
+ get: function () {
63
+ return _syncStore.makePostgresFeedSyncStore;
64
+ }
65
+ });
66
+ Object.defineProperty(exports, "pooledConnectionFault", {
67
+ enumerable: true,
68
+ get: function () {
69
+ return _lease.pooledConnectionFault;
70
+ }
71
+ });
72
+ Object.defineProperty(exports, "withFeed", {
73
+ enumerable: true,
74
+ get: function () {
75
+ return _withFeed.withFeed;
76
+ }
77
+ });
78
+ var _config = require("./config.js");
79
+ var _ensureFeedSchema = require("./ensureFeedSchema.js");
80
+ var _feed = require("./feed.js");
81
+ var _lease = require("./lease.js");
82
+ var _syncStore = require("./syncStore.js");
83
+ var _withFeed = require("./withFeed.js");
84
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["_config","require","_ensureFeedSchema","_feed","_lease","_syncStore","_withFeed"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,IAAAA,OAAA,GAAAC,OAAA;AAIA,IAAAC,iBAAA,GAAAD,OAAA;AACA,IAAAE,KAAA,GAAAF,OAAA;AAMA,IAAAG,MAAA,GAAAH,OAAA;AAQA,IAAAI,UAAA,GAAAJ,OAAA;AAIA,IAAAK,SAAA,GAAAL,OAAA","ignoreList":[]}
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.writeFailed = exports.unreadable = exports.feedContext = exports.bigintOf = void 0;
7
+ var _feed = require("@kairos-es/feed");
8
+ var _config = require("../config.js");
9
+ var _ddl = require("./ddl.js");
10
+ var _statements = require("./statements.js");
11
+ /**
12
+ * What every part of this package shares about one config: the resolved names,
13
+ * the compiled statements, and the error mapping from `SqlError` to the feed's own
14
+ * two infrastructure errors.
15
+ *
16
+ * A `PostgresFeed` carries its context privately, in a `WeakMap` keyed by the
17
+ * feed value, so `withFeed` can reach the feed's names and statements without the
18
+ * public type growing a field no consumer should touch.
19
+ */
20
+
21
+ /** Decode `config` and compile its statements; throws on a malformed config. */
22
+ const feedContext = config => {
23
+ const names = (0, _ddl.resolveFeedNames)((0, _config.decodeFeedPostgresConfig)(config));
24
+ return {
25
+ names,
26
+ statements: (0, _statements.compileFeedStatements)(names)
27
+ };
28
+ };
29
+ /** A read that failed, named for what was being read. */
30
+ exports.feedContext = feedContext;
31
+ const unreadable = attempt => cause => new _feed.FeedUnreadable({
32
+ reason: `${attempt} failed: ${cause.message}`,
33
+ cause
34
+ });
35
+ /** A write that failed, named for what was being written. */
36
+ exports.unreadable = unreadable;
37
+ const writeFailed = attempt => cause => new _feed.FeedWriteFailed({
38
+ reason: `${attempt} failed: ${cause.message}`,
39
+ cause
40
+ });
41
+ /** A 64-bit column read back as text, as a `bigint`; see `statements.ts`. */
42
+ exports.writeFailed = writeFailed;
43
+ const bigintOf = text => BigInt(text);
44
+ exports.bigintOf = bigintOf;
45
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","names":["_feed","require","_config","_ddl","_statements","feedContext","config","names","resolveFeedNames","decodeFeedPostgresConfig","statements","compileFeedStatements","exports","unreadable","attempt","cause","FeedUnreadable","reason","message","writeFailed","FeedWriteFailed","bigintOf","text","BigInt"],"sources":["../../../src/internal/context.ts"],"sourcesContent":[null],"mappings":";;;;;;AASA,IAAAA,KAAA,GAAAC,OAAA;AAEA,IAAAC,OAAA,GAAAD,OAAA;AACA,IAAAE,IAAA,GAAAF,OAAA;AACA,IAAAG,WAAA,GAAAH,OAAA;AAbA;;;;;;;;;;AAqBA;AACO,MAAMI,WAAW,GAAIC,MAA0B,IAAiB;EACrE,MAAMC,KAAK,GAAG,IAAAC,qBAAgB,EAAC,IAAAC,gCAAwB,EAACH,MAAM,CAAC,CAAC;EAChE,OAAO;IAAEC,KAAK;IAAEG,UAAU,EAAE,IAAAC,iCAAqB,EAACJ,KAAK;EAAC,CAAE;AAC5D,CAAC;AAED;AAAAK,OAAA,CAAAP,WAAA,GAAAA,WAAA;AACO,MAAMQ,UAAU,GACpBC,OAAe,IACfC,KAAwB,IACvB,IAAIC,oBAAc,CAAC;EAAEC,MAAM,EAAE,GAAGH,OAAO,YAAYC,KAAK,CAACG,OAAO,EAAE;EAAEH;AAAK,CAAE,CAAC;AAEhF;AAAAH,OAAA,CAAAC,UAAA,GAAAA,UAAA;AACO,MAAMM,WAAW,GACrBL,OAAe,IACfC,KAAwB,IACvB,IAAIK,qBAAe,CAAC;EAClBH,MAAM,EAAE,GAAGH,OAAO,YAAYC,KAAK,CAACG,OAAO,EAAE;EAC7CH;CACD,CAAC;AAEN;AAAAH,OAAA,CAAAO,WAAA,GAAAA,WAAA;AACO,MAAME,QAAQ,GAAIC,IAA8B,IAAaC,MAAM,CAACD,IAAI,CAAC;AAAAV,OAAA,CAAAS,QAAA,GAAAA,QAAA","ignoreList":[]}