@kairos-es/read-postgres 1.0.0-alpha.4 → 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 (51) hide show
  1. package/README.md +35 -29
  2. package/dist/cjs/config.js +9 -13
  3. package/dist/cjs/config.js.map +1 -1
  4. package/dist/cjs/ensureCheckpointSchema.js +2 -2
  5. package/dist/cjs/ensureCheckpointSchema.js.map +1 -1
  6. package/dist/cjs/index.js.map +1 -1
  7. package/dist/cjs/internal/ddl.js +2 -2
  8. package/dist/cjs/projectionStore.js +62 -12
  9. package/dist/cjs/projectionStore.js.map +1 -1
  10. package/dist/{dts → dts-cjs}/config.d.ts +7 -11
  11. package/dist/{dts → dts-cjs}/config.d.ts.map +1 -1
  12. package/dist/{dts → dts-cjs}/ensureCheckpointSchema.d.ts +2 -2
  13. package/dist/{dts → dts-cjs}/ensureCheckpointSchema.d.ts.map +1 -1
  14. package/dist/{dts → dts-cjs}/index.d.ts +6 -4
  15. package/dist/{dts → dts-cjs}/index.d.ts.map +1 -1
  16. package/dist/{dts → dts-cjs}/internal/ddl.d.ts +4 -4
  17. package/dist/{dts → dts-cjs}/internal/ddl.d.ts.map +1 -1
  18. package/dist/dts-cjs/package.json +3 -0
  19. package/dist/dts-cjs/projectionStore.d.ts +77 -0
  20. package/dist/dts-cjs/projectionStore.d.ts.map +1 -0
  21. package/dist/dts-esm/config.d.ts +61 -0
  22. package/dist/dts-esm/config.d.ts.map +1 -0
  23. package/dist/dts-esm/ensureCheckpointSchema.d.ts +39 -0
  24. package/dist/dts-esm/ensureCheckpointSchema.d.ts.map +1 -0
  25. package/dist/dts-esm/index.d.ts +92 -0
  26. package/dist/dts-esm/index.d.ts.map +1 -0
  27. package/dist/dts-esm/internal/ddl.d.ts +146 -0
  28. package/dist/dts-esm/internal/ddl.d.ts.map +1 -0
  29. package/dist/dts-esm/internal/statements.d.ts +100 -0
  30. package/dist/dts-esm/internal/statements.d.ts.map +1 -0
  31. package/dist/dts-esm/package.json +3 -0
  32. package/dist/dts-esm/projectionStore.d.ts +77 -0
  33. package/dist/dts-esm/projectionStore.d.ts.map +1 -0
  34. package/dist/esm/config.js +9 -13
  35. package/dist/esm/config.js.map +1 -1
  36. package/dist/esm/ensureCheckpointSchema.js +2 -2
  37. package/dist/esm/ensureCheckpointSchema.js.map +1 -1
  38. package/dist/esm/index.js.map +1 -1
  39. package/dist/esm/internal/ddl.js +2 -2
  40. package/dist/esm/projectionStore.js +61 -12
  41. package/dist/esm/projectionStore.js.map +1 -1
  42. package/package.json +13 -9
  43. package/src/config.ts +11 -11
  44. package/src/ensureCheckpointSchema.ts +3 -2
  45. package/src/index.ts +6 -4
  46. package/src/internal/ddl.ts +4 -4
  47. package/src/projectionStore.ts +28 -20
  48. package/dist/dts/projectionStore.d.ts +0 -30
  49. package/dist/dts/projectionStore.d.ts.map +0 -1
  50. /package/dist/{dts → dts-cjs}/internal/statements.d.ts +0 -0
  51. /package/dist/{dts → dts-cjs}/internal/statements.d.ts.map +0 -0
package/README.md CHANGED
@@ -5,7 +5,7 @@ guarded compare-and-set that commits a read model's view writes and its checkpoi
5
5
  advance in one transaction, and an idempotent `ensureCheckpointSchema` for the
6
6
  consumer's own migrator (ADR-0007).
7
7
 
8
- Peers `@effect/sql` and needs only the generic `SqlClient` — no `@effect/sql-pg`
8
+ Needs only the generic `SqlClient` from `effect/sql` — no `@effect/sql-pg`
9
9
  and no reserved connection, because the event store owns the LISTEN.
10
10
 
11
11
  A store is defined entirely by its `{ schema, tablePrefix }` pair — the same pair
@@ -15,28 +15,33 @@ store" keeps holding across the read side.
15
15
 
16
16
  ## Installation
17
17
 
18
- `@kairos-es/core`, `@kairos-es/read`, `effect` and `@effect/sql` are peer
19
- dependencies (so the app owns the single shared Effect + `SqlClient` instance,
20
- per ADR-0003):
18
+ `@kairos-es/core`, `@kairos-es/read` and `effect` are peer dependencies (so the
19
+ app owns the single shared Effect + `SqlClient` instance, per ADR-0003); the
20
+ generic SQL modules (`SqlClient`, `Migrator`, `SqlError`) ship inside `effect`
21
+ itself, as `effect/sql`:
21
22
 
22
23
  ```sh
23
24
  pnpm add @kairos-es/read-postgres@alpha @kairos-es/read@alpha @kairos-es/codec@alpha \
24
- @kairos-es/core@alpha @effect/sql@^0.52.0 @effect/platform@^0.97.0 @effect/experimental@^0.61.0 \
25
- effect@^3.22.0
25
+ @kairos-es/core@alpha effect@^4.0.5
26
26
  ```
27
27
 
28
- Three of those are on the line because something ELSE on it requires them, not
29
- because this package names them. `@kairos-es/codec` is a peer of
30
- `@kairos-es/read`, which imports from it; `@effect/platform` and
31
- `@effect/experimental` are peers of `@effect/sql`. Neither requirer marks any of
32
- them optional — an optional peer is one the REQUIRER lists in its own
33
- `peerDependenciesMeta`, and neither declares that field at all — so all three
34
- are required. This package re-declares none of them, so the unmet-peer warning
35
- you get without them names the requirer rather than anything here.
28
+ `@kairos-es/codec` is on the line because something ELSE on it requires it, not
29
+ because this package names it: it is a peer of `@kairos-es/read`, which imports
30
+ from it. That requirer does not mark it optional — an optional peer is one the
31
+ REQUIRER lists in its own `peerDependenciesMeta`, and it declares no such field —
32
+ so it is required. This package does not re-declare it, so a package manager that
33
+ reports an unmet peer names the requirer rather than anything here.
36
34
 
37
35
  A `SqlClient` **implementation** is deliberately not peered: this package needs
