@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.
- package/README.md +13 -5
- package/dist/cjs/ensureSchema.js +6 -1
- package/dist/cjs/ensureSchema.js.map +1 -1
- package/dist/cjs/internal/ddl.js +29 -12
- package/dist/cjs/internal/ddl.js.map +1 -1
- package/dist/cjs/internal/matchSql.js +46 -4
- package/dist/cjs/internal/matchSql.js.map +1 -1
- package/dist/cjs/internal/readPlan.js +8 -4
- package/dist/cjs/internal/readPlan.js.map +1 -1
- package/dist/cjs/internal/transport.js +27 -4
- package/dist/cjs/internal/transport.js.map +1 -1
- package/dist/cjs/store.js.map +1 -1
- package/dist/dts/ensureSchema.d.ts +6 -1
- package/dist/dts/ensureSchema.d.ts.map +1 -1
- package/dist/dts/internal/ddl.d.ts.map +1 -1
- package/dist/dts/internal/matchSql.d.ts +26 -2
- package/dist/dts/internal/matchSql.d.ts.map +1 -1
- package/dist/dts/internal/readPlan.d.ts.map +1 -1
- package/dist/dts/internal/transport.d.ts +17 -7
- package/dist/dts/internal/transport.d.ts.map +1 -1
- package/dist/dts/store.d.ts.map +1 -1
- package/dist/esm/ensureSchema.js +6 -1
- package/dist/esm/ensureSchema.js.map +1 -1
- package/dist/esm/internal/ddl.js +30 -13
- package/dist/esm/internal/ddl.js.map +1 -1
- package/dist/esm/internal/matchSql.js +44 -2
- package/dist/esm/internal/matchSql.js.map +1 -1
- package/dist/esm/internal/readPlan.js +8 -4
- package/dist/esm/internal/readPlan.js.map +1 -1
- package/dist/esm/internal/transport.js +26 -3
- package/dist/esm/internal/transport.js.map +1 -1
- package/dist/esm/store.js.map +1 -1
- package/package.json +2 -2
- package/src/ensureSchema.ts +6 -1
- package/src/internal/ddl.ts +32 -12
- package/src/internal/matchSql.ts +52 -1
- package/src/internal/readPlan.ts +8 -4
- package/src/internal/transport.ts +46 -7
- 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
|
|
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 {
|
|
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`);
|
|
58
|
-
*
|
|
59
|
-
* the
|
|
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[]
|
|
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
|
|
34
|
-
*
|
|
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
|