@kairos-es/store-postgres 1.0.0-alpha.2 → 1.0.0-alpha.4

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 (39) hide show
  1. package/README.md +13 -5
  2. package/dist/cjs/ensureSchema.js +6 -1
  3. package/dist/cjs/ensureSchema.js.map +1 -1
  4. package/dist/cjs/internal/ddl.js +29 -12
  5. package/dist/cjs/internal/ddl.js.map +1 -1
  6. package/dist/cjs/internal/matchSql.js +46 -4
  7. package/dist/cjs/internal/matchSql.js.map +1 -1
  8. package/dist/cjs/internal/readPlan.js +8 -4
  9. package/dist/cjs/internal/readPlan.js.map +1 -1
  10. package/dist/cjs/internal/transport.js +27 -4
  11. package/dist/cjs/internal/transport.js.map +1 -1
  12. package/dist/cjs/store.js.map +1 -1
  13. package/dist/dts/ensureSchema.d.ts +6 -1
  14. package/dist/dts/ensureSchema.d.ts.map +1 -1
  15. package/dist/dts/internal/ddl.d.ts.map +1 -1
  16. package/dist/dts/internal/matchSql.d.ts +26 -2
  17. package/dist/dts/internal/matchSql.d.ts.map +1 -1
  18. package/dist/dts/internal/readPlan.d.ts.map +1 -1
  19. package/dist/dts/internal/transport.d.ts +17 -7
  20. package/dist/dts/internal/transport.d.ts.map +1 -1
  21. package/dist/dts/store.d.ts.map +1 -1
  22. package/dist/esm/ensureSchema.js +6 -1
  23. package/dist/esm/ensureSchema.js.map +1 -1
  24. package/dist/esm/internal/ddl.js +30 -13
  25. package/dist/esm/internal/ddl.js.map +1 -1
  26. package/dist/esm/internal/matchSql.js +44 -2
  27. package/dist/esm/internal/matchSql.js.map +1 -1
  28. package/dist/esm/internal/readPlan.js +8 -4
  29. package/dist/esm/internal/readPlan.js.map +1 -1
  30. package/dist/esm/internal/transport.js +26 -3
  31. package/dist/esm/internal/transport.js.map +1 -1
  32. package/dist/esm/store.js.map +1 -1
  33. package/package.json +2 -2
  34. package/src/ensureSchema.ts +6 -1
  35. package/src/internal/ddl.ts +32 -12
  36. package/src/internal/matchSql.ts +52 -1
  37. package/src/internal/readPlan.ts +8 -4
  38. package/src/internal/transport.ts +46 -7
  39. package/src/store.ts +2 -4
@@ -8,7 +8,7 @@
8
8
  * where transport code lives, and the two per-column type maps (`EventJson`,
9
9
  * `EventRow`) sit next to each other rather than in two files.
10
10
  *
11
- * The 5-column event contract stays single-sourced on `EventColumnName`
11
+ * The event column contract stays single-sourced on `EventColumnName`
12
12
  * (`internal/matchSql.ts`), the one column-NAME descriptor: both type maps here
13
13
  * are mapped types over it, so a column rename in `EVENT_COLUMNS` is a COMPILE
14
14
  * error in this module, never a silent transport drift the lax read boundary
@@ -19,7 +19,13 @@
19
19
  * (`toEventJson`) serialises `data` as base64 text and domain time as an ISO
20
20
  * string, both restored in SQL.
21
21
  */
22
- import type { DcbEvent, Position, Query, SequencedEvent } from '@kairos-es/core'
22
+ import type {
23
+ DcbEvent,
24
+ Metadata,
25
+ Position,
26
+ Query,
27
+ SequencedEvent,
28
+ } from '@kairos-es/core'
23
29
  import { DateTime, Encoding } from 'effect'
24
30
  import type { EventColumnName } from './matchSql'
25
31
 
@@ -54,9 +60,11 @@ export const toQueryItemsJson = (query: Query): ReadonlyArray<QueryItemJson> =>
54
60
  /**
55
61
  * The per-column TS types of the transport JSON shape — one entry per
56
62
  * `EVENT_COLUMNS` name. `data` is base64 TEXT and `occurred_at` an ISO string
57
- * (both restored in SQL: `decode(…, 'base64')` / `timestamptz`); the rest map
58
- * straight across. Kept as a plain map so `EventJson` below is a mapped type over
59
- * the ONE column descriptor rather than a hand-restated column list.
63
+ * (both restored in SQL: `decode(…, 'base64')` / `timestamptz`); `metadata`
64
+ * travels as a nested JSON object, which `jsonb_to_recordset` hands to the
65
+ * insert as `jsonb` untouched; the rest map straight across. Kept as a plain
66
+ * map so `EventJson` below is a mapped type over the ONE column descriptor
67
+ * rather than a hand-restated column list.
60
68
  */
61
69
  interface EventJsonColumns {
62
70
  readonly type: string
@@ -64,6 +72,7 @@ interface EventJsonColumns {
64
72
  readonly tags: ReadonlyArray<string>
65
73
  readonly uuid: string
66
74
  readonly occurred_at: string
75
+ readonly metadata: Metadata
67
76
  }
68
77
 
69
78
  /**
@@ -86,12 +95,16 @@ export const toEventJson = (event: DcbEvent): EventJson => ({
86
95
  tags: [...event.tags],
87
96
  uuid: event.uuid,
88
97
  occurred_at: DateTime.formatIso(event.occurredAt),
98
+ metadata: event.metadata,
89
99
  })
90
100
 
91
101
  /**
92
102
  * The per-column TS types of a raw driver row — one entry per `EVENT_COLUMNS`
93
103
  * name. These are node-postgres's decoded JS types: `data` a `Buffer` (or null
94
- * for a payload-less event), `occurred_at` a JS `Date`, `tags` a `string[]`. `id`
104
+ * for a payload-less event), `occurred_at` a JS `Date`, `tags` a `string[]`,
105
+ * `metadata` whatever node-postgres parses a `jsonb` value into — declared
106
+ * `unknown`, because `jsonb` is the one column here whose SQL type does not
107
+ * fix its JS shape, and `metadataFrom` below is where it is checked. `id`
95
108
  * (the `bigserial` position) is NOT here — it is prepended by the read projection
96
109
  * and absent from the events recordset, so it sits outside the shared descriptor.
97
110
  */
@@ -101,6 +114,7 @@ interface EventRowColumns {
101
114
  readonly tags: ReadonlyArray<string>
102
115
  readonly uuid: string
103
116
  readonly occurred_at: Date
117
+ readonly metadata: unknown
104
118
  }
105
119
 
106
120
  /**
@@ -114,10 +128,34 @@ export type EventRow = { readonly id: string } & {
114
128
  readonly [K in EventColumnName]: EventRowColumns[K]
115
129
  }
116
130
 
131
+ /**
132
+ * The `metadata` column as the event's bag, checked to be a JSON OBJECT.
133
+ *
134
+ * The store only ever writes an object, but `jsonb` admits any JSON value — an
135
+ * array, a string, a number, a boolean, JSON `null` (which `NOT NULL` does not
136
+ * exclude) — so a hand-written row can hold one. Such a row is corrupt, and
137
+ * handing an event whose `metadata` is not a bag to every projection
138
+ * downstream is worse than stopping at the read. This is the CONTAINER check
139
+ * only; the shape rule itself has one home, core's `Metadata` doc. The VALUES
140
+ * are deliberately not re-checked as strings, because a value check on read
141
+ * alone would make an event the store accepted unreadable for ever.
142
+ */
143
+ const metadataFrom = (value: unknown): Metadata => {
144
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
145
+ throw new Error(
146
+ `@kairos-es/store-postgres: the \`metadata\` column did not hold a JSON object (got ${JSON.stringify(value)}).`,
147
+ )
148
+ }
149
+ return value as Metadata
150
+ }
151
+
117
152
  /**
118
153
  * Map a raw main-table row to a core `SequencedEvent`. `data` NULL → empty bytes
119
154
  * (a payload-less event round-trips to a zero-length `Uint8Array`); `occurred_at`
120
- * Date → `DateTime.Utc`; `id` string → branded `Position`.
155
+ * Date → `DateTime.Utc`; `id` string → branded `Position`; `metadata` through
156
+ * `metadataFrom`. The column is `NOT NULL DEFAULT '{}'`, so there is no absent
157
+ * case to reconcile: an event appended with an empty bag reads back as `{}` on
158
+ * this engine exactly as on the in-memory oracle.
121
159
  *
122
160
  * The branded scalars (`type`, `tags`, `uuid`, `position`) are minted by trusted
123
161
  * `as`-cast, NOT by re-running `EventType.make` / `Tag.make` etc. This is
@@ -136,6 +174,7 @@ export const rowToSequenced = (row: EventRow): SequencedEvent => ({
136
174
  tags: row.tags as unknown as DcbEvent['tags'],
137
175
  uuid: row.uuid as DcbEvent['uuid'],
138
176
  occurredAt: DateTime.unsafeFromDate(row.occurred_at),
177
+ metadata: metadataFrom(row.metadata),
139
178
  },
140
179
  position: BigInt(row.id) as Position,
141
180
  })
package/src/store.ts CHANGED
@@ -30,10 +30,8 @@
30
30
  * the guarded statement to meet its own row and read zero. Surfacing rather than
31
31
  * RESOLVING is what makes that free: an engine willing to re-issue an undecided
32
32
  * append needs a way to tell its own committed row from a rival's BEFORE it does,
33
- * or the re-issue is the very thing that manufactures the phantom — see
34
- * `@kairos-es/store-sqlite`, which resolves one by identity and re-issues only
35
- * when identity answers absent. So the missing verify path here is a consequence
36
- * of that choice rather than an omission from it.
33
+ * or the re-issue is the very thing that manufactures the phantom. So the missing
34
+ * verify path here is a consequence of that choice rather than an omission from it.
37
35
  *
38
36
  * This module is WIRING + APPEND: the JSON/JSONB transport (the event/query-item
39
37
  * (de)serialisers and row mapping) lives in `internal/transport.ts` and the