38
- only the generic `SqlClient` tag, so the driver (`@effect/sql-pg`, or whatever
39
- already provides that tag in your app) stays your choice.
36
+ only the generic `SqlClient` service, so the driver (`@effect/sql-pg`, or whatever
37
+ already provides that service in your app) stays your choice.
38
+
39
+ If that driver is `@effect/sql-pg` 4, it caches named prepared statements by
40
+ default, its transaction's `BEGIN` and `COMMIT` included. Behind a
41
+ transaction-mode pooler, such as the PgBouncer behind Neon's `-pooler` endpoint, a
42
+ statement may run on a different server connection from the one that prepared
43
+ it, so unless the pooler documents support for protocol-level prepared
44
+ statements, build the client you hand this package with `prepare: false`.
40
45
 
41
46
  ## Schema: `ensureCheckpointSchema` as a migration
42
47
 
@@ -47,9 +52,9 @@ so re-running it is a no-op. Migration **orchestration** is yours; the package
47
52
  only provides the idempotent **payload**.
48
53
 
49
54
  ```ts
50
- import { Migrator } from '@effect/sql'
51
55
  import { ensureSchema } from '@kairos-es/store-postgres'
52
56
  import { ensureCheckpointSchema } from '@kairos-es/read-postgres'
57
+ import { Migrator } from 'effect/sql'
53
58
 
54
59
  const config = { schema: 'public', tablePrefix: 'dcb_events' }
55
60
 
@@ -59,7 +64,7 @@ const migrations = Migrator.fromRecord({
59
64
  })
60
65
  ```
61
66
 
62
- Because it requires the **generic** `SqlClient` tag, it slots into any migrator or
67
+ Because it requires the **generic** `SqlClient` service, it slots into any migrator or
63
68
  boot sequence that already provides one. The table it creates:
64
69
 
65
70
  ```sql
@@ -78,10 +83,10 @@ lookup, and it is the arbiter index the first commit's `ON CONFLICT` names.
78
83
 
79
84
  `makeSqlProjectionStore(config?)` is an `Effect`, not a `Layer` — the view store
80
85
  is selected **per materialisation**, so it is a value handed to a read model rather
81
- than a context tag one store could claim process-wide. Per materialisation and not
86
+ than a context service one store could claim process-wide. Per materialisation and not
82
87
  merely per read model: one read model may be materialised several times at once, a
83
88
  durable store from here beside an in-memory one, which is a shape a process-wide
84
- tag could not express at all. It closes over the `SqlClient` it acquires, so the
89
+ service could not express at all. It closes over the `SqlClient` it acquires, so the
85
90
  returned store's `commit` adds no `SqlClient` to the read model's own requirements.
86
91
 
87
92
  ```ts
@@ -99,7 +104,7 @@ const config = { schema: 'public', tablePrefix: 'dcb_events' }
99
104
 
100
105
  const key = checkpointKey(ProjectionId.make('course-subscriptions'))
101
106
 
