@kairos-es/store-postgres 1.0.0-alpha.3 → 1.0.0-alpha.5
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 +61 -29
- package/dist/cjs/clients.js +34 -24
- package/dist/cjs/clients.js.map +1 -1
- package/dist/cjs/config.js +9 -15
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/ensureSchema.js +8 -3
- package/dist/cjs/ensureSchema.js.map +1 -1
- package/dist/cjs/index.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 +57 -4
- package/dist/cjs/internal/matchSql.js.map +1 -1
- package/dist/cjs/internal/readPlan.js +33 -5
- package/dist/cjs/internal/readPlan.js.map +1 -1
- package/dist/cjs/internal/transport.js +42 -15
- package/dist/cjs/internal/transport.js.map +1 -1
- package/dist/cjs/store.js +30 -25
- package/dist/cjs/store.js.map +1 -1
- package/dist/dts-cjs/clients.d.ts +66 -0
- package/dist/dts-cjs/clients.d.ts.map +1 -0
- package/dist/{dts → dts-cjs}/config.d.ts +9 -19
- package/dist/{dts → dts-cjs}/config.d.ts.map +1 -1
- package/dist/{dts → dts-cjs}/ensureSchema.d.ts +8 -3
- package/dist/dts-cjs/ensureSchema.d.ts.map +1 -0
- package/dist/{dts → dts-cjs}/index.d.ts +4 -3
- package/dist/dts-cjs/index.d.ts.map +1 -0
- package/dist/{dts → dts-cjs}/internal/ddl.d.ts +1 -1
- package/dist/{dts → dts-cjs}/internal/ddl.d.ts.map +1 -1
- package/dist/{dts → dts-cjs}/internal/matchSql.d.ts +26 -2
- package/dist/dts-cjs/internal/matchSql.d.ts.map +1 -0
- package/dist/{dts → dts-cjs}/internal/readPlan.d.ts +1 -1
- package/dist/{dts → dts-cjs}/internal/readPlan.d.ts.map +1 -1
- package/dist/{dts → dts-cjs}/internal/transport.d.ts +34 -19
- package/dist/dts-cjs/internal/transport.d.ts.map +1 -0
- package/dist/dts-cjs/package.json +3 -0
- package/dist/dts-cjs/store.d.ts.map +1 -0
- package/dist/dts-esm/clients.d.ts +66 -0
- package/dist/dts-esm/clients.d.ts.map +1 -0
- package/dist/dts-esm/config.d.ts +48 -0
- package/dist/dts-esm/config.d.ts.map +1 -0
- package/dist/dts-esm/ensureSchema.d.ts +40 -0
- package/dist/dts-esm/ensureSchema.d.ts.map +1 -0
- package/dist/dts-esm/index.d.ts +42 -0
- package/dist/dts-esm/index.d.ts.map +1 -0
- package/dist/dts-esm/internal/ddl.d.ts +174 -0
- package/dist/dts-esm/internal/ddl.d.ts.map +1 -0
- package/dist/dts-esm/internal/matchSql.d.ts +164 -0
- package/dist/dts-esm/internal/matchSql.d.ts.map +1 -0
- package/dist/dts-esm/internal/readPlan.d.ts +72 -0
- package/dist/dts-esm/internal/readPlan.d.ts.map +1 -0
- package/dist/dts-esm/internal/transport.d.ts +131 -0
- package/dist/dts-esm/internal/transport.d.ts.map +1 -0
- package/dist/dts-esm/package.json +3 -0
- package/dist/dts-esm/store.d.ts +17 -0
- package/dist/dts-esm/store.d.ts.map +1 -0
- package/dist/esm/clients.js +34 -24
- package/dist/esm/clients.js.map +1 -1
- package/dist/esm/config.js +9 -15
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/ensureSchema.js +8 -3
- package/dist/esm/ensureSchema.js.map +1 -1
- package/dist/esm/index.js +4 -3
- package/dist/esm/index.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 +55 -2
- package/dist/esm/internal/matchSql.js.map +1 -1
- package/dist/esm/internal/readPlan.js +32 -5
- package/dist/esm/internal/readPlan.js.map +1 -1
- package/dist/esm/internal/transport.js +42 -15
- package/dist/esm/internal/transport.js.map +1 -1
- package/dist/esm/store.js +30 -25
- package/dist/esm/store.js.map +1 -1
- package/package.json +13 -11
- package/src/clients.ts +43 -31
- package/src/config.ts +12 -13
- package/src/ensureSchema.ts +8 -3
- package/src/index.ts +4 -3
- package/src/internal/ddl.ts +33 -13
- package/src/internal/matchSql.ts +63 -1
- package/src/internal/readPlan.ts +11 -6
- package/src/internal/transport.ts +71 -24
- package/src/store.ts +35 -28
- package/dist/dts/clients.d.ts +0 -34
- package/dist/dts/clients.d.ts.map +0 -1
- package/dist/dts/ensureSchema.d.ts.map +0 -1
- package/dist/dts/index.d.ts.map +0 -1
- package/dist/dts/internal/matchSql.d.ts.map +0 -1
- package/dist/dts/internal/transport.d.ts.map +0 -1
- package/dist/dts/store.d.ts.map +0 -1
- /package/dist/{dts → dts-cjs}/store.d.ts +0 -0
package/README.md
CHANGED
|
@@ -13,34 +13,45 @@ database without interference.
|
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
16
|
-
`@kairos-es/core`, `effect`, and
|
|
17
|
-
|
|
18
|
-
per ADR-0003):
|
|
16
|
+
`@kairos-es/core`, `effect`, and `@effect/sql-pg` are peer dependencies (so
|
|
17
|
+
the app owns the single shared Effect + `SqlClient` instance, per ADR-0003):
|
|
19
18
|
|
|
20
19
|
```sh
|
|
21
20
|
pnpm add @kairos-es/store-postgres@alpha @kairos-es/core@alpha \
|
|
22
|
-
@effect/sql
|
|
23
|
-
@effect/experimental@^0.61.0 effect@^3.22.0
|
|
21
|
+
@effect/sql-pg@^4.0.5 effect@^4.0.5
|
|
24
22
|
```
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
consumers.
|
|
24
|
+
The generic SQL modules (`SqlClient`, `Migrator`, `SqlError`) and the
|
|
25
|
+
`Reactivity` service the pooled client requires ship inside `effect` itself, as
|
|
26
|
+
`effect/sql` and `effect/reactivity`, so nothing else has to be installed.
|
|
30
27
|
|
|
31
28
|
## Schema: `ensureSchema` as a migration
|
|
32
29
|
|
|
33
30
|
The package ships **no migrator**. `ensureSchema(config)` is a single idempotent
|
|
34
|
-
`Effect<void, SqlError, SqlClient>` that creates
|
|
35
|
-
|
|
36
|
-
FUNCTION`, so re-running it is
|
|
37
|
-
package only provides the idempotent **payload**.
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
`Effect<void, SqlError, SqlClient>` that creates every object the store owns and
|
|
32
|
+
replaces its append functions. Every statement is `CREATE … IF NOT EXISTS` or
|
|
33
|
+
`CREATE OR REPLACE FUNCTION`, so re-running it is safe. Migration
|
|
34
|
+
**orchestration** is yours; the package only provides the idempotent **payload**.
|
|
35
|
+
|
|
36
|
+
**Upgrading.** A re-run replaces the append functions but never alters an existing
|
|
37
|
+
table. So register this version's `ensureSchema` again as a NEW migration on every
|
|
38
|
+
upgrade, even one that adds no column: a migrator never re-runs one it has already
|
|
39
|
+
applied, and an append function left behind keeps the older version's body. When a
|
|
40
|
+
release adds a column, its release notes give the `ALTER TABLE` statement, and it
|
|
41
|
+
runs before that `ensureSchema`; an append function left behind silently ignores
|
|
42
|
+
the new column.
|
|
43
|
+
|
|
44
|
+
**One statement per call.** `@effect/sql-pg` 4 has no simple-query path: every
|
|
45
|
+
statement goes through the extended protocol, which takes one command. So a string
|
|
46
|
+
holding several statements, such as a SQL migration file, fails with `cannot insert
|
|
47
|
+
multiple commands into a prepared statement`, and `prepare: false` does not change
|
|
48
|
+
that. Split the file and run each statement on its own.
|
|
49
|
+
|
|
50
|
+
Drop it straight into an `effect/sql` migrator as a one-liner:
|
|
40
51
|
|
|
41
52
|
```ts
|
|
42
|
-
import { Migrator } from '@effect/sql'
|
|
43
53
|
import { ensureSchema } from '@kairos-es/store-postgres'
|
|
54
|
+
import { Migrator } from 'effect/sql'
|
|
44
55
|
|
|
45
56
|
const config = { schema: 'public', tablePrefix: 'dcb_events' }
|
|
46
57
|
|
|
@@ -49,14 +60,13 @@ const migrations = Migrator.fromRecord({
|
|
|
49
60
|
})
|
|
50
61
|
```
|
|
51
62
|
|
|
52
|
-
Because `ensureSchema` requires the **generic** `SqlClient`
|
|
53
|
-
store-owned client
|
|
63
|
+
Because `ensureSchema` requires the **generic** `SqlClient` service (not the
|
|
64
|
+
store-owned client services), it slots into any migrator or boot sequence that
|
|
54
65
|
already provides a `SqlClient`.
|
|
55
66
|
|
|
56
67
|
Or run it directly once at boot, over whatever `SqlClient` you have wired:
|
|
57
68
|
|
|
58
69
|
```ts
|
|
59
|
-
import { SqlClient } from '@effect/sql'
|
|
60
70
|
import { PgClient } from '@effect/sql-pg'
|
|
61
71
|
import { ensureSchema } from '@kairos-es/store-postgres'
|
|
62
72
|
import { Effect, Layer, Redacted } from 'effect'
|
|
@@ -71,24 +81,34 @@ await Effect.runPromise(program.pipe(Effect.provide(SqlLive)))
|
|
|
71
81
|
```
|
|
72
82
|
|
|
73
83
|
`ensureSchema` must have run before the store `layer` is used; the `layer` itself
|
|
74
|
-
performs no DDL and depends only on the two client
|
|
84
|
+
performs no DDL and depends only on the two client services.
|
|
75
85
|
|
|
76
86
|
## Two-endpoint wiring (Neon)
|
|
77
87
|
|
|
78
|
-
`@effect/sql-pg` exposes a single `PgClient`/`SqlClient`
|
|
79
|
-
defines **two distinct
|
|
88
|
+
`@effect/sql-pg` exposes a single `PgClient`/`SqlClient` service, so this
|
|
89
|
+
package defines **two distinct services**, each holding a `PgClient` value:
|
|
80
90
|
|
|
81
91
|
- `PgClientPooled` — reads, the append function, and `NOTIFY`. Point it at your
|
|
82
92
|
pooled endpoint.
|
|
83
93
|
- `PgClientDirect` — used **only** by `subscribe`'s `LISTEN`. Point it at a
|
|
84
94
|
**direct** (non-pooler) endpoint.
|
|
85
95
|
|
|
86
|
-
Why the split matters on Neon: the built-in `listen`
|
|
87
|
-
|
|
88
|
-
(`…-pooler…`) lands that connection on the PgBouncer
|
|
89
|
-
**silently drops `LISTEN`/`NOTIFY`** — the subscription would
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
Why the split matters on Neon: the built-in `listen` reserves a connection from
|
|
97
|
+
its own client's pool, so it listens on whatever host that client was given. A
|
|
98
|
+
Neon **pooled** endpoint (`…-pooler…`) lands that connection on the PgBouncer
|
|
99
|
+
front door, which **silently drops `LISTEN`/`NOTIFY`** — the subscription would
|
|
100
|
+
never wake, and would deliver only at the poll interval. Pointing
|
|
101
|
+
`PgClientDirect` at Neon's direct endpoint fixes it. `subscribe` is the only
|
|
102
|
+
method that touches `PgClientDirect`.
|
|
103
|
+
|
|
104
|
+
Each live subscription holds one `PgClientDirect` connection for as long as it
|
|
105
|
+
runs, so that client's `maxConnections` (default 10) is how many subscriptions
|
|
106
|
+
can be listening at once. One past it still delivers, at the poll interval,
|
|
107
|
+
until a connection frees up. Size `directLayer` for your subscriber count:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
directLayer({ url, maxConnections: 50 })
|
|
111
|
+
```
|
|
92
112
|
|
|
93
113
|
```ts
|
|
94
114
|
import {
|
|
@@ -110,7 +130,19 @@ const clients = Layer.merge(
|
|
|
110
130
|
const store = layer(config).pipe(Layer.provide(clients))
|
|
111
131
|
```
|
|
112
132
|
|
|
113
|
-
|
|
133
|
+
Both layers take `@effect/sql-pg`'s own pool config, so its options pass
|
|
134
|
+
straight through. That driver caches named prepared statements by default, and a
|
|
135
|
+
transaction-mode pooler, such as the PgBouncer behind Neon's `-pooler` endpoint,
|
|
136
|
+
may run each statement on a different server connection from the one that
|
|
137
|
+
prepared it. Behind such a pooler, unless it documents support for
|
|
138
|
+
protocol-level prepared statements, pass `prepare: false` to the client pointed
|
|
139
|
+
at it:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
pooledLayer({ url: Redacted.make(process.env.NEON_POOLED_URL!), prepare: false })
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
On a plain (non-Neon) Postgres, point both services at the **same** URL — the split
|
|
114
146
|
costs nothing there:
|
|
115
147
|
|
|
116
148
|
```ts
|
package/dist/cjs/clients.js
CHANGED
|
@@ -4,59 +4,69 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.pooledLayer = exports.directLayer = exports.PgClientPooled = exports.PgClientDirect = void 0;
|
|
7
|
-
var _experimental = require("@effect/experimental");
|
|
8
7
|
var _sqlPg = require("@effect/sql-pg");
|
|
9
8
|
var _effect = require("effect");
|
|
9
|
+
var _reactivity = require("effect/reactivity");
|
|
10
10
|
/**
|
|
11
11
|
* The TWO store-owned Postgres clients — the "Neon trap" mitigation (ADR-0002).
|
|
12
12
|
*
|
|
13
|
-
* `@effect/sql-pg` exposes a single `PgClient` (and `SqlClient`)
|
|
13
|
+
* `@effect/sql-pg` exposes a single `PgClient` (and `SqlClient`) service; a
|
|
14
14
|
* `PgClient.layer` claims BOTH. If we built two `PgClient.layer`s they would
|
|
15
|
-
* collide on that one
|
|
16
|
-
* define two DISTINCT `Context.
|
|
15
|
+
* collide on that one key — the second would shadow the first. So instead we
|
|
16
|
+
* define two DISTINCT `Context.Service`s, each HOLDING a `PgClient` value:
|
|
17
17
|
*
|
|
18
18
|
* - `PgClientPooled` — the pooled client used for reads, the append function,
|
|
19
|
-
* and `notify`.
|
|
19
|
+
* and `notify`. Its statements borrow a connection from the client's pool per
|
|
20
|
+
* query.
|
|
20
21
|
* - `PgClientDirect` — a client used ONLY by `subscribe`'s `listen`. Its
|
|
21
|
-
* built-in `listen`
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* built-in `listen` RESERVES a connection from that client's own pool and
|
|
23
|
+
* holds it for the life of the subscription, so the listening session lives
|
|
24
|
+
* on whatever host the client was configured with. On a Neon POOLED endpoint
|
|
25
|
+
* (`…-pooler…`) that session is a PgBouncer front-door connection, which
|
|
26
|
+
* SILENTLY DROPS `LISTEN`/`NOTIFY` — the subscription would simply never
|
|
25
27
|
* wake. Pointing `PgClientDirect` at Neon's DIRECT (non-pooler) endpoint fixes
|
|
26
28
|
* it. `subscribe` is the ONLY method that touches `PgClientDirect`.
|
|
27
29
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
+
* Because each live subscription holds one of `PgClientDirect`'s pooled
|
|
31
|
+
* connections until it closes, that client's `maxConnections` is also the number
|
|
32
|
+
* of subscriptions that can be listening at once. A subscription past it waits
|
|
33
|
+
* for a connection on its wake fibre only — delivery still proceeds at the poll
|
|
34
|
+
* rate meanwhile (see `store.ts`) — so size the direct client for the expected
|
|
35
|
+
* subscriber count rather than for query throughput.
|
|
36
|
+
*
|
|
37
|
+
* For a plain (non-Neon) Postgres both services point at the same URL; the split
|
|
38
|
+
* costs nothing there. The two-service design is what makes the Neon fix a
|
|
30
39
|
* deployment-time wiring choice rather than a code change.
|
|
31
40
|
*/
|
|
32
41
|
|
|
33
42
|
/**
|
|
34
|
-
* The pooled client
|
|
43
|
+
* The pooled client service: reads, the append function, and `notify`. Holds a
|
|
35
44
|
* `PgClient` value rather than being a `PgClient.layer`, so it never collides
|
|
36
|
-
* with the direct client on the shared `PgClient`/`SqlClient`
|
|
45
|
+
* with the direct client on the shared `PgClient`/`SqlClient` key.
|
|
37
46
|
*/
|
|
38
|
-
class PgClientPooled extends /*#__PURE__*/_effect.Context.
|
|
47
|
+
class PgClientPooled extends /*#__PURE__*/_effect.Context.Service()('@kairos-es/store-postgres/PgClientPooled') {}
|
|
39
48
|
/**
|
|
40
|
-
* The direct (non-pooler) client
|
|
41
|
-
* at a direct endpoint on Neon so `LISTEN`/`NOTIFY` is not dropped by the
|
|
49
|
+
* The direct (non-pooler) client service: the `subscribe` listener ONLY. Point
|
|
50
|
+
* this at a direct endpoint on Neon so `LISTEN`/`NOTIFY` is not dropped by the
|
|
51
|
+
* pooler.
|
|
42
52
|
*/
|
|
43
53
|
exports.PgClientPooled = PgClientPooled;
|
|
44
|
-
class PgClientDirect extends /*#__PURE__*/_effect.Context.
|
|
54
|
+
class PgClientDirect extends /*#__PURE__*/_effect.Context.Service()('@kairos-es/store-postgres/PgClientDirect') {}
|
|
45
55
|
/**
|
|
46
|
-
* Build a `PgClientPooled` layer from a `PgClient` config. We construct the
|
|
47
|
-
* client via `PgClient.make` (scoped) and place it in our OWN
|
|
48
|
-
* `Reactivity.layer` (which `PgClient.make` requires) — exactly what the
|
|
56
|
+
* Build a `PgClientPooled` layer from a `PgClient` pool config. We construct the
|
|
57
|
+
* client via `PgClient.make` (scoped) and place it in our OWN service, then
|
|
58
|
+
* provide `Reactivity.layer` (which `PgClient.make` requires) — exactly what the
|
|
49
59
|
* built-in `PgClient.layer` does internally, minus the collision-prone claim on
|
|
50
|
-
* the shared `PgClient`
|
|
60
|
+
* the shared `PgClient` key.
|
|
51
61
|
*/
|
|
52
62
|
exports.PgClientDirect = PgClientDirect;
|
|
53
|
-
const pooledLayer = config => _effect.Layer.
|
|
63
|
+
const pooledLayer = config => _effect.Layer.effect(PgClientPooled, _sqlPg.PgClient.make(config)).pipe(_effect.Layer.provide(_reactivity.Reactivity.layer));
|
|
54
64
|
/**
|
|
55
|
-
* Build a `PgClientDirect` layer from a `PgClient` config. Same construction as
|
|
65
|
+
* Build a `PgClientDirect` layer from a `PgClient` pool config. Same construction as
|
|
56
66
|
* `pooledLayer`; the two differ only in which endpoint the caller points them
|
|
57
67
|
* at (and that only matters on Neon).
|
|
58
68
|
*/
|
|
59
69
|
exports.pooledLayer = pooledLayer;
|
|
60
|
-
const directLayer = config => _effect.Layer.
|
|
70
|
+
const directLayer = config => _effect.Layer.effect(PgClientDirect, _sqlPg.PgClient.make(config)).pipe(_effect.Layer.provide(_reactivity.Reactivity.layer));
|
|
61
71
|
exports.directLayer = directLayer;
|
|
62
72
|
//# sourceMappingURL=clients.js.map
|
package/dist/cjs/clients.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clients.js","names":["
|
|
1
|
+
{"version":3,"file":"clients.js","names":["_sqlPg","require","_effect","_reactivity","PgClientPooled","Context","Service","exports","PgClientDirect","pooledLayer","config","Layer","effect","PgClient","make","pipe","provide","Reactivity","layer","directLayer"],"sources":["../../src/clients.ts"],"sourcesContent":[null],"mappings":";;;;;;AA+BA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,OAAA,GAAAD,OAAA;AACA,IAAAE,WAAA,GAAAF,OAAA;AAjCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA;;;;;AAKM,MAAOG,cAAe,sBAAQC,eAAO,CAACC,OAAO,EAGhD,CAAC,0CAA0C,CAAC;AAE/C;;;;;AAAAC,OAAA,CAAAH,cAAA,GAAAA,cAAA;AAKM,MAAOI,cAAe,sBAAQH,eAAO,CAACC,OAAO,EAGhD,CAAC,0CAA0C,CAAC;AAE/C;;;;;;;AAAAC,OAAA,CAAAC,cAAA,GAAAA,cAAA;AAOO,MAAMC,WAAW,GACtBC,MAA6B,IAE7BC,aAAK,CAACC,MAAM,CAACR,cAAc,EAAES,eAAQ,CAACC,IAAI,CAACJ,MAAM,CAAC,CAAC,CAACK,IAAI,CACtDJ,aAAK,CAACK,OAAO,CAACC,sBAAU,CAACC,KAAK,CAAC,CAChC;AAEH;;;;;AAAAX,OAAA,CAAAE,WAAA,GAAAA,WAAA;AAKO,MAAMU,WAAW,GACtBT,MAA6B,IAE7BC,aAAK,CAACC,MAAM,CAACJ,cAAc,EAAEK,eAAQ,CAACC,IAAI,CAACJ,MAAM,CAAC,CAAC,CAACK,IAAI,CACtDJ,aAAK,CAACK,OAAO,CAACC,sBAAU,CAACC,KAAK,CAAC,CAChC;AAAAX,OAAA,CAAAY,WAAA,GAAAA,WAAA","ignoreList":[]}
|
package/dist/cjs/config.js
CHANGED
|
@@ -34,7 +34,7 @@ var _ddl = require("./internal/ddl.js");
|
|
|
34
34
|
* `sql(identifier)` dot-splits, so an embedded dot would silently re-partition
|
|
35
35
|
* the name into the wrong schema/object.
|
|
36
36
|
*/
|
|
37
|
-
const IdentifierAtom = /*#__PURE__*/_effect.Schema.String.
|
|
37
|
+
const IdentifierAtom = /*#__PURE__*/_effect.Schema.String.check(/*#__PURE__*/_effect.Schema.isNonEmpty(), /*#__PURE__*/_effect.Schema.isPattern(/^[^.]+$/, {
|
|
38
38
|
identifier: 'PostgresIdentifierAtom',
|
|
39
39
|
description: 'a non-empty identifier atom containing no "."'
|
|
40
40
|
}));
|
|
@@ -65,29 +65,23 @@ const IdentifierAtom = /*#__PURE__*/_effect.Schema.String.pipe(/*#__PURE__*/_eff
|
|
|
65
65
|
* fetches before looping.
|
|
66
66
|
*/
|
|
67
67
|
const PostgresStoreConfig = exports.PostgresStoreConfig = /*#__PURE__*/_effect.Schema.Struct({
|
|
68
|
-
// `
|
|
68
|
+
// `optionalKey` keeps the decoded type at `key?: T` (never `T | undefined`),
|
|
69
69
|
// matching the repo's `exactOptionalPropertyTypes` discipline — an absent key,
|
|
70
70
|
// not an explicit `undefined`.
|
|
71
|
-
schema: _effect.Schema.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
tablePrefix: _effect.Schema.optionalWith(IdentifierAtom, {
|
|
75
|
-
exact: true
|
|
76
|
-
}),
|
|
77
|
-
lockTimeout: _effect.Schema.optionalWith(_effect.Schema.Number.pipe(_effect.Schema.nonNegative(), _effect.Schema.finite()), {
|
|
78
|
-
exact: true
|
|
79
|
-
}),
|
|
71
|
+
schema: _effect.Schema.optionalKey(IdentifierAtom),
|
|
72
|
+
tablePrefix: _effect.Schema.optionalKey(IdentifierAtom),
|
|
73
|
+
lockTimeout: _effect.Schema.optionalKey(_effect.Schema.Number.check(_effect.Schema.isGreaterThanOrEqualTo(0), _effect.Schema.isFinite())),
|
|
80
74
|
// The two subscribe knobs are core's field definitions, not this engine's: see
|
|
81
75
|
// `subscriptionTuningFields`.
|
|
82
76
|
..._core.subscriptionTuningFields
|
|
83
|
-
}).
|
|
77
|
+
}).check(/*#__PURE__*/
|
|
84
78
|
// Cross-field guard: the derived table/index/function/channel names must all
|
|
85
79
|
// fit Postgres's 63-byte identifier limit. Returning the message string fails
|
|
86
|
-
// the decode with it; `undefined
|
|
87
|
-
_effect.Schema.
|
|
80
|
+
// the decode with it; `undefined` passes.
|
|
81
|
+
_effect.Schema.makeFilter(config => (0, _ddl.oversizedIdentifier)(config)));
|
|
88
82
|
/**
|
|
89
83
|
* Decode an unknown value into a validated `PostgresStoreConfig`, throwing a
|
|
90
|
-
* `
|
|
84
|
+
* `SchemaError` on any violation. Used by `layer`/`ensureSchema` so a bad config
|
|
91
85
|
* fails at construction time.
|
|
92
86
|
*/
|
|
93
87
|
const decodePostgresStoreConfig = exports.decodePostgresStoreConfig = /*#__PURE__*/_effect.Schema.decodeUnknownSync(PostgresStoreConfig);
|
package/dist/cjs/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","names":["_core","require","_effect","_ddl","IdentifierAtom","Schema","String","
|
|
1
|
+
{"version":3,"file":"config.js","names":["_core","require","_effect","_ddl","IdentifierAtom","Schema","String","check","isNonEmpty","isPattern","identifier","description","PostgresStoreConfig","exports","Struct","schema","optionalKey","tablePrefix","lockTimeout","Number","isGreaterThanOrEqualTo","isFinite","subscriptionTuningFields","makeFilter","config","oversizedIdentifier","decodePostgresStoreConfig","decodeUnknownSync"],"sources":["../../src/config.ts"],"sourcesContent":[null],"mappings":";;;;;;AAoBA,IAAAA,KAAA,GAAAC,OAAA;AACA,IAAAC,OAAA,GAAAD,OAAA;AACA,IAAAE,IAAA,GAAAF,OAAA;AAtBA;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAMA,MAAMG,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;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BO,MAAMC,mBAAmB,GAAAC,OAAA,CAAAD,mBAAA,gBAAGP,cAAM,CAACS,MAAM,CAAC;EAC/C;EACA;EACA;EACAC,MAAM,EAAEV,cAAM,CAACW,WAAW,CAACZ,cAAc,CAAC;EAC1Ca,WAAW,EAAEZ,cAAM,CAACW,WAAW,CAACZ,cAAc,CAAC;EAC/Cc,WAAW,EAAEb,cAAM,CAACW,WAAW,CAC7BX,cAAM,CAACc,MAAM,CAACZ,KAAK,CAACF,cAAM,CAACe,sBAAsB,CAAC,CAAC,CAAC,EAAEf,cAAM,CAACgB,QAAQ,EAAE,CAAC,CACzE;EACD;EACA;EACA,GAAGC;CACJ,CAAC,CAACf,KAAK;AACN;AACA;AACA;AACAF,cAAM,CAACkB,UAAU,CAAEC,MAAM,IAAK,IAAAC,wBAAmB,EAACD,MAAM,CAAC,CAAC,CAC3D;AAGD;;;;;AAKO,MAAME,yBAAyB,GAAAb,OAAA,CAAAa,yBAAA,gBACpCrB,cAAM,CAACsB,iBAAiB,CAACf,mBAAmB,CAAC","ignoreList":[]}
|
package/dist/cjs/ensureSchema.js
CHANGED
|
@@ -4,8 +4,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.ensureSchema = 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
|
* This is deliberately NOT a migrator. It is a single `Effect<void, SqlError,
|
|
15
15
|
* SqlClient>` that creates (or refreshes) every object a store owns: both
|
|
16
16
|
* tables, both indexes, and both plpgsql functions. It requires the GENERIC
|
|
17
|
-
* `SqlClient`
|
|
17
|
+
* `SqlClient` service — NOT `PgClientPooled` — precisely so a consumer can drop it
|
|
18
18
|
* straight into THEIR OWN migrator, e.g.:
|
|
19
19
|
*
|
|
20
20
|
* ```ts
|
|
@@ -28,7 +28,12 @@ var _ddl = require("./internal/ddl.js");
|
|
|
28
28
|
* consumer, this package only provides the idempotent PAYLOAD.
|
|
29
29
|
*
|
|
30
30
|
* Idempotence: every statement is `CREATE … IF NOT EXISTS` or `CREATE OR REPLACE
|
|
31
|
-
* FUNCTION`, so re-running
|
|
31
|
+
* FUNCTION`, so re-running it is safe. It is NOT a no-op across versions: a
|
|
32
|
+
* re-run replaces the append functions with this version's, and does NOT alter
|
|
33
|
+
* an existing table, so a column a newer version adds must be added by hand
|
|
34
|
+
* before the re-run (the release notes give the statement). A store whose
|
|
35
|
+
* table gained a column but whose functions were never replaced keeps writing
|
|
36
|
+
* through the old ones, which do not know the column. Names come from
|
|
32
37
|
* `sql(identifier)` (via the pre-quoted builder in `internal/ddl.ts`); the DDL
|
|
33
38
|
* bodies carry no bound values (pure schema), and the append functions keep
|
|
34
39
|
* their runtime VALUES as bound parameters, never interpolated.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ensureSchema.js","names":["
|
|
1
|
+
{"version":3,"file":"ensureSchema.js","names":["_effect","require","_sql","_config","_ddl","ensureSchema","config","Effect","gen","sql","SqlClient","names","resolveNames","decodePostgresStoreConfig","all","runDdl","discard","exports"],"sources":["../../src/ensureSchema.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,YAAY,GACvBC,MAA2B,IAE3BC,cAAM,CAACC,GAAG,CAAC,aAAS;EAClB,MAAMC,GAAG,GAAG,OAAOC,cAAS,CAACA,SAAS;EACtC;EACA;EACA,MAAMC,KAAK,GAAG,IAAAC,iBAAY,EAAC,IAAAC,iCAAyB,EAACP,MAAM,CAAC,CAAC;EAC7D;EACA,OAAOC,cAAM,CAACO,GAAG,CAAC,IAAAC,WAAM,EAACN,GAAG,EAAEE,KAAK,CAAC,EAAE;IAAEK,OAAO,EAAE;EAAI,CAAE,CAAC;AAC1D,CAAC,CAAC;AAAAC,OAAA,CAAAZ,YAAA,GAAAA,YAAA","ignoreList":[]}
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["_clients","require","_ensureSchema","_store"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","names":["_clients","require","_ensureSchema","_store"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,IAAAA,QAAA,GAAAC,OAAA;AAOA,IAAAC,aAAA,GAAAD,OAAA;AACA,IAAAE,MAAA,GAAAF,OAAA","ignoreList":[]}
|
package/dist/cjs/internal/ddl.js
CHANGED
|
@@ -129,6 +129,16 @@ const lockClause = names => `LOCK TABLE ${quoteQualified(names.mainTable)} IN EX
|
|
|
129
129
|
* preserves batch order, and `RETURNING id, tags` is captured into a temp
|
|
130
130
|
* result set so the tag fan-out and the head can both read it. `data` arrives as
|
|
131
131
|
* a base64 text field and is `decode(…, 'base64')`-restored to `bytea`.
|
|
132
|
+
*
|
|
133
|
+
* The recordset definition, its projection, the `INSERT INTO (…)` target list and
|
|
134
|
+
* the `SELECT` list are all derived from the one `EVENT_COLUMNS` descriptor in
|
|
135
|
+
* `matchSql.ts`, never written out here. The last two must agree POSITIONALLY and a
|
|
136
|
+
* mismatch is not an error, so deriving both from one walk is what keeps them from
|
|
137
|
+
* falling out of step; a column added without a source is a compile error there.
|
|
138
|
+
* What the compiler does NOT reach is whether the source READS the right field, or
|
|
139
|
+
* whether the column comes back out on the read; the round-trip contract group run
|
|
140
|
+
* against this engine is what proves a new column, and only with a value distinct
|
|
141
|
+
* per column, which is what makes a swap visible.
|
|
132
142
|
*/
|
|
133
143
|
const insertFanoutNotify = (names, options = {}) => {
|
|
134
144
|
const main = quoteQualified(names.mainTable);
|
|
@@ -146,18 +156,18 @@ const insertFanoutNotify = (names, options = {}) => {
|
|
|
146
156
|
IF new_head IS NOT NULL THEN
|
|
147
157
|
PERFORM pg_notify(${channel}, '1');
|
|
148
158
|
END IF;` : '';
|
|
149
|
-
|
|
150
|
-
|
|
159
|
+
const ne = _matchSql.RECORDSET_ALIAS;
|
|
160
|
+
return ` WITH ${ne} AS (
|
|
161
|
+
SELECT ${_matchSql.eventColumnList}, ordinality
|
|
151
162
|
FROM ROWS FROM(
|
|
152
163
|
jsonb_to_recordset(new_events) AS (${_matchSql.eventRecordsetColumns})
|
|
153
164
|
) WITH ORDINALITY
|
|
154
165
|
),
|
|
155
166
|
inserted AS (
|
|
156
|
-
INSERT INTO ${main} (
|
|
157
|
-
SELECT
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
ORDER BY ne.ordinality
|
|
167
|
+
INSERT INTO ${main} (${_matchSql.eventColumnList})
|
|
168
|
+
SELECT ${_matchSql.eventInsertSelectList}
|
|
169
|
+
FROM ${ne}
|
|
170
|
+
ORDER BY ${ne}.ordinality
|
|
161
171
|
RETURNING id, tags
|
|
162
172
|
),
|
|
163
173
|
fanned AS (
|
|
@@ -206,9 +216,12 @@ const conditionalAppendBody = (names, options = {}) => {
|
|
|
206
216
|
// its ITEMS — none at all, an item carrying no tags, an item carrying tags —
|
|
207
217
|
// which between them cover every `Query` value the grammar can express, so a new
|
|
208
218
|
// SHAPE changes which SQL the read compiles without changing what this body has
|
|
209
|
-
// to match. The exposure is the GRAMMAR itself: a
|
|
210
|
-
// meaning for an existing one, is something this body ignores rather than
|
|
211
|
-
// on, and no `switch` over the shape union would have caught that either.
|
|
219
|
+
// to match. The exposure is the GRAMMAR itself: a field ADDED to `QueryItem`, or a
|
|
220
|
+
// new meaning for an existing one, is something this body ignores rather than
|
|
221
|
+
// fails on, and no `switch` over the shape union would have caught that either. A
|
|
222
|
+
// key the grammar does NOT define never reaches this body: core's
|
|
223
|
+
// `servableQueryFault` refuses it at the contract boundary, which is what keeps a
|
|
224
|
+
// smuggled predicate from being dropped here in silence.
|
|
212
225
|
const after = 'COALESCE(after_id, 0)';
|
|
213
226
|
return `
|
|
214
227
|
DECLARE
|
|
@@ -378,14 +391,18 @@ const ddlStatements = names => {
|
|
|
378
391
|
// positions — a rolled-back insert permanently consumes its id, which is
|
|
379
392
|
// fine: ordering is by id alone and the store is never made gapless.
|
|
380
393
|
// `data` is NULLABLE (a payload-less event is valid); `occurred_at` is
|
|
381
|
-
// informational domain time, never an ordering key.
|
|
394
|
+
// informational domain time, never an ordering key. `metadata` is the
|
|
395
|
+
// event's cleartext context bag (ADR-0017): `jsonb` only so a containment
|
|
396
|
+
// index stays possible without a rewrite, though nothing queries it and no
|
|
397
|
+
// index is built; `NOT NULL DEFAULT '{}'` gives an empty bag one shape.
|
|
382
398
|
`CREATE TABLE IF NOT EXISTS ${main} (
|
|
383
399
|
id bigserial,
|
|
384
400
|
type text NOT NULL,
|
|
385
401
|
data bytea,
|
|
386
402
|
tags text[] NOT NULL,
|
|
387
403
|
uuid text NOT NULL,
|
|
388
|
-
occurred_at timestamptz NOT NULL
|
|
404
|
+
occurred_at timestamptz NOT NULL,
|
|
405
|
+
metadata jsonb NOT NULL DEFAULT '{}'::jsonb
|
|
389
406
|
) WITH (${AUTOVACUUM_WITH});`,
|
|
390
407
|
// Covering unique index: uniqueness on id plus INCLUDE(type) so the read
|
|
391
408
|
// path's id→type lookups are index-only.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ddl.js","names":["_matchSql","require","DEFAULT_SCHEMA","exports","DEFAULT_TABLE_PREFIX","MAX_IDENTIFIER_BYTES","utf8ByteLength","value","TextEncoder","encode","length","derivedAtoms","config","schema","tablePrefix","replace","oversizedIdentifier","label","atom","bytes","undefined","resolveNames","problem","Error","mainTable","tagTable","mainIndex","tagIndex","appendFn","appendUnconditionalFn","channel","quoteQualified","name","split","map","join","quoteAtom","quoteLiteral","AUTOVACUUM_WITH","lockClause","names","insertFanoutNotify","options","main","tag","notify","eventRecordsetColumns","conditionalAppendBody","lock","after","taggedMatchChain","queryItemsExpr","afterExpr","allowedTypesPredicate","qualifiedRows","unconditionalAppendBody","appendFunctionStatement","qualifiedName","argList","body","kind","ddlStatements","runDdl","sql","statement","unsafe"],"sources":["../../../src/internal/ddl.ts"],"sourcesContent":[null],"mappings":";;;;;;AA4BA,IAAAA,SAAA,GAAAC,OAAA;
|
|
1
|
+
{"version":3,"file":"ddl.js","names":["_matchSql","require","DEFAULT_SCHEMA","exports","DEFAULT_TABLE_PREFIX","MAX_IDENTIFIER_BYTES","utf8ByteLength","value","TextEncoder","encode","length","derivedAtoms","config","schema","tablePrefix","replace","oversizedIdentifier","label","atom","bytes","undefined","resolveNames","problem","Error","mainTable","tagTable","mainIndex","tagIndex","appendFn","appendUnconditionalFn","channel","quoteQualified","name","split","map","join","quoteAtom","quoteLiteral","AUTOVACUUM_WITH","lockClause","names","insertFanoutNotify","options","main","tag","notify","ne","RECORDSET_ALIAS","eventColumnList","eventRecordsetColumns","eventInsertSelectList","conditionalAppendBody","lock","after","taggedMatchChain","queryItemsExpr","afterExpr","allowedTypesPredicate","qualifiedRows","unconditionalAppendBody","appendFunctionStatement","qualifiedName","argList","body","kind","ddlStatements","runDdl","sql","statement","unsafe"],"sources":["../../../src/internal/ddl.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.wildcardReadSql = exports.typeOnlyReadSql = exports.taggedReadSql = exports.taggedMatchChain = exports.readColumns = exports.qualifiedRows = exports.eventRecordsetColumns = exports.allowedTypesPredicate = void 0;
|
|
6
|
+
exports.wildcardReadSql = exports.typeOnlyReadSql = exports.taggedReadSql = exports.taggedMatchChain = exports.readColumns = exports.qualifiedRows = exports.eventRecordsetColumns = exports.eventInsertSelectList = exports.eventColumnList = exports.allowedTypesPredicate = exports.RECORDSET_ALIAS = void 0;
|
|
7
7
|
/**
|
|
8
8
|
* Single source of truth for the tag-first match SQL.
|
|
9
9
|
*
|
|
@@ -38,10 +38,11 @@ exports.wildcardReadSql = exports.typeOnlyReadSql = exports.taggedReadSql = expo
|
|
|
38
38
|
* that union, so a column rename here is a COMPILE error there, not a silent drift
|
|
39
39
|
* the lax read boundary (ADR-0006) would only surface as corrupt data under test.
|
|
40
40
|
*/
|
|
41
|
-
const EVENT_COLUMNS = [['type', 'text'], ['data', 'text'], ['tags', 'text[]'], ['uuid', 'text'], ['occurred_at', 'timestamptz']];
|
|
41
|
+
const EVENT_COLUMNS = [['type', 'text'], ['data', 'text'], ['tags', 'text[]'], ['uuid', 'text'], ['occurred_at', 'timestamptz'], ['metadata', 'jsonb']];
|
|
42
42
|
/**
|
|
43
43
|
* The `jsonb_to_recordset(new_events) AS (…)` column definitions —
|
|
44
|
-
* `type text, data text, tags text[], uuid text, occurred_at timestamptz
|
|
44
|
+
* `type text, data text, tags text[], uuid text, occurred_at timestamptz,
|
|
45
|
+
* metadata jsonb`. The
|
|
45
46
|
* transport `data` is base64 TEXT (restored to `bytea` by `decode(…, 'base64')`
|
|
46
47
|
* at insert), which is why it is `text` here, not `bytea`.
|
|
47
48
|
*/
|
|
@@ -52,13 +53,65 @@ const eventRecordsetColumns = exports.eventRecordsetColumns = /*#__PURE__*/EVENT
|
|
|
52
53
|
* cannot drift from the recordset.
|
|
53
54
|
*/
|
|
54
55
|
const readColumns = (prefix = '') => [`${prefix}id`, ...EVENT_COLUMNS.map(([name]) => `${prefix}${name}`)].join(', ');
|
|
56
|
+
/**
|
|
57
|
+
* The alias the append body gives the expanded `new_events` recordset — the `ne`
|
|
58
|
+
* CTE in `ddl.ts` — which every expression in `EVENT_COLUMN_SOURCE` reads from.
|
|
59
|
+
* Exported so the CTE is declared under the one name its readers assume.
|
|
60
|
+
*/
|
|
61
|
+
exports.readColumns = readColumns;
|
|
62
|
+
const RECORDSET_ALIAS = exports.RECORDSET_ALIAS = 'ne';
|
|
63
|
+
/**
|
|
64
|
+
* What the append body inserts into each event column, read from the recordset row.
|
|
65
|
+
*
|
|
66
|
+
* Typed as a `Record` over `EventColumnName` so that adding a column to
|
|
67
|
+
* `EVENT_COLUMNS` without saying where its value comes from is a COMPILE error,
|
|
68
|
+
* rather than a column the insert silently leaves at its default. Every column but
|
|
69
|
+
* `data` and `metadata` passes through as the recordset already types it — `text[]`
|
|
70
|
+
* tags, `timestamptz` — because `jsonb_to_recordset` did the casting; `data`
|
|
71
|
+
* crosses as base64 TEXT and is restored to `bytea` here, its one decode.
|
|
72
|
+
*
|
|
73
|
+
* `metadata` is wrapped in `COALESCE(…, '{}'::jsonb)`, for a caller this package's
|
|
74
|
+
* own client never is. `ensureSchema` replaces each append function with
|
|
75
|
+
* `CREATE OR REPLACE`, which keeps its argument signature, so a client from before
|
|
76
|
+
* the column existed (`1.0.0-alpha.3` and earlier) goes on calling the current body
|
|
77
|
+
* with events JSON that has no `metadata` key. `jsonb_to_recordset` reads an absent
|
|
78
|
+
* key as `NULL`, and an insert that names the column inserts that `NULL` rather than
|
|
79
|
+
* the column's `DEFAULT`, so `NOT NULL` would refuse every such append. The
|
|
80
|
+
* `COALESCE` gives that row the empty bag the `DEFAULT` would have, which is what
|
|
81
|
+
* makes "migrate, then deploy" a safe order from those versions. An event sent WITH
|
|
82
|
+
* a bag passes through unchanged, so what is stored for it does not move.
|
|
83
|
+
*/
|
|
84
|
+
const EVENT_COLUMN_SOURCE = {
|
|
85
|
+
type: `${RECORDSET_ALIAS}.type`,
|
|
86
|
+
data: `decode(${RECORDSET_ALIAS}.data, 'base64')`,
|
|
87
|
+
tags: `${RECORDSET_ALIAS}.tags`,
|
|
88
|
+
uuid: `${RECORDSET_ALIAS}.uuid`,
|
|
89
|
+
occurred_at: `${RECORDSET_ALIAS}.occurred_at`,
|
|
90
|
+
metadata: `COALESCE(${RECORDSET_ALIAS}.metadata, '{}'::jsonb)`
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* The bare event column names — `type, data, tags, uuid, occurred_at, metadata` —
|
|
94
|
+
* which the append body uses twice: as the recordset CTE's projection and as the
|
|
95
|
+
* `INSERT INTO t (…)` target list.
|
|
96
|
+
*/
|
|
97
|
+
const eventColumnList = exports.eventColumnList = /*#__PURE__*/EVENT_COLUMNS.map(([name]) => name).join(', ');
|
|
98
|
+
/**
|
|
99
|
+
* The append insert's SELECT list: one source expression per column, in the SAME
|
|
100
|
+
* ORDER as `eventColumnList`.
|
|
101
|
+
*
|
|
102
|
+
* The two halves of `INSERT INTO t (a, b) SELECT x, y` agree POSITION BY POSITION
|
|
103
|
+
* and nothing checks that they do: two columns of compatible type swap without an
|
|
104
|
+
* error at any layer and the data lands in the wrong one. Both halves are therefore
|
|
105
|
+
* one walk of the one descriptor, so the append body has no second ordering to keep
|
|
106
|
+
* in step and no hand-written list for a new column to be missing from.
|
|
107
|
+
*/
|
|
108
|
+
const eventInsertSelectList = exports.eventInsertSelectList = /*#__PURE__*/EVENT_COLUMNS.map(([name]) => EVENT_COLUMN_SOURCE[name]).join(', ');
|
|
55
109
|
/**
|
|
56
110
|
* The allowed-types filter for a candidate row aliased `m`: an item with no
|
|
57
111
|
* types matches ANY type; otherwise the event's type must be one of them.
|
|
58
112
|
* `typesCol` is the `text[]` column holding the item's types — `q.allowed_types`
|
|
59
113
|
* for a tagged match, `qi.types` for a tagless one.
|
|
60
114
|
*/
|
|
61
|
-
exports.readColumns = readColumns;
|
|
62
115
|
const allowedTypesPredicate = typesCol => `(array_length(${typesCol}, 1) IS NULL OR array_length(${typesCol}, 1) = 0 OR m.type = ANY(${typesCol}))`;
|
|
63
116
|
/**
|
|
64
117
|
* The tag-first match CTE chain shared by the read and conflict paths: expand
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"matchSql.js","names":["EVENT_COLUMNS","eventRecordsetColumns","exports","map","name","type","join","readColumns","prefix","allowedTypesPredicate","typesCol","taggedMatchChain","tokens","queryItemsExpr","tag","afterExpr","qualifiedRows","main","wildcardReadSql","typeOnlyReadSql","taggedReadSql"],"sources":["../../../src/internal/matchSql.ts"],"sourcesContent":[null],"mappings":";;;;;;AAAA;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;;;;;;AAWA,MAAMA,aAAa,GAAG,CACpB,CAAC,MAAM,EAAE,MAAM,CAAC,EAChB,CAAC,MAAM,EAAE,MAAM,CAAC,EAChB,CAAC,MAAM,EAAE,QAAQ,CAAC,EAClB,CAAC,MAAM,EAAE,MAAM,CAAC,EAChB,CAAC,aAAa,EAAE,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"matchSql.js","names":["EVENT_COLUMNS","eventRecordsetColumns","exports","map","name","type","join","readColumns","prefix","RECORDSET_ALIAS","EVENT_COLUMN_SOURCE","data","tags","uuid","occurred_at","metadata","eventColumnList","eventInsertSelectList","allowedTypesPredicate","typesCol","taggedMatchChain","tokens","queryItemsExpr","tag","afterExpr","qualifiedRows","main","wildcardReadSql","typeOnlyReadSql","taggedReadSql"],"sources":["../../../src/internal/matchSql.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
|