@kronos-ts/postgres 0.1.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 (62) hide show
  1. package/README.md +176 -0
  2. package/dist/adapter.d.ts +89 -0
  3. package/dist/adapter.d.ts.map +1 -0
  4. package/dist/adapter.js +29 -0
  5. package/dist/adapter.js.map +1 -0
  6. package/dist/adapters/bun-sql.d.ts +23 -0
  7. package/dist/adapters/bun-sql.d.ts.map +1 -0
  8. package/dist/adapters/bun-sql.js +175 -0
  9. package/dist/adapters/bun-sql.js.map +1 -0
  10. package/dist/adapters/pg.d.ts +24 -0
  11. package/dist/adapters/pg.d.ts.map +1 -0
  12. package/dist/adapters/pg.js +156 -0
  13. package/dist/adapters/pg.js.map +1 -0
  14. package/dist/adapters/postgres.d.ts +27 -0
  15. package/dist/adapters/postgres.d.ts.map +1 -0
  16. package/dist/adapters/postgres.js +99 -0
  17. package/dist/adapters/postgres.js.map +1 -0
  18. package/dist/advisory-locks.d.ts +56 -0
  19. package/dist/advisory-locks.d.ts.map +1 -0
  20. package/dist/advisory-locks.js +112 -0
  21. package/dist/advisory-locks.js.map +1 -0
  22. package/dist/criteria-sql.d.ts +29 -0
  23. package/dist/criteria-sql.d.ts.map +1 -0
  24. package/dist/criteria-sql.js +69 -0
  25. package/dist/criteria-sql.js.map +1 -0
  26. package/dist/errors.d.ts +30 -0
  27. package/dist/errors.d.ts.map +1 -0
  28. package/dist/errors.js +41 -0
  29. package/dist/errors.js.map +1 -0
  30. package/dist/index.d.ts +7 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +26 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/postgres-event-store.d.ts +52 -0
  35. package/dist/postgres-event-store.d.ts.map +1 -0
  36. package/dist/postgres-event-store.js +496 -0
  37. package/dist/postgres-event-store.js.map +1 -0
  38. package/dist/postgres-snapshot-store.d.ts +34 -0
  39. package/dist/postgres-snapshot-store.d.ts.map +1 -0
  40. package/dist/postgres-snapshot-store.js +122 -0
  41. package/dist/postgres-snapshot-store.js.map +1 -0
  42. package/dist/postgres.d.ts +34 -0
  43. package/dist/postgres.d.ts.map +1 -0
  44. package/dist/postgres.js +42 -0
  45. package/dist/postgres.js.map +1 -0
  46. package/dist/schema.d.ts +96 -0
  47. package/dist/schema.d.ts.map +1 -0
  48. package/dist/schema.js +174 -0
  49. package/dist/schema.js.map +1 -0
  50. package/package.json +93 -0
  51. package/src/adapter.ts +104 -0
  52. package/src/adapters/bun-sql.ts +228 -0
  53. package/src/adapters/pg.ts +189 -0
  54. package/src/adapters/postgres.ts +134 -0
  55. package/src/advisory-locks.ts +139 -0
  56. package/src/criteria-sql.ts +89 -0
  57. package/src/errors.ts +47 -0
  58. package/src/index.ts +56 -0
  59. package/src/postgres-event-store.ts +593 -0
  60. package/src/postgres-snapshot-store.ts +153 -0
  61. package/src/postgres.ts +66 -0
  62. package/src/schema.ts +204 -0
package/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # @kronos-ts/postgres
2
+
3
+ First-party Postgres extension for Kronos. Provides DCB-compliant event storage and snapshots via the framework's `eventStore` + `snapshotStore` slots.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ bun add @kronos-ts/postgres
9
+ # Choose one driver:
10
+ bun add pg # for pgAdapter (Node + Bun)
11
+ bun add postgres # for postgresAdapter (porsager — Node + Bun)
12
+ # bunSqlAdapter has no external dep (uses Bun.sql built-in, requires Bun >= 1.2)
13
+ ```
14
+
15
+ Requires Postgres 14+ (for `xid8` and `pg_snapshot_xmin`).
16
+
17
+ ## Usage
18
+
19
+ ```typescript
20
+ import { kronos } from "@kronos-ts/app"
21
+ import { postgres } from "@kronos-ts/postgres"
22
+ import { pgAdapter } from "@kronos-ts/postgres/adapters/pg"
23
+
24
+ const app = await kronos()
25
+ .use(postgres({
26
+ adapter: pgAdapter({ connectionString: "postgresql://user:pass@host/db" }),
27
+ // Default: auto-create schema on connect. Set false to manage migrations yourself.
28
+ bootstrap: true,
29
+ }))
30
+ .start()
31
+ ```
32
+
33
+ ### Accessing slots
34
+
35
+ In normal usage you don't touch the `eventStore` directly — command handlers, `load()`, and tracking processors handle it. If you need the raw store (e.g. for tests, scripts, or low-level access), capture it via a probe decorator before `start()` — `RunningApp` doesn't expose it on its public surface:
36
+
37
+ ```typescript
38
+ import type { App } from "@kronos-ts/app"
39
+ import type { EventStore } from "@kronos-ts/eventsourcing"
40
+ import { EventCriteria } from "@kronos-ts/messaging"
41
+ import { tag } from "@kronos-ts/common"
42
+
43
+ let eventStore: EventStore | undefined
44
+ const capture = (a: App) => {
45
+ a.decorate("eventStore", (inner) => { eventStore = inner; return inner })
46
+ }
47
+
48
+ const app = await kronos()
49
+ .use(capture)
50
+ .use(postgres({ adapter: pgAdapter({ connectionString }) }))
51
+ .start()
52
+
53
+ // 1. Source events for a state (criteria + optional start position).
54
+ // Returns the matched events plus a consistency marker.
55
+ const criteria = EventCriteria.havingTags(tag("order", "123"))
56
+ const { events: sourced, marker } = await eventStore!.source({ criteria })
57
+
58
+ // 2. Append with a DCB precondition. The marker locks in the prefix you read
59
+ // from source() — if anything matching `criteria` was appended in the
60
+ // meantime, AppendConditionError is thrown.
61
+ const newMarker = await eventStore!.append(newEvents, { criteria, marker })
62
+
63
+ // 3. Tail events. open() returns a pull-based MessageStream<SequencedEvent>,
64
+ // not an AsyncIterable. Pull with next() and register a callback for
65
+ // wake-ups when more events arrive.
66
+ const stream = eventStore!.open({ position: 0n, criteria })
67
+ stream.setCallback(() => {
68
+ while (stream.hasNextAvailable()) {
69
+ const seq = stream.next()
70
+ if (!seq) break
71
+ // seq.event is the EventMessage; seq.sequence is the bigint position.
72
+ }
73
+ })
74
+ // remember to stream.close() when you're done
75
+ ```
76
+
77
+ ## Adapters
78
+
79
+ Three reference adapters ship with the package:
80
+
81
+ | Adapter | Runtime | Driver | LISTEN support |
82
+ |---------|---------|--------|----------------|
83
+ | `pgAdapter` | Node, Bun | `pg` (node-postgres) 8.20+ | native |
84
+ | `postgresAdapter` | Node, Bun | `postgres` (porsager) 3.4+ | native |
85
+ | `bunSqlAdapter` | Bun 1.2+ | built-in `Bun.sql` | native (1.2+) or polling shim |
86
+
87
+ You can also implement your own — `PostgresAdapter` from `@kronos-ts/postgres` is the contract.
88
+
89
+ ### pgAdapter
90
+
91
+ ```typescript
92
+ import { pgAdapter } from "@kronos-ts/postgres/adapters/pg"
93
+
94
+ const adapter = pgAdapter({
95
+ connectionString: "postgresql://user:pass@host/db",
96
+ poolConfig: { max: 10 }, // optional pg.Pool overrides
97
+ })
98
+ ```
99
+
100
+ ### postgresAdapter
101
+
102
+ ```typescript
103
+ import { postgresAdapter } from "@kronos-ts/postgres/adapters/postgres"
104
+
105
+ const adapter = postgresAdapter({
106
+ connectionString: "postgresql://user:pass@host/db",
107
+ clientOptions: { idle_timeout: 20 }, // optional porsager/postgres options
108
+ })
109
+ ```
110
+
111
+ ### bunSqlAdapter
112
+
113
+ ```typescript
114
+ import { bunSqlAdapter } from "@kronos-ts/postgres/adapters/bun-sql"
115
+
116
+ // Bun runtime only — throws at connect() if run under Node
117
+ const adapter = bunSqlAdapter({
118
+ connectionString: "postgresql://user:pass@host/db",
119
+ })
120
+ ```
121
+
122
+ ## DCB Semantics
123
+
124
+ The engine implements [Dynamic Consistency Boundaries](https://dcb.events):
125
+ - **Atomic multi-tag conflict check** on append with advisory-lock serialization
126
+ - **Criteria-based sourcing** with `@>` (contains-all) tag semantics
127
+ - **Gap-free tailing** using `xid8` + `pg_snapshot_xmin(pg_current_snapshot())`
128
+
129
+ Concurrent writers on **disjoint tags** run in parallel (advisory-lock taxonomy permits this). Concurrent writers on the **same tag** serialise — exactly one commits; the other receives `AppendConditionError`.
130
+
131
+ ### Gap-free streaming
132
+
133
+ The `open()` streaming method uses a two-phase cursor to prevent the concurrent-commit gap bug:
134
+
135
+ - **Initial fetch:** `WHERE sequence_position > $cursor AND transaction_id < pg_snapshot_xmin(pg_current_snapshot())`
136
+ - **Subsequent fetches:** `WHERE (transaction_id, sequence_position) > ($xid, $pos) AND transaction_id < pg_snapshot_xmin(pg_current_snapshot())`
137
+
138
+ The `pg_snapshot_xmin` watermark hides events from in-flight transactions, preventing the cursor from advancing past events that haven't committed yet. When a slow transaction finally commits, its events become visible in the correct xid8 order — no gaps, no re-ordering.
139
+
140
+ ## Schema
141
+
142
+ `bootstrap: true` (default) auto-creates these tables on connect:
143
+ - `kronos_events` — append-only event log with `xid8` watermark, BIGSERIAL position, JSONB payload, GIN index on tags
144
+ - `kronos_snapshots` — composite-PK `(state_name, state_id)` with BYTEA payload
145
+
146
+ Custom table names: `postgres({ adapter, tableNames: { events: "my_events", snapshots: "my_snaps" } })`.
147
+
148
+ ## SQLSTATE
149
+
150
+ DCB violations raise SQLSTATE `KR001`. The adapter catches this and throws `AppendConditionError`. To detect violations from your own code:
151
+
152
+ ```typescript
153
+ import { AppendConditionError } from "@kronos-ts/postgres"
154
+
155
+ try {
156
+ await eventStore.append(events, condition)
157
+ } catch (e) {
158
+ if (e instanceof AppendConditionError) {
159
+ // Re-source the state to get a fresh marker, then retry.
160
+ }
161
+ }
162
+ ```
163
+
164
+ If you're working directly against the driver (bypassing the adapter), `isDcbViolation(err)` from `@kronos-ts/postgres` inspects a raw pg / postgres.js / Bun.sql error's `.code` field for the KR001 SQLSTATE.
165
+
166
+ ## FAQ
167
+
168
+ **Q: Can I run my own migrations?** Yes — set `bootstrap: false` and apply equivalent DDL via your own migration tooling. The reference DDL lives in `packages/extensions/postgres/src/schema.ts` (`buildEventsTableDDL`, `buildEventsIndexesDDL`, `buildSnapshotsTableDDL`).
169
+
170
+ **Q: PgBouncer compatibility?** Yes — the adapter uses xact-scoped advisory locks (never session-scoped), so transaction-pooling mode is safe.
171
+
172
+ **Q: Older Postgres support?** No. `xid8` requires PG14+. PG14 has been GA since 2021 and is universally supported across managed services.
173
+
174
+ **Q: Is Bun.sql LISTEN available in all Bun versions?** `Bun.SQL` was introduced in Bun 1.2. The `bunSqlAdapter` feature-detects native LISTEN; if unavailable (older Bun), it falls back to a 250ms polling shim. For production streaming workloads, use Bun 1.2+ or switch to `pgAdapter` / `postgresAdapter` which always have native LISTEN.
175
+
176
+ **Q: TokenStore?** Out of scope for this package. Compose with `@kronos-ts/drizzle` / `@kronos-ts/knex` / etc. for token-store needs.
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Driver-agnostic adapter contract for @kronos-ts/postgres.
3
+ *
4
+ * Per D-12.05 the package itself has zero direct dependency on any specific
5
+ * Postgres client library. Engine code (Plan 04) talks to this interface;
6
+ * adapter implementations (pg, postgres.js, Bun.sql) live under
7
+ * `./adapters/*` and are imported via package sub-path exports (D-12.08).
8
+ *
9
+ * Capability coverage per D-12.07:
10
+ * - query execution (parameterised) -> query / queryOne
11
+ * - transactions with isolation level -> transaction(isolationLevel, fn)
12
+ * - LISTEN-style subscription -> listen(channel, onNotification)
13
+ * - lifecycle -> connect / disconnect
14
+ *
15
+ * Error contract per D-12.12: SQLSTATE on thrown errors as `.code` (string).
16
+ * Adapter implementations MUST NOT swallow / rewrap SQLSTATE-bearing errors
17
+ * — the engine relies on `isDcbViolation(err)` reading `err.code === "KR001"`.
18
+ */
19
+ /**
20
+ * Postgres isolation levels the engine actually emits. SQL string values are
21
+ * the verbatim Postgres syntax used after `SET TRANSACTION ISOLATION LEVEL`,
22
+ * so adapters can interpolate directly without a lookup table.
23
+ */
24
+ export declare const IsolationLevel: {
25
+ readonly READ_COMMITTED: "READ COMMITTED";
26
+ readonly REPEATABLE_READ: "REPEATABLE READ";
27
+ readonly SERIALIZABLE: "SERIALIZABLE";
28
+ };
29
+ export type IsolationLevel = (typeof IsolationLevel)[keyof typeof IsolationLevel];
30
+ /** Plain row shape returned by query/queryOne. Adapter implementations cast
31
+ * driver-specific row objects to this. */
32
+ export type QueryRow = Record<string, unknown>;
33
+ /**
34
+ * Active in-transaction handle. Pinned to a single underlying connection so
35
+ * statements never interleave with sibling pool traffic. Intentionally NO
36
+ * nested-transaction method — Plan 04's engine does not need it and savepoints
37
+ * would invite confusion about which level a SQLSTATE error propagates from.
38
+ */
39
+ export interface PostgresAdapterTransaction {
40
+ query<R extends QueryRow = QueryRow>(sql: string, params?: unknown[]): Promise<R[]>;
41
+ }
42
+ /** Handle to a live LISTEN subscription. unlisten() unregisters + releases
43
+ * the dedicated connection (if any). */
44
+ export interface ListenSubscription {
45
+ unlisten(): Promise<void>;
46
+ }
47
+ /**
48
+ * The single interface the engine layer (Plan 04+) consumes. All concurrency
49
+ * (pool sizing, idle eviction, reconnect) lives below this seam — the engine
50
+ * code MUST NOT know about pools.
51
+ */
52
+ export interface PostgresAdapter {
53
+ /**
54
+ * Run a parameterised SQL statement on a pool-borrowed connection.
55
+ * Returns rows; empty array if none. Thrown errors carry SQLSTATE on
56
+ * `.code` unchanged so `isDcbViolation(err)` works at any call site.
57
+ */
58
+ query<R extends QueryRow = QueryRow>(sql: string, params?: unknown[]): Promise<R[]>;
59
+ /**
60
+ * Convenience for single-row queries. Returns `null` if zero rows.
61
+ * Throws if more than one row is returned (caller bug — use query()).
62
+ */
63
+ queryOne<R extends QueryRow = QueryRow>(sql: string, params?: unknown[]): Promise<R | null>;
64
+ /**
65
+ * Open a transaction at the given isolation level, run `fn` against a
66
+ * pinned-connection handle, and COMMIT on resolution or ROLLBACK on
67
+ * rejection. If `fn` throws, the original error is re-thrown after
68
+ * ROLLBACK; rollback failures do NOT mask the original.
69
+ *
70
+ * The framework's AppendTransaction has a synchronous `rollback(): void`
71
+ * (see packages/eventsourcing/src/event-storage-engine.ts) — the
72
+ * implementation translates that into a fire-and-forget reject inside
73
+ * this method's promise, NOT an awaited rollback round-trip.
74
+ */
75
+ transaction<T>(isolationLevel: IsolationLevel, fn: (tx: PostgresAdapterTransaction) => Promise<T>): Promise<T>;
76
+ /**
77
+ * Subscribe to `LISTEN <channel>` on a dedicated long-lived connection.
78
+ * Adapter implementations that lack a real LISTEN channel (e.g. Bun.sql
79
+ * pre-1.x) MAY implement a polling shim — the subscription handle is the
80
+ * same regardless. Plan 05's streaming open() uses this to wake up
81
+ * tailers immediately instead of waiting for a poll tick.
82
+ */
83
+ listen(channel: string, onNotification: (payload: string | undefined) => void): Promise<ListenSubscription>;
84
+ /** Initialise pool / verify reachability. Idempotent. */
85
+ connect(): Promise<void>;
86
+ /** Drain pool, close LISTEN connections, release sockets. Idempotent. */
87
+ disconnect(): Promise<void>;
88
+ }
89
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;CAIjB,CAAA;AACV,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,CAAA;AAEjF;2CAC2C;AAC3C,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAE9C;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IACzC,KAAK,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAA;CACpF;AAED;yCACyC;AACzC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,KAAK,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAA;IAEnF;;;OAGG;IACH,QAAQ,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;IAE3F;;;;;;;;;;OAUG;IACH,WAAW,CAAC,CAAC,EACX,cAAc,EAAE,cAAc,EAC9B,EAAE,EAAE,CAAC,EAAE,EAAE,0BAA0B,KAAK,OAAO,CAAC,CAAC,CAAC,GACjD,OAAO,CAAC,CAAC,CAAC,CAAA;IAEb;;;;;;OAMG;IACH,MAAM,CACJ,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,GACpD,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAE9B,yDAAyD;IACzD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAExB,yEAAyE;IACzE,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5B"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Driver-agnostic adapter contract for @kronos-ts/postgres.
3
+ *
4
+ * Per D-12.05 the package itself has zero direct dependency on any specific
5
+ * Postgres client library. Engine code (Plan 04) talks to this interface;
6
+ * adapter implementations (pg, postgres.js, Bun.sql) live under
7
+ * `./adapters/*` and are imported via package sub-path exports (D-12.08).
8
+ *
9
+ * Capability coverage per D-12.07:
10
+ * - query execution (parameterised) -> query / queryOne
11
+ * - transactions with isolation level -> transaction(isolationLevel, fn)
12
+ * - LISTEN-style subscription -> listen(channel, onNotification)
13
+ * - lifecycle -> connect / disconnect
14
+ *
15
+ * Error contract per D-12.12: SQLSTATE on thrown errors as `.code` (string).
16
+ * Adapter implementations MUST NOT swallow / rewrap SQLSTATE-bearing errors
17
+ * — the engine relies on `isDcbViolation(err)` reading `err.code === "KR001"`.
18
+ */
19
+ /**
20
+ * Postgres isolation levels the engine actually emits. SQL string values are
21
+ * the verbatim Postgres syntax used after `SET TRANSACTION ISOLATION LEVEL`,
22
+ * so adapters can interpolate directly without a lookup table.
23
+ */
24
+ export const IsolationLevel = {
25
+ READ_COMMITTED: "READ COMMITTED",
26
+ REPEATABLE_READ: "REPEATABLE READ",
27
+ SERIALIZABLE: "SERIALIZABLE",
28
+ };
29
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,cAAc,EAAE,gBAAgB;IAChC,eAAe,EAAE,iBAAiB;IAClC,YAAY,EAAE,cAAc;CACpB,CAAA"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Bun.sql adapter for @kronos-ts/postgres.
3
+ *
4
+ * Import via the sub-path (Bun runtime only):
5
+ * import { bunSqlAdapter } from "@kronos-ts/postgres/adapters/bun-sql"
6
+ *
7
+ * Requires Bun >= 1.2 for Bun.SQL (sql.transaction()). LISTEN support is
8
+ * feature-detected; if Bun.SQL lacks native LISTEN on the running version,
9
+ * the adapter falls back to a 250ms polling shim — slower wake-up but
10
+ * correct semantics.
11
+ *
12
+ * This file uses `globalThis as { Bun?: ... }` access to avoid a hard
13
+ * compile-time reference to Bun's global, so the package can ship under
14
+ * Node (where this file would never be imported) without TypeScript
15
+ * compilation errors. Users importing the sub-path under Node will get
16
+ * a clear runtime error from the connect() call.
17
+ */
18
+ import type { PostgresAdapter } from "../adapter.js";
19
+ export interface BunSqlAdapterConfig {
20
+ readonly connectionString: string;
21
+ }
22
+ export declare function bunSqlAdapter(config: BunSqlAdapterConfig): PostgresAdapter;
23
+ //# sourceMappingURL=bun-sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bun-sql.d.ts","sourceRoot":"","sources":["../../src/adapters/bun-sql.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EACV,eAAe,EAIhB,MAAM,eAAe,CAAA;AAGtB,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;CAClC;AA6ED,wBAAgB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,eAAe,CA0H1E"}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Bun.sql adapter for @kronos-ts/postgres.
3
+ *
4
+ * Import via the sub-path (Bun runtime only):
5
+ * import { bunSqlAdapter } from "@kronos-ts/postgres/adapters/bun-sql"
6
+ *
7
+ * Requires Bun >= 1.2 for Bun.SQL (sql.transaction()). LISTEN support is
8
+ * feature-detected; if Bun.SQL lacks native LISTEN on the running version,
9
+ * the adapter falls back to a 250ms polling shim — slower wake-up but
10
+ * correct semantics.
11
+ *
12
+ * This file uses `globalThis as { Bun?: ... }` access to avoid a hard
13
+ * compile-time reference to Bun's global, so the package can ship under
14
+ * Node (where this file would never be imported) without TypeScript
15
+ * compilation errors. Users importing the sub-path under Node will get
16
+ * a clear runtime error from the connect() call.
17
+ */
18
+ import { IsolationLevel } from "../adapter.js";
19
+ /**
20
+ * Bun.SQL surfaces SQLSTATE in `err.errno` (not `err.code`). The adapter
21
+ * contract (D-12.12) requires SQLSTATE on `.code` so that `isDcbViolation(err)`
22
+ * works uniformly across all adapters.
23
+ *
24
+ * This helper normalises Bun.SQL errors: if `err.code === "ERR_POSTGRES_SERVER_ERROR"`
25
+ * and `err.errno` is a non-empty string, we copy `errno` to `code` before
26
+ * re-throwing, giving callers the SQLSTATE they expect on `.code`.
27
+ */
28
+ /**
29
+ * Bun.SQL's `.unsafe(text, params)` does not auto-encode JS arrays as Postgres
30
+ * array literals — single-element arrays get unwrapped to their scalar value
31
+ * and bound as TEXT, which fails when the column is TEXT[]. We pre-encode any
32
+ * plain Array param to the canonical `{"v1","v2"}` literal so it round-trips
33
+ * to TEXT[] / array operators (`@>`, `ANY`) correctly.
34
+ *
35
+ * Uint8Array / Buffer are not plain arrays (Array.isArray returns false), so
36
+ * BYTEA params remain untouched.
37
+ */
38
+ function encodeArrayParam(arr) {
39
+ const escaped = arr.map((v) => {
40
+ if (v === null || v === undefined)
41
+ return "NULL";
42
+ const s = String(v);
43
+ return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
44
+ });
45
+ return `{${escaped.join(",")}}`;
46
+ }
47
+ function normalizeParams(params) {
48
+ if (!params)
49
+ return [];
50
+ return params.map((p) => (Array.isArray(p) ? encodeArrayParam(p) : p));
51
+ }
52
+ function normalizeBunSqlError(err) {
53
+ if (typeof err === "object" &&
54
+ err !== null &&
55
+ err.code === "ERR_POSTGRES_SERVER_ERROR" &&
56
+ typeof err.errno === "string" &&
57
+ err.errno.length > 0) {
58
+ ;
59
+ err.code = err.errno;
60
+ }
61
+ throw err;
62
+ }
63
+ function getBunSql() {
64
+ const g = globalThis;
65
+ if (!g.Bun?.SQL) {
66
+ throw new Error("bunSqlAdapter requires the Bun runtime with built-in Bun.SQL (>= 1.2). " +
67
+ "Detected non-Bun runtime — use pgAdapter or postgresAdapter instead.");
68
+ }
69
+ return g.Bun.SQL;
70
+ }
71
+ export function bunSqlAdapter(config) {
72
+ let sql;
73
+ let disconnected = false;
74
+ function getInstance() {
75
+ if (!sql) {
76
+ const SQL = getBunSql();
77
+ sql = new SQL({ url: config.connectionString });
78
+ }
79
+ return sql;
80
+ }
81
+ return {
82
+ async connect() {
83
+ const inst = getInstance();
84
+ const rows = await inst.unsafe("SELECT 1 AS ok").catch(normalizeBunSqlError);
85
+ if (rows[0]?.ok !== 1) {
86
+ throw new Error("bunSqlAdapter.connect: unexpected health-check response");
87
+ }
88
+ },
89
+ async disconnect() {
90
+ if (disconnected)
91
+ return;
92
+ disconnected = true;
93
+ if (sql) {
94
+ // Bun.SQL exposes both close() and end() — try end() first (graceful
95
+ // drain), fall back to close() if end is unavailable.
96
+ try {
97
+ if (typeof sql.end === "function") {
98
+ await sql.end();
99
+ }
100
+ else {
101
+ await sql.close();
102
+ }
103
+ }
104
+ catch {
105
+ /* ignore disconnect errors */
106
+ }
107
+ sql = undefined;
108
+ }
109
+ },
110
+ async query(text, params) {
111
+ const inst = getInstance();
112
+ return inst.unsafe(text, normalizeParams(params)).catch(normalizeBunSqlError);
113
+ },
114
+ async queryOne(text, params) {
115
+ const rows = await this.query(text, params);
116
+ if (rows.length === 0)
117
+ return null;
118
+ if (rows.length > 1) {
119
+ throw new Error(`bunSqlAdapter.queryOne: more than one row returned (got ${rows.length}). Use query() for multi-row results.`);
120
+ }
121
+ return rows[0] ?? null;
122
+ },
123
+ async transaction(isolationLevel, fn) {
124
+ const inst = getInstance();
125
+ // Bun.SQL.begin(isolation, fn) starts a transaction at the given isolation
126
+ // level. The callback receives a scoped sql instance pinned to the
127
+ // underlying connection. SQLSTATE errors from within the transaction are
128
+ // normalized (errno -> code) via normalizeBunSqlError before propagating.
129
+ return inst.begin(`ISOLATION LEVEL ${isolationLevel}`, async (txSql) => {
130
+ const tx = {
131
+ async query(text, params) {
132
+ return txSql.unsafe(text, normalizeParams(params)).catch(normalizeBunSqlError);
133
+ },
134
+ };
135
+ return fn(tx);
136
+ }).catch(normalizeBunSqlError);
137
+ },
138
+ async listen(channel, onNotification) {
139
+ if (!/^[A-Za-z0-9_]+$/.test(channel)) {
140
+ throw new Error(`bunSqlAdapter.listen: channel name must match /^[A-Za-z0-9_]+$/, got: ${channel}`);
141
+ }
142
+ const inst = getInstance();
143
+ // Feature-detect native LISTEN
144
+ if (typeof inst.listen === "function") {
145
+ const sub = await inst.listen(channel, (p) => onNotification(p || undefined));
146
+ return {
147
+ async unlisten() {
148
+ await sub.unlisten();
149
+ },
150
+ };
151
+ }
152
+ // Polling fallback for Bun versions without native LISTEN.
153
+ // The fallback fires the callback on every tick with undefined payload,
154
+ // which causes the streaming engine to re-poll. This is coarse but
155
+ // semantically correct — the caller (open() in postgres-event-store.ts)
156
+ // already falls back to 250ms polling as a safety net, so this shim
157
+ // merely triggers additional pump cycles.
158
+ let stopped = false;
159
+ const tick = setInterval(() => {
160
+ if (stopped) {
161
+ clearInterval(tick);
162
+ return;
163
+ }
164
+ onNotification(undefined);
165
+ }, 250);
166
+ return {
167
+ async unlisten() {
168
+ stopped = true;
169
+ clearInterval(tick);
170
+ },
171
+ };
172
+ },
173
+ };
174
+ }
175
+ //# sourceMappingURL=bun-sql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bun-sql.js","sourceRoot":"","sources":["../../src/adapters/bun-sql.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * pg (node-postgres 8.20+) reference adapter for @kronos-ts/postgres.
3
+ *
4
+ * Import via the sub-path:
5
+ * import { pgAdapter } from "@kronos-ts/postgres/adapters/pg"
6
+ *
7
+ * Implements every PostgresAdapter method per the contract in
8
+ * ../adapter.ts. Pool sizing follows pg defaults; override via the
9
+ * `poolConfig` field if needed.
10
+ *
11
+ * LISTEN uses a dedicated long-lived PoolClient pinned outside the pool
12
+ * (never released) so notification delivery is not racing pool eviction.
13
+ * That client is closed by `disconnect()`.
14
+ */
15
+ import { type PoolConfig } from "pg";
16
+ import type { PostgresAdapter } from "../adapter.js";
17
+ export interface PgAdapterConfig {
18
+ /** Standard libpq URI: postgresql://user:pass@host:port/db */
19
+ readonly connectionString: string;
20
+ /** Optional pg.Pool config overrides (max connections, idleTimeoutMillis, etc.). */
21
+ readonly poolConfig?: Omit<PoolConfig, "connectionString">;
22
+ }
23
+ export declare function pgAdapter(config: PgAdapterConfig): PostgresAdapter;
24
+ //# sourceMappingURL=pg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg.d.ts","sourceRoot":"","sources":["../../src/adapters/pg.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,IAAI,CAAA;AAC3D,OAAO,KAAK,EACV,eAAe,EAIhB,MAAM,eAAe,CAAA;AAGtB,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,oFAAoF;IACpF,QAAQ,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAA;CAC3D;AAOD,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAwJlE"}