102
- const courseView = Layer.unwrapEffect(
107
+ const courseView = Layer.unwrap(
103
108
  Effect.gen(function* () {
104
109
  const store = yield* makeSqlProjectionStore(config)
105
110
  return projectionLayer({
@@ -169,12 +174,13 @@ as the event store's `layer` assumes `ensureSchema` has.
169
174
 
170
175
  ## The atomic bracket
171
176
 
172
- `commit` is `sql.withTransaction(viewWrites *> guardedAdvance)`. Your own `sql`
173
- statements join that transaction because `@effect/sql` carries the transaction's
174
- connection in the Effect **context**, not in the client value; `withTransaction`
175
- is savepoint-aware, so a projection already inside a transaction composes without
176
- a second physical one. A write through an unrelated client (or to another
177
- database) is **not** bracketed.
177
+ `commit` is `sql.withTransaction(viewWrites *> guardedAdvance)`. Your view writes
178
+ join `commit` only when issued through the **same `SqlClient`** the store was
179
+ built from. A write through any other `SqlClient` instance — even one connected
180
+ to the same database — or to another database is **not** bracketed, and is not
181
+ rolled back on a lost guard. `withTransaction` is savepoint-aware, so a
182
+ projection already inside a transaction on that client composes without a second
183
+ physical one.
178
184
 
179
185
  The guarded advance touches zero rows when the stored position no longer equals
180
186
  `expected`, which fails `CheckpointSuperseded` — and that failure aborts the
@@ -205,8 +211,8 @@ projection", and a defect carries no such distinction.
205
211
  ## Positions are strictly-increasing, not gapless
206
212
 
207
213
  `last_position` is a `bigint` because a `Position` is 64-bit and exceeds JS
208
- `number` safety at 2^53; it is read back as a decimal string and reconstructed
209
- with `BigInt(…)`, never coerced through `number`. The stored position is the last
214
+ `number` safety at 2^53; the driver reads it back as a `bigint`, and it is never
215
+ coerced through `number`. The stored position is the last
210
216
  processed event's position **as is** — never `expected + 1` — so a gap is normal
211
217
  and arithmetic on a position would be wrong.
212
218
 
@@ -29,7 +29,7 @@ var _ddl = require("./internal/ddl.js");
29
29
  * per-materialisation view-store design exists to avoid: a durable view must be
30
30
  * free to sit over any log satisfying `DcbEventStore` — the in-memory oracle,
31
31
  * Postgres, a future backend — so this package depends on the PORT
32
- * (`@kairos-es/read`) and on `@effect/sql`, never on a sibling backend. The
32
+ * (`@kairos-es/read`) and on `effect/sql`, never on a sibling backend. The
33
33
  * duplication also keeps the two guards honestly independent: each package
34
34
  * measures only the identifiers IT materialises, so neither inherits a byte budget
35
35
  * from names it never creates.
@@ -48,7 +48,7 @@ var _ddl = require("./internal/ddl.js");
48
48
  * dot-split when quoted, so an embedded dot would silently re-partition the name
49
49
  * into the wrong schema/object.
50
50
  */
51
- const IdentifierAtom = /*#__PURE__*/_effect.Schema.String.pipe(/*#__PURE__*/_effect.Schema.nonEmptyString(), /*#__PURE__*/_effect.Schema.pattern(/^[^.]+$/, {
51
+ const IdentifierAtom = /*#__PURE__*/_effect.Schema.String.check(/*#__PURE__*/_effect.Schema.isNonEmpty(), /*#__PURE__*/_effect.Schema.isPattern(/^[^.]+$/, {
52
52
  identifier: 'PostgresIdentifierAtom',
53
53
  description: 'a non-empty identifier atom containing no "."'
54
54
  }));
@@ -63,23 +63,19 @@ const IdentifierAtom = /*#__PURE__*/_effect.Schema.String.pipe(/*#__PURE__*/_eff
63
63
  * objects, one isolated store per prefix.
64
64
  */
65
65
  const ReadPostgresConfig = exports.ReadPostgresConfig = /*#__PURE__*/_effect.Schema.Struct({
66
- // `exact: true` keeps the decoded type at `key?: T` (never `T | undefined`),
66
+ // `optionalKey` keeps the decoded type at `key?: T` (never `T | undefined`),
67
67
  // matching the repo's `exactOptionalPropertyTypes` discipline — an absent key,
68
68
  // not an explicit `undefined`.
69
- schema: _effect.Schema.optionalWith(IdentifierAtom, {
70
- exact: true
71
- }),
72
- tablePrefix: _effect.Schema.optionalWith(IdentifierAtom, {
73
- exact: true
74
- })
75
- }).pipe(/*#__PURE__*/
69
+ schema: _effect.Schema.optionalKey(IdentifierAtom),
70
+ tablePrefix: _effect.Schema.optionalKey(IdentifierAtom)
71
+ }).check(/*#__PURE__*/
76
72
  // Cross-field guard: the derived table name must fit Postgres's 63-byte
77
73
  // identifier limit. Returning the message string fails the decode with it;
78
- // `undefined`/`true` passes.
79
- _effect.Schema.filter(config => (0, _ddl.oversizedIdentifier)(config) ?? true));
74
+ // `undefined` passes.
75
+ _effect.Schema.makeFilter(config => (0, _ddl.oversizedIdentifier)(config)));
80
76
  /**
81
77
  * Decode an unknown value into a validated `ReadPostgresConfig`, throwing a
82
- * `ParseError` on any violation. Used by `makeSqlProjectionStore` and
78
+ * `SchemaError` on any violation. Used by `makeSqlProjectionStore` and
83
79
  * `ensureCheckpointSchema` so a bad config fails at construction time.
84
80
  */
85
81
  const decodeReadPostgresConfig = exports.decodeReadPostgresConfig = /*#__PURE__*/_effect.Schema.decodeUnknownSync(ReadPostgresConfig);
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","names":["_effect","require","_ddl","IdentifierAtom","Schema","String","pipe","nonEmptyString","pattern","identifier","description","ReadPostgresConfig","exports","Struct","schema","optionalWith","exact","tablePrefix","filter","config","oversizedIdentifier","decodeReadPostgresConfig","decodeUnknownSync"],"sources":["../../src/config.ts"],"sourcesContent":[null],"mappings":";;;;;;AAmCA,IAAAA,OAAA,GAAAC,OAAA;AACA,IAAAC,IAAA,GAAAD,OAAA;AApCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA;;;;;;AAMA,MAAME,cAAc,gBAAGC,cAAM,CAACC,MAAM,CAACC,IAAI,cACvCF,cAAM,CAACG,cAAc,EAAE,eACvBH,cAAM,CAACI,OAAO,CAAC,SAAS,EAAE;EACxBC,UAAU,EAAE,wBAAwB;EACpCC,WAAW,EAAE;CACd,CAAC,CACH;AAED;;;;;;;;;;AAUO,MAAMC,kBAAkB,GAAAC,OAAA,CAAAD,kBAAA,gBAAGP,cAAM,CAACS,MAAM,CAAC;EAC9C;EACA;EACA;EACAC,MAAM,EAAEV,cAAM,CAACW,YAAY,CAACZ,cAAc,EAAE;IAAEa,KAAK,EAAE;EAAI,CAAE,CAAC;EAC5DC,WAAW,EAAEb,cAAM,CAACW,YAAY,CAACZ,cAAc,EAAE;IAAEa,KAAK,EAAE;EAAI,CAAE;CACjE,CAAC,CAACV,IAAI;AACL;AACA;AACA;AACAF,cAAM,CAACc,MAAM,CAAEC,MAAM,IAAK,IAAAC,wBAAmB,EAACD,MAAM,CAAC,IAAI,IAAI,CAAC,CAC/D;AAGD;;;;;AAKO,MAAME,wBAAwB,GAAAT,OAAA,CAAAS,wBAAA,gBACnCjB,cAAM,CAACkB,iBAAiB,CAACX,kBAAkB,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"config.js","names":["_effect","require","_ddl","IdentifierAtom","Schema","String","check","isNonEmpty","isPattern","identifier","description","ReadPostgresConfig","exports","Struct","schema","optionalKey","tablePrefix","makeFilter","config","oversizedIdentifier","decodeReadPostgresConfig","decodeUnknownSync"],"sources":["../../src/config.ts"],"sourcesContent":[null],"mappings":";;;;;;AAmCA,IAAAA,OAAA,GAAAC,OAAA;AACA,IAAAC,IAAA,GAAAD,OAAA;AApCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA;;;;;;AAMA,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;;;;;;;;;;AAUO,MAAMC,kBAAkB,GAAAC,OAAA,CAAAD,kBAAA,gBAAGP,cAAM,CAACS,MAAM,CAAC;EAC9C;EACA;EACA;EACAC,MAAM,EAAEV,cAAM,CAACW,WAAW,CAACZ,cAAc,CAAC;EAC1Ca,WAAW,EAAEZ,cAAM,CAACW,WAAW,CAACZ,cAAc;CAC/C,CAAC,CAACG,KAAK;AACN;AACA;AACA;AACAF,cAAM,CAACa,UAAU,CAAEC,MAAM,IAAK,IAAAC,wBAAmB,EAACD,MAAM,CAAC,CAAC,CAC3D;AAGD;;;;;AAKO,MAAME,wBAAwB,GAAAR,OAAA,CAAAQ,wBAAA,gBACnChB,cAAM,CAACiB,iBAAiB,CAACV,kBAAkB,CAAC","ignoreList":[]}
@@ -4,8 +4,8 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.ensureCheckpointSchema = void 0;
7
- var _sql = require("@effect/sql");
8
7
  var _effect = require("effect");
8
+ var _sql = require("effect/sql");
9
9
  var _config = require("./config.js");
10
10
  var _ddl = require("./internal/ddl.js");
11
11
  /**
@@ -14,7 +14,7 @@ var _ddl = require("./internal/ddl.js");
14
14
  *
15
15
  * It is a single `Effect<void, SqlError, SqlClient>` that creates (or refreshes)
16
16
  * the one object this package owns: the checkpoint table. It requires the GENERIC
17
- * `SqlClient` tag — not a package-owned client tag — precisely so a consumer can
17
+ * `SqlClient` service — not a package-owned client service — precisely so a consumer can
18
18
  * drop it straight into THEIR OWN migrator, e.g.:
19
19
  *
20
20
  * ```ts
@@ -1 +1 @@
1
- {"version":3,"file":"ensureCheckpointSchema.js","names":["_sql","require","_effect","_config","_ddl","ensureCheckpointSchema","config","Effect","gen","sql","SqlClient","names","resolveCheckpointNames","decodeReadPostgresConfig","all","runCheckpointDdl","discard","exports"],"sources":["../../src/ensureCheckpointSchema.ts"],"sourcesContent":[null],"mappings":";;;;;;AA6BA,IAAAA,IAAA,GAAAC,OAAA;AACA,IAAAC,OAAA,GAAAD,OAAA;AACA,IAAAE,OAAA,GAAAF,OAAA;AACA,IAAAG,IAAA,GAAAH,OAAA;AAhCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA;;;;;AAKO,MAAMI,sBAAsB,GACjCC,MAA0B,IAE1BC,cAAM,CAACC,GAAG,CAAC,aAAS;EAClB,MAAMC,GAAG,GAAG,OAAOC,cAAS,CAACA,SAAS;EACtC;EACA;EACA;EACA,MAAMC,KAAK,GAAG,IAAAC,2BAAsB,EAAC,IAAAC,gCAAwB,EAACP,MAAM,CAAC,CAAC;EACtE;EACA,OAAOC,cAAM,CAACO,GAAG,CAAC,IAAAC,qBAAgB,EAACN,GAAG,EAAEE,KAAK,CAAC,EAAE;IAAEK,OAAO,EAAE;EAAI,CAAE,CAAC;AACpE,CAAC,CAAC;AAAAC,OAAA,CAAAZ,sBAAA,GAAAA,sBAAA","ignoreList":[]}
1
+ {"version":3,"file":"ensureCheckpointSchema.js","names":["_effect","require","_sql","_config","_ddl","ensureCheckpointSchema","config","Effect","gen","sql","SqlClient","names","resolveCheckpointNames","decodeReadPostgresConfig","all","runCheckpointDdl","discard","exports"],"sources":["../../src/ensureCheckpointSchema.ts"],"sourcesContent":[null],"mappings":";;;;;;AA8BA,IAAAA,OAAA,GAAAC,OAAA;AACA,IAAAC,IAAA,GAAAD,OAAA;AACA,IAAAE,OAAA,GAAAF,OAAA;AACA,IAAAG,IAAA,GAAAH,OAAA;AAjCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA;;;;;AAKO,MAAMI,sBAAsB,GACjCC,MAA0B,IAE1BC,cAAM,CAACC,GAAG,CAAC,aAAS;EAClB,MAAMC,GAAG,GAAG,OAAOC,cAAS,CAACA,SAAS;EACtC;EACA;EACA;EACA,MAAMC,KAAK,GAAG,IAAAC,2BAAsB,EAAC,IAAAC,gCAAwB,EAACP,MAAM,CAAC,CAAC;EACtE;EACA,OAAOC,cAAM,CAACO,GAAG,CAAC,IAAAC,qBAAgB,EAACN,GAAG,EAAEE,KAAK,CAAC,EAAE;IAAEK,OAAO,EAAE;EAAI,CAAE,CAAC;AACpE,CAAC,CAAC;AAAAC,OAAA,CAAAZ,sBAAA,GAAAA,sBAAA","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["_ensureCheckpointSchema","require","_projectionStore"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;;AAuFA,IAAAA,uBAAA,GAAAC,OAAA;AACA,IAAAC,gBAAA,GAAAD,OAAA","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["_ensureCheckpointSchema","require","_projectionStore"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;;AAyFA,IAAAA,uBAAA,GAAAC,OAAA;AACA,IAAAC,gBAAA,GAAAD,OAAA","ignoreList":[]}
@@ -122,8 +122,8 @@ const CHECKPOINT_COLUMNS = exports.CHECKPOINT_COLUMNS = {
122
122
  partition: /*#__PURE__*/quoteAtom('partition'),
123
123
  /**
124
124
  * The scalar checkpoint: `bigint`, because a `Position` is 64-bit and exceeds
125
- * JS `number` safety at 2^53. It is read back as a decimal STRING and
126
- * reconstructed with `BigInt(...)`, never coerced through `number`.
125
+ * JS `number` safety at 2^53. The driver reads it back as a `bigint`, and it
126
+ * is never coerced through `number`.
127
127
  */
128
128
  lastPosition: /*#__PURE__*/quoteAtom('last_position')
129
129
  };
@@ -4,13 +4,58 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.makeSqlProjectionStore = void 0;
7
- var _sql = require("@effect/sql");
8
7
  var _core = require("@kairos-es/core");
9
8
  var _read = require("@kairos-es/read");
10
9
  var _effect = require("effect");
10
+ var _sql = require("effect/sql");
11
11
  var _config = require("./config.js");
12
12
  var _ddl = require("./internal/ddl.js");
13
13
  var _statements = require("./internal/statements.js");
14
+ /**
15
+ * The SQL `ProjectionStore`: a durable scalar checkpoint plus the guarded
16
+ * compare-and-set that brackets a read model's own view writes in ONE ordinary
17
+ * transaction (ADR-0007).
18
+ *
19
+ * This is the durable half of the parity pair. `@kairos-es/read`'s in-memory store
20
+ * is the oracle; this backend must produce identical OBSERVABLE behaviour, and the
21
+ * shared `projection-store-contract-tests` suite runs unchanged against both. The
22
+ * one place the two genuinely differ is HOW the abort is achieved:
23
+ *
24
+ * - in memory, the guard is checked FIRST and a loss means `viewWrites` never ran
25
+ * (abort by omission — there is nothing to undo);
26
+ * - here, `viewWrites` runs first and a lost guard FAILS, so the transaction rolls
27
+ * the view writes back with it (abort by undo).
28
+ *
29
+ * Both satisfy the port's promise that a lost guard leaves no observable view
30
+ * effect, which is why ordering inside the transaction is a performance choice
31
+ * rather than a correctness one: running the guard last keeps the checkpoint row
32
+ * lock held only for the advance instead of for the whole batch's worth of view
33
+ * writes.
34
+ *
35
+ * Two contract inversions worth naming, both deliberate:
36
+ *
37
+ * 1. A `SqlError` becomes a `ProjectionStoreError` on the ERROR CHANNEL — the
38
+ * opposite of the event store, where an infrastructure fault is a DEFECT
39
+ * (`Effect.die`, ADR-0002). The read side has a supervisor above it whose whole
40
+ * job is telling "retry the subscription with backoff" from "give up and report
41
+ * a stalled projection", and a defect carries no such distinction. On the event
42
+ * store there is no supervisor: a caller cannot meaningfully retry a broken
43
+ * connection, so dying is right there and wrong here.
44
+ * 2. A lost guard is a tagged failure (`CheckpointSuperseded`), never a returned
45
+ * outcome value, and that failure is exactly what aborts the transaction. A
46
+ * returned value would only roll back if the caller remembered to convert it
47
+ * into a failure, and forgetting is silent double-application.
48
+ *
49
+ * The store closes over the `SqlClient` it acquired, so the returned
50
+ * `ProjectionStore.commit` adds NO `SqlClient` to the caller's `R` — a read model
51
+ * stays free of the view store's requirements, which is what lets one runner drive
52
+ * read models over different backends.
53
+ *
54
+ * What this module owns is the Effect wiring: the client, the transaction bracket,
55
+ * the error classification. The SQL itself and the parameter lists that feed it are
56
+ * `internal/statements.ts`, which owns the argument for why the two live together.
57
+ */
58
+
14
59
  /**
15
60
  * Turn a `ProjectionStoreError` out of a SQL fault, naming what was being
16
61
  * attempted and for which key. `reason` is the human-readable line a supervisor
@@ -41,20 +86,23 @@ const unwrapCommitError = (key, error) => {
41
86
  *
42
87
  * A factory `Effect` rather than a `Layer`, matching the in-memory store: the view
43
88
  * store is selected PER MATERIALISATION, so it is a value handed to a read model, not
44
- * a context tag one store could claim process-wide. Per materialisation rather than
89
+ * a context service one store could claim process-wide. Per materialisation rather than
45
90
  * per read model, because one read model may be materialised several times at once —
46
91
  * a store from here beside an in-memory one, wired by `@kairos-es/read`'s
47
- * `runProjections` — which is a shape a process-wide tag could not express at all. It
92
+ * `runProjections` — which is a shape a process-wide service could not express at all. It
48
93
  * acquires the client ONCE and closes over it, which is what keeps `commit`'s `R`
49
94
  * down to the caller's own.
50
95
  *
51
96
  * **The atomic bracket covers writes issued through THIS client.** The caller's
52
- * `viewWrites` join the transaction because `@effect/sql` carries the transaction's
53
- * connection in the Effect CONTEXT, not in the client value — so any statement run
54
- * through the same underlying client inside `commit` is in the same transaction,
55
- * including through a differently-transformed copy of it. A write to a different
56
- * database, or through an unrelated client, is NOT bracketed and the exactly-once
57
- * property does not extend to it.
97
+ * `viewWrites` join the transaction only when they are issued through the same
98
+ * `SqlClient` the store was built from: `effect/sql` carries the transaction's
99
+ * connection in the Effect CONTEXT under a key that belongs to that one client
100
+ * instance, so a statement run inside `commit` finds the open transaction only if
101
+ * its client owns that key. The store's own `withoutTransforms()` copy shares the
102
+ * key, which is why the guarded advance and the caller's view writes land in one
103
+ * transaction; a write through any OTHER `SqlClient` instance — even one over the
104
+ * same database — or to a different database is NOT bracketed, is not rolled back
105
+ * on a lost guard, and the exactly-once property does not extend to it.
58
106
  *
59
107
  * `ensureCheckpointSchema(config)` must have run (in the caller's migrator or at
60
108
  * boot) before the store is used; this factory performs no DDL, so it stays a pure
@@ -62,13 +110,15 @@ const unwrapCommitError = (key, error) => {
62
110
  */
63
111
  const makeSqlProjectionStore = (config = {}) => _effect.Effect.gen(function* () {
64
112
  // `withoutTransforms()` because this package takes the CONSUMER's generic
65
- // client, which may carry a result-name transform (`@effect/sql` will happily
113
+ // client, which may carry a result-name transform (`effect/sql` will happily
66
114
  // camel-case every returned key). Under one, `last_position` would arrive as
67
115
  // `lastPosition`, `readCheckpoint` would see `undefined` and report `ORIGIN`,
68
116
  // and a durable projection would silently replay its whole backlog on every
69
117
  // boot. Stripping the transform for OUR statements is the fix; the caller's
70
118
  // own view writes keep their client's transforms and still join the same
71
- // transaction (the connection travels in the context, not the client).
119
+ // transaction, because the transform-free copy carries the original client's
120
+ // transaction key and so finds the connection that client's transaction put
121
+ // in the context.
72
122
  const sql = (yield* _sql.SqlClient.SqlClient).withoutTransforms();
73
123
  // Decode INSIDE the Effect so a malformed config (empty/dotted atom, an
74
124
  // over-63-byte derived name) surfaces as a defect on the fibre building the
@@ -131,7 +181,7 @@ const makeSqlProjectionStore = (config = {}) => _effect.Effect.gen(function* ()
131
181
  // interrupt landing mid-body rolls back exactly like a failure.
132
182
  // `args` itself, not three values pulled out of it in an order: see
133
183
  // `guardedAdvance`.
134
- _effect.Effect.zipRight(guardedAdvance(args)))).pipe(_effect.Effect.mapError(error => unwrapCommitError(args.key, error)));
184
+ _effect.Effect.andThen(guardedAdvance(args)))).pipe(_effect.Effect.mapError(error => unwrapCommitError(args.key, error)));
135
185
  const resetCheckpoint = key =>
136
186
  // DELETE rather than `SET last_position = 0`, so "never committed" and
137
187
  // "reset" stay ONE state — which is exactly what the port promises the
@@ -1 +1 @@
1
- {"version":3,"file":"projectionStore.js","names":["_sql","require","_core","_read","_effect","_config","_ddl","_statements","storeError","attempt","key","cause","ProjectionStoreError","reason","projection","partition","message","unwrapCommitError","error","_tag","makeSqlProjectionStore","config","Effect","gen","sql","SqlClient","withoutTransforms","names","resolveCheckpointNames","decodeReadPostgresConfig","statements","compileStatements","readCheckpoint","unsafe","select","keyParams","pipe","mapError","map","rows","stored","last_position","undefined","ORIGIN","Position","make","BigInt","guardedAdvance","args","expected","text","upsertFromOrigin","advance","advanceParams","flatMap","length","fail","CheckpointSuperseded","void","commit","withTransaction","viewWrites","zipRight","resetCheckpoint","reset","asVoid","store","durability","exports"],"sources":["../../src/projectionStore.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"projectionStore.js","names":["_core","require","_read","_effect","_sql","_config","_ddl","_statements","storeError","attempt","key","cause","ProjectionStoreError","reason","projection","partition","message","unwrapCommitError","error","_tag","makeSqlProjectionStore","config","Effect","gen","sql","SqlClient","withoutTransforms","names","resolveCheckpointNames","decodeReadPostgresConfig","statements","compileStatements","readCheckpoint","unsafe","select","keyParams","pipe","mapError","map","rows","stored","last_position","undefined","ORIGIN","Position","make","BigInt","guardedAdvance","args","expected","text","upsertFromOrigin","advance","advanceParams","flatMap","length","fail","CheckpointSuperseded","void","commit","withTransaction","viewWrites","andThen","resetCheckpoint","reset","asVoid","store","durability","exports"],"sources":["../../src/projectionStore.ts"],"sourcesContent":[null],"mappings":";;;;;;AA6CA,IAAAA,KAAA,GAAAC,OAAA;AACA,IAAAC,KAAA,GAAAD,OAAA;AAMA,IAAAE,OAAA,GAAAF,OAAA;AAEA,IAAAG,IAAA,GAAAH,OAAA;AACA,IAAAI,OAAA,GAAAJ,OAAA;AACA,IAAAK,IAAA,GAAAL,OAAA;AACA,IAAAM,WAAA,GAAAN,OAAA;AAzDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyGA;;;;;;AAMA,MAAMO,UAAU,GACdA,CAACC,OAAe,EAAEC,GAAkB,KACnCC,KAAwB,IACvB,IAAIC,0BAAoB,CAAC;EACvBC,MAAM,EAAE,GAAGJ,OAAO,oBAAoBC,GAAG,CAACI,UAAU,gBAAgBJ,GAAG,CAACK,SAAS,aAAaJ,KAAK,CAACK,OAAO,EAAE;EAC7GL;CACD,CAAC;AAEN;;;;;;AAMA,MAAMM,iBAAiB,GAAGA,CACxBP,GAAkB,EAClBQ,KAAuB,KAC4B;EACnD,IAAIA,KAAK,CAACC,IAAI,KAAK,iBAAiB,EAAE;IACpC,OAAOD,KAAK,CAACA,KAAK;EACpB;EACA,IAAIA,KAAK,CAACC,IAAI,KAAK,UAAU,EAAE;IAC7B,OAAOX,UAAU,CAAC,mCAAmC,EAAEE,GAAG,CAAC,CAACQ,KAAK,CAAC;EACpE;EACA,OAAOA,KAAK;AACd,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BO,MAAME,sBAAsB,GAAGA,CACpCC,MAAA,GAA6B,EAAE,KAE/BC,cAAM,CAACC,GAAG,CAAC,aAAS;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,MAAMC,GAAG,GAAG,CAAC,OAAOC,cAAS,CAACA,SAAS,EAAEC,iBAAiB,EAAE;EAE5D;EACA;EACA;EACA;EACA,MAAMC,KAAK,GAAG,IAAAC,2BAAsB,EAAC,IAAAC,gCAAwB,EAACR,MAAM,CAAC,CAAC;EACtE,MAAMS,UAAU,GAAG,IAAAC,6BAAiB,EAACJ,KAAK,CAAC;EAE3C,MAAMK,cAAc,GAClBtB,GAAkB,IAElBc,GAAG,CAACS,MAAM,CAAkBH,UAAU,CAACI,MAAM,EAAE,IAAAC,qBAAS,EAACzB,GAAG,CAAC,CAAC,CAAC0B,IAAI,CACjEd,cAAM,CAACe,QAAQ,CAAC7B,UAAU,CAAC,wBAAwB,EAAEE,GAAG,CAAC,CAAC,EAC1DY,cAAM,CAACgB,GAAG,CAAEC,IAAI,IAAI;IAClB,MAAMC,MAAM,GAAGD,IAAI,CAAC,CAAC,CAAC,EAAEE,aAAa;IACrC;IACA;IACA;IACA,OAAOD,MAAM,KAAKE,SAAS,GACvBC,YAAM;IACN;IACA;IACA;IACAC,cAAQ,CAACC,IAAI,CAACC,MAAM,CAACN,MAAM,CAAC,CAAC;EACnC,CAAC,CAAC,CACH;EAEH;;;;;;;;;;;;EAYA,MAAMO,cAAc,GAAIC,IAIvB,IAAsE;IACrE,MAAM;MAAEtC,GAAG;MAAEuC;IAAQ,CAAE,GAAGD,IAAI;IAC9B,MAAM,CAACE,IAAI,EAAEzC,OAAO,CAAC,GACnBwC,QAAQ,KAAKN,YAAM,GACd,CACCb,UAAU,CAACqB,gBAAgB,EAC3B,gCAAgC,CACvB,GACV,CAACrB,UAAU,CAACsB,OAAO,EAAE,0BAA0B,CAAW;IACjE;IACA;IACA;IACA,OAAO5B,GAAG,CAACS,MAAM,CAAkBiB,IAAI,EAAE,IAAAG,yBAAa,EAACL,IAAI,CAAC,CAAC,CAACZ,IAAI,CAChEd,cAAM,CAACe,QAAQ,CAAC7B,UAAU,CAACC,OAAO,EAAEC,GAAG,CAAC,CAAC,EACzCY,cAAM,CAACgC,OAAO,CAAEf,IAAI;IAClB;IACA;IACA;IACAA,IAAI,CAACgB,MAAM,KAAK,CAAC,GACbjC,cAAM,CAACkC,IAAI,CAAC,IAAIC,0BAAoB,CAAC;MAAE/C,GAAG;MAAEuC;IAAQ,CAAE,CAAC,CAAC,GACxD3B,cAAM,CAACoC,IAAI,CAChB,CACF;EACH,CAAC;EAED,MAAMC,MAAM,GAAUX,IAKrB,IAKCxB,GAAG,CACAoC,eAAe,CACdZ,IAAI,CAACa,UAAU,CAACzB,IAAI,CAClBd,cAAM,CAACe,QAAQ,CACZnB,KAAK,KAA0B;IAC9BC,IAAI,EAAE,iBAAiB;IACvBD;GACD,CAAC,CACH;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACAI,cAAM,CAACwC,OAAO,CAACf,cAAc,CAACC,IAAI,CAAC,CAAC,CACrC,CACF,CACAZ,IAAI,CAACd,cAAM,CAACe,QAAQ,CAAEnB,KAAK,IAAKD,iBAAiB,CAAI+B,IAAI,CAACtC,GAAG,EAAEQ,KAAK,CAAC,CAAC,CAAC;EAE5E,MAAM6C,eAAe,GACnBrD,GAAkB;EAElB;EACA;EACA;EACAc,GAAG,CACAS,MAAM,CAASH,UAAU,CAACkC,KAAK,EAAE,IAAA7B,qBAAS,EAACzB,GAAG,CAAC,CAAC,CAChD0B,IAAI,CACHd,cAAM,CAACe,QAAQ,CAAC7B,UAAU,CAAC,0BAA0B,EAAEE,GAAG,CAAC,CAAC,EAC5DY,cAAM,CAAC2C,MAAM,CACd;EAEL;EACA;EACA,MAAMC,KAAK,GAAoB;IAC7B;IACA;IACA;IACAC,UAAU,EAAE,SAAS;IACrBnC,cAAc;IACd2B,MAAM;IACNI;GACD;EACD,OAAOG,KAAK;AACd,CAAC,CAAC;AAAAE,OAAA,CAAAhD,sBAAA,GAAAA,sBAAA","ignoreList":[]}
@@ -21,7 +21,7 @@
21
21
  * per-materialisation view-store design exists to avoid: a durable view must be
22
22
  * free to sit over any log satisfying `DcbEventStore` — the in-memory oracle,
23
23
  * Postgres, a future backend — so this package depends on the PORT
24
- * (`@kairos-es/read`) and on `@effect/sql`, never on a sibling backend. The
24
+ * (`@kairos-es/read`) and on `effect/sql`, never on a sibling backend. The
25
25
  * duplication also keeps the two guards honestly independent: each package
26
26
  * measures only the identifiers IT materialises, so neither inherits a byte budget
27
27
  * from names it never creates.
@@ -44,21 +44,17 @@ import { Schema } from 'effect';
44
44
  * store is configured with and the checkpoint table lands beside that store's
45
45
  * objects, one isolated store per prefix.
46
46
  */
47
- export declare const ReadPostgresConfig: Schema.filter<Schema.Struct<{
48
- schema: Schema.optionalWith<Schema.filter<Schema.filter<typeof Schema.String>>, {
49
- exact: true;
50
- }>;
51
- tablePrefix: Schema.optionalWith<Schema.filter<Schema.filter<typeof Schema.String>>, {
52
- exact: true;
53
- }>;
54
- }>>;
47
+ export declare const ReadPostgresConfig: Schema.Struct<{
48
+ readonly schema: Schema.optionalKey<Schema.String>;
49
+ readonly tablePrefix: Schema.optionalKey<Schema.String>;
50
+ }>;
55
51
  export type ReadPostgresConfig = typeof ReadPostgresConfig.Type;
56
52
  /**
57
53
  * Decode an unknown value into a validated `ReadPostgresConfig`, throwing a
58
- * `ParseError` on any violation. Used by `makeSqlProjectionStore` and
54
+ * `SchemaError` on any violation. Used by `makeSqlProjectionStore` and
59
55
  * `ensureCheckpointSchema` so a bad config fails at construction time.
60
56
  */
61
- export declare const decodeReadPostgresConfig: (u: unknown, overrideOptions?: import("effect/SchemaAST").ParseOptions) => {
57
+ export declare const decodeReadPostgresConfig: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => {
62
58
  readonly schema?: string;
63
59
  readonly tablePrefix?: string;
64
60
  };
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAiB/B;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB;;;;;;;GAW9B,CAAA;AACD,MAAM,MAAM,kBAAkB,GAAG,OAAO,kBAAkB,CAAC,IAAI,CAAA;AAE/D;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;;;CACS,CAAA"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAiB/B;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB;;;EAW9B,CAAA;AACD,MAAM,MAAM,kBAAkB,GAAG,OAAO,kBAAkB,CAAC,IAAI,CAAA;AAE/D;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;;;CACS,CAAA"}
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * It is a single `Effect<void, SqlError, SqlClient>` that creates (or refreshes)
6
6
  * the one object this package owns: the checkpoint table. It requires the GENERIC
7
- * `SqlClient` tag — not a package-owned client tag — precisely so a consumer can
7
+ * `SqlClient` service — not a package-owned client service — precisely so a consumer can
8
8
  * drop it straight into THEIR OWN migrator, e.g.:
9
9
  *
10
10
  * ```ts
@@ -27,8 +27,8 @@
27
27
  * reads no rows and passes no identifier through `sql(identifier)`, so a
28
28
  * consumer's name transforms have nothing here to rewrite.
29
29
  */
30
- import { SqlClient, type SqlError } from '@effect/sql';
31
30
  import { Effect } from 'effect';
31
+ import { SqlClient, type SqlError } from 'effect/sql';
32
32
  import { type ReadPostgresConfig } from './config.js';
33
33
  /**
34
34
  * Produce the checkpoint migration payload for `config`. Resolves the table name
@@ -1 +1 @@
1
- {"version":3,"file":"ensureCheckpointSchema.d.ts","sourceRoot":"","sources":["../../src/ensureCheckpointSchema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAG5E;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,QAAQ,kBAAkB,KACzB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,SAAS,CASzD,CAAA"}
1
+ {"version":3,"file":"ensureCheckpointSchema.d.ts","sourceRoot":"","sources":["../../src/ensureCheckpointSchema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrD,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAG5E;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,QAAQ,kBAAkB,KACzB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,SAAS,CASzD,CAAA"}
@@ -3,7 +3,7 @@
3
3
  * table plus the guarded compare-and-set that brackets a read model's own view
4
4
  * writes in one ordinary transaction (ADR-0007).
5
5
  *
6
- * Peers `@effect/sql` and needs only the GENERIC `SqlClient`, deliberately not
6
+ * Needs only the GENERIC `SqlClient` from `effect/sql`, deliberately not
7
7
  * `@effect/sql-pg` and deliberately no reserved connection: the event store owns
8
8
  * the LISTEN, so the read side needs no dedicated connection of its own, and
9
9
  * requiring the generic client is what lets `ensureCheckpointSchema` drop into a
@@ -66,8 +66,10 @@
66
66
  * package performs no DDL at runtime.
67
67
  *
68
68
  * `commit` is `sql.withTransaction(viewWrites *> guardedAdvance)`. The author's own
69
- * `sql` statements join that transaction through the same client, and
70
- * `withTransaction` is savepoint-aware, so a projection already inside a
69
+ * view writes join that transaction only when issued through the SAME `SqlClient`
70
+ * the store was built from; a write through any other `SqlClient` instance is NOT
71
+ * bracketed and is not rolled back on a lost guard. `withTransaction` is
72
+ * savepoint-aware, so a projection already inside a
71
73
  * transaction composes without a second physical one. A guarded advance touching
72
74
  * zero rows raises `CheckpointSuperseded`, whose FAILURE aborts the transaction and
73
75
  * rolls the view writes back with it — the abort that makes the projection
@@ -82,7 +84,7 @@
82
84
  * implementation, which is precisely what per-materialisation view-store selection
83
85
  * exists to avoid. A durable view must be free to sit over any log satisfying
84
86
  * `DcbEventStore`, so this package depends on the port (`@kairos-es/read`) and
85
- * on `@effect/sql`, never on a sibling backend.
87
+ * on `effect/sql`, never on a sibling backend.
86
88
  */
87
89
  export type { ReadPostgresConfig } from './config.js';
88
90
  export { ensureCheckpointSchema } from './ensureCheckpointSchema.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqFG;AACH,YAAY,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAA;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuFG;AACH,YAAY,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAA;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA"}
@@ -25,13 +25,13 @@
25
25
  *
26
26
  * The runtime deliberately does NOT route these names through `sql(identifier)`.
27
27
  * This package takes the CONSUMER's generic `SqlClient`, which may carry a
28
- * query-name transform (`@effect/sql` applies it to every `sql(identifier)`), so
28
+ * query-name transform (`effect/sql` applies it to every `sql(identifier)`), so
29
29
  * a camel-casing consumer would silently rewrite `last_position` to
30
30
  * `lastPosition` and address a column that does not exist. Pre-quoted text inside
31
31
  * `sql.unsafe` is transform-proof; the result side is handled by
32
32
  * `withoutTransforms()` in `projectionStore.ts`.
33
33
  */
34
- import type { SqlClient, Statement } from '@effect/sql';
34
+ import type { SqlClient, Statement } from 'effect/sql';
35
35
  /**
36
36
  * The one object a read-side config owns. An interface rather than a bare string
37
37
  * so the `{ schema, tablePrefix }` atoms stay available to error messages and so
@@ -111,8 +111,8 @@ export declare const CHECKPOINT_COLUMNS: {
111
111
  readonly partition: string;
112
112
  /**
113
113
  * The scalar checkpoint: `bigint`, because a `Position` is 64-bit and exceeds
114
- * JS `number` safety at 2^53. It is read back as a decimal STRING and
115
- * reconstructed with `BigInt(...)`, never coerced through `number`.
114
+ * JS `number` safety at 2^53. The driver reads it back as a `bigint`, and it
115
+ * is never coerced through `number`.
116
116
  */
117
117
  readonly lastPosition: string;
118
118
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ddl.d.ts","sourceRoot":"","sources":["../../../src/internal/ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvD;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,qEAAqE;IACrE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;CACjC;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,WAAW,CAAA;AACtC,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,eAAe,CAAA;AA0ChD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,GAAI,QAAQ;IAC1C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B,KAAG,MAAM,GAAG,SAQZ,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GAAI,QAAQ;IAC7C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B,KAAG,uBAYH,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,KAAG,MAIhC,CAAA;AAQd;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB;IAC7B,uCAAuC;;IAEvC,wEAAwE;;IAExE;;;;OAIG;;CAEK,CAAA;AAEV;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,uBAAuB,GAClC,OAAO,uBAAuB,KAC7B,aAAa,CAAC,MAAM,CAiBtB,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAC3B,KAAK,SAAS,CAAC,SAAS,EACxB,OAAO,uBAAuB,KAC7B,aAAa,CAAC,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC,CAC6B,CAAA"}
1
+ {"version":3,"file":"ddl.d.ts","sourceRoot":"","sources":["../../../src/internal/ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAEtD;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,qEAAqE;IACrE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;CACjC;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,WAAW,CAAA;AACtC,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,eAAe,CAAA;AA0ChD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,GAAI,QAAQ;IAC1C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B,KAAG,MAAM,GAAG,SAQZ,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GAAI,QAAQ;IAC7C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B,KAAG,uBAYH,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,KAAG,MAIhC,CAAA;AAQd;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB;IAC7B,uCAAuC;;IAEvC,wEAAwE;;IAExE;;;;OAIG;;CAEK,CAAA;AAEV;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,uBAAuB,GAClC,OAAO,uBAAuB,KAC7B,aAAa,CAAC,MAAM,CAiBtB,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAC3B,KAAK,SAAS,CAAC,SAAS,EACxB,OAAO,uBAAuB,KAC7B,aAAa,CAAC,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC,CAC6B,CAAA"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The SQL `ProjectionStore`: a durable scalar checkpoint plus the guarded
3
+ * compare-and-set that brackets a read model's own view writes in ONE ordinary
4
+ * transaction (ADR-0007).
5
+ *
6
+ * This is the durable half of the parity pair. `@kairos-es/read`'s in-memory store
7
+ * is the oracle; this backend must produce identical OBSERVABLE behaviour, and the
8
+ * shared `projection-store-contract-tests` suite runs unchanged against both. The
9
+ * one place the two genuinely differ is HOW the abort is achieved:
10
+ *
11
+ * - in memory, the guard is checked FIRST and a loss means `viewWrites` never ran
12
+ * (abort by omission — there is nothing to undo);
13
+ * - here, `viewWrites` runs first and a lost guard FAILS, so the transaction rolls
14
+ * the view writes back with it (abort by undo).
15
+ *
16
+ * Both satisfy the port's promise that a lost guard leaves no observable view
17
+ * effect, which is why ordering inside the transaction is a performance choice
18
+ * rather than a correctness one: running the guard last keeps the checkpoint row
19
+ * lock held only for the advance instead of for the whole batch's worth of view
20
+ * writes.
21
+ *
22
+ * Two contract inversions worth naming, both deliberate:
23
+ *
24
+ * 1. A `SqlError` becomes a `ProjectionStoreError` on the ERROR CHANNEL — the
25
+ * opposite of the event store, where an infrastructure fault is a DEFECT
26
+ * (`Effect.die`, ADR-0002). The read side has a supervisor above it whose whole
27
+ * job is telling "retry the subscription with backoff" from "give up and report
28
+ * a stalled projection", and a defect carries no such distinction. On the event
29
+ * store there is no supervisor: a caller cannot meaningfully retry a broken
30
+ * connection, so dying is right there and wrong here.
31
+ * 2. A lost guard is a tagged failure (`CheckpointSuperseded`), never a returned
32
+ * outcome value, and that failure is exactly what aborts the transaction. A
33
+ * returned value would only roll back if the caller remembered to convert it
34
+ * into a failure, and forgetting is silent double-application.
35
+ *
36
+ * The store closes over the `SqlClient` it acquired, so the returned
37
+ * `ProjectionStore.commit` adds NO `SqlClient` to the caller's `R` — a read model
38
+ * stays free of the view store's requirements, which is what lets one runner drive
39
+ * read models over different backends.
40
+ *
41
+ * What this module owns is the Effect wiring: the client, the transaction bracket,
42
+ * the error classification. The SQL itself and the parameter lists that feed it are
43
+ * `internal/statements.ts`, which owns the argument for why the two live together.
44
+ */
45
+ import { type ProjectionStore } from '@kairos-es/read';
46
+ import { Effect } from 'effect';
47
+ import { SqlClient } from 'effect/sql';
48
+ import { type ReadPostgresConfig } from './config.js';
49
+ /**
50
+ * Build a durable `ProjectionStore` over the ambient `SqlClient`.
51
+ *
52
+ * A factory `Effect` rather than a `Layer`, matching the in-memory store: the view
53
+ * store is selected PER MATERIALISATION, so it is a value handed to a read model, not
54
+ * a context service one store could claim process-wide. Per materialisation rather than
55
+ * per read model, because one read model may be materialised several times at once —
56
+ * a store from here beside an in-memory one, wired by `@kairos-es/read`'s
57
+ * `runProjections` — which is a shape a process-wide service could not express at all. It
58
+ * acquires the client ONCE and closes over it, which is what keeps `commit`'s `R`
59
+ * down to the caller's own.
60
+ *
61
+ * **The atomic bracket covers writes issued through THIS client.** The caller's
62
+ * `viewWrites` join the transaction only when they are issued through the same
63
+ * `SqlClient` the store was built from: `effect/sql` carries the transaction's
64
+ * connection in the Effect CONTEXT under a key that belongs to that one client
65
+ * instance, so a statement run inside `commit` finds the open transaction only if
66
+ * its client owns that key. The store's own `withoutTransforms()` copy shares the
67
+ * key, which is why the guarded advance and the caller's view writes land in one
68
+ * transaction; a write through any OTHER `SqlClient` instance — even one over the
69
+ * same database — or to a different database is NOT bracketed, is not rolled back
70
+ * on a lost guard, and the exactly-once property does not extend to it.
71
+ *
72
+ * `ensureCheckpointSchema(config)` must have run (in the caller's migrator or at
73
+ * boot) before the store is used; this factory performs no DDL, so it stays a pure
74
+ * runtime dependency on the client alone.
75
+ */
76
+ export declare const makeSqlProjectionStore: (config?: ReadPostgresConfig) => Effect.Effect<ProjectionStore, never, SqlClient.SqlClient>;
77
+ //# sourceMappingURL=projectionStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projectionStore.d.ts","sourceRoot":"","sources":["../../src/projectionStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAGH,OAAO,EAGL,KAAK,eAAe,EAErB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAmF5E;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,sBAAsB,GACjC,SAAQ,kBAAuB,KAC9B,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,KAAK,EAAE,SAAS,CAAC,SAAS,CA4IxD,CAAA"}