@owlmeans/postgres 0.1.18-rc.2 → 0.1.18-rc.20

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 CHANGED
@@ -14,7 +14,7 @@ provisioning, and an opt-in least-privilege bootstrap path.
14
14
  ## Installation
15
15
 
16
16
  ```bash
17
- bun add @owlmeans/postgres @owlmeans/postgres-resource
17
+ bun add @owlmeans/postgres@^0.1.18-rc.13 @owlmeans/postgres-resource@^0.1.18-rc.12
18
18
  ```
19
19
 
20
20
  ## Usage
@@ -46,8 +46,8 @@ A whole connection string works too, and wins over `host`/`user`/`secret`:
46
46
  cfg.dbs = [{ service: 'postgres', alias: 'postgres', schema: 'app', meta: { url: process.env.DATABASE_URL } }]
47
47
  ```
48
48
 
49
- `schema` is the Postgres **schema**, not the database — `dbName()` suffixes it per Entity/User layer,
50
- so per-tenant namespaces stay cheap. The database comes from `meta.database` and is never suffixed.
49
+ `schema` is the Postgres **schema**, not the database — `dbName()` resolves it as
50
+ `config.schema ?? config.alias ?? service.alias`. The database comes from `meta.database`.
51
51
 
52
52
  At `init()` the service opens a pool, runs a `SELECT 1` readiness probe (30 attempts, 2s apart by
53
53
  default — a Postgres sidecar routinely accepts TCP before it accepts queries), issues
@@ -129,7 +129,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
129
129
  your project's skill store (`.agents/skills/`):
130
130
 
131
131
  ```sh
132
- npx @owlmeans/agent-skills
132
+ npx @owlmeans/agent-skills@^0.1.18-rc.18
133
133
  ```
134
134
 
135
135
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/postgres",
4
- "version": "0.1.18-rc.0",
5
- "generatedAt": "2026-08-16T22:20:50.517Z",
4
+ "version": "0.1.18-rc.20",
5
+ "generatedAt": "2026-09-12T13:35:38.923Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: postgres
3
- description: How to use @owlmeans/postgres — PostgreSQL connection service (makePostgresDbService / appendPostgres) registered on a server context, plus the least-privilege bootstrap admin path. Auto-invoked when wiring PostgreSQL into a server app.
3
+ description: How to use @owlmeans/postgres — PostgreSQL connection service (makePostgresDbService / appendPostgres) registered on a server context, its health checks, plus the least-privilege bootstrap admin path. Auto-invoked when wiring PostgreSQL into a server app.
4
4
  user-invocable: false
5
5
  ---
6
6
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/postgres
9
9
 
10
10
  **Layer:** Infra
11
- **Install:** `"@owlmeans/postgres": "^0.1.18-rc.0"` in `dependencies`
11
+ **Install:** `"@owlmeans/postgres": "^0.1.18-rc.20"` in `dependencies`
12
12
 
13
13
  Pooled `pg` connections for a server context, plus the admin path that provisions the role, database
14
14
  and schema an app connects with. All DDL for application tables belongs to [[postgres-resource]] —
@@ -21,7 +21,9 @@ this package only creates the schema those tables live in.
21
21
  | `makePostgresDbService(alias?)` | The connection service. `alias` defaults to `DEFAULT_ALIAS` (`'postgres'`). |
22
22
  | `appendPostgres(context, alias?)` | Registers the service **and** its drain middleware. Use this, not a bare `registerService`. |
23
23
  | `PostgresService` | `PostgresDbService` + `bootstrap(configAlias, opts)`. |
24
- | `BootstrapOptions`, `BootstrapReport`, `bootstrapDb` | The admin path; `bootstrapDb` is usable without a context. |
24
+ | `BootstrapOptions`, `BootstrapReport`, `bootstrapDb` | The admin path. `bootstrapDb(service, configAlias, opts)` is what `service.bootstrap(configAlias, opts)` calls — both read the config through a **context-bound** service, so neither runs outside a context. |
25
+ | `pingDb(context, alias?, configAlias?)` / `checkDbHealth(...)` | Health checks through the registered service; `DbHealth` = `{ ok, summary, error? }`. |
26
+ | `getLastDbHealth(alias?, configAlias?)` / `formatDbError(error)` | The cached verdict, and the cause-chain error formatter. |
25
27
  | `drainMiddleware(alias?)` | Drains work deferred until every resource has initialized. |
26
28
  | `parseUrl`, `prepareConfig`, `poolDatabase`, `probe`, `ensureSchema` | Config and connection helpers. |
27
29
  | `DEFAULT_ALIAS`, `DEF_ADMIN_ALIAS`, `DEF_MAINTENANCE_DB`, `DEF_PORT`, `DEF_POOL_SIZE`, `DEF_RETRIES`, `DEF_RETRY_DELAY`, `TERMINAL_CONNECT_CODES` | Constants. |
@@ -50,20 +52,36 @@ cfg.dbs = [{
50
52
  for a `DATABASE_URL` env var. Because a leading `/` is auto-read, moving to file-mounted secrets
51
53
  later is a config change, not a code change.
52
54
 
53
- **`schema` is the SCHEMA, not the database.** `dbName()` suffixes it per Entity/User layer, so
54
- per-tenant namespaces cost a `CREATE SCHEMA` rather than a `CREATE DATABASE`. The database comes
55
- from `meta.database` and is never suffixed.
55
+ **`schema` is the SCHEMA, not the database.** The service's `name(alias?)` returns it as given
56
+ (`config.schema ?? config.alias ?? service.alias`), so a separate namespace costs a `CREATE SCHEMA`
57
+ inside a shared database rather than a `CREATE DATABASE`. The database comes from `meta.database`.
56
58
 
57
59
  Other `meta` keys: `autoSync` (see [[postgres-resource]]), `ssl`, `max`, `idleTimeoutMillis`,
58
60
  `connectionTimeoutMillis`, `statementTimeoutMillis`, `retries`, `retryDelayMillis`.
59
61
 
60
62
  ## What `init()` does
61
63
 
62
- Opens a `pg.Pool` → `SELECT 1` readiness probe (30 attempts, 2s apart) → `CREATE SCHEMA IF NOT
63
- EXISTS` → `SIGTERM` handler that drains the pool. Do **not** hand-roll the retry loop around it: a
64
- Postgres sidecar routinely accepts TCP before it accepts queries, which is precisely what the probe
65
- is for. Credential, missing-database and permission failures (`TERMINAL_CONNECT_CODES`) fail
66
- immediately instead of burning the full retry budget on an error that will never clear.
64
+ Opens a `pg.Pool` → attaches the pool's `error` handler → `SELECT 1` readiness probe
65
+ (`DEF_RETRIES` 30 attempts, `DEF_RETRY_DELAY` 2s apart, both overridable through
66
+ `meta.retries`/`meta.retryDelayMillis`) → `SIGTERM` handler that drains the pool → registers the
67
+ client → `CREATE SCHEMA IF NOT EXISTS`.
68
+
69
+ Do **not** hand-roll the retry loop around it: a Postgres sidecar routinely accepts TCP before it
70
+ accepts queries, which is precisely what the probe is for. Credential, missing-database and
71
+ permission failures (`TERMINAL_CONNECT_CODES`) fail immediately instead of burning the full retry
72
+ budget on an error that will never clear. A probe that gives up ends the pool before raising, so a
73
+ failed boot leaves no sockets behind.
74
+
75
+ **The schema is created only when the config entry names one.** `config.schema` absent means no
76
+ `CREATE SCHEMA` at all — an entry that identifies its database through `meta.database` or
77
+ `meta.url` and omits `schema` gets none, deliberately: resources create the schema they need
78
+ anyway, and inventing one from the service alias would leave an empty `postgres` schema behind on
79
+ every boot of the admin connection.
80
+
81
+ **The pool's `error` handler is not optional.** `node-postgres` emits `error` on *idle* clients
82
+ whenever the server closes a connection — routine behind a load balancer — and an unhandled
83
+ `error` on an EventEmitter terminates the process. The service attaches one that logs; a pool you
84
+ open yourself needs the same.
67
85
 
68
86
  Keep the pool small. Postgres caps connections cluster-wide (`max_connections`, 100 by default), so
69
87
  an oversized per-process pool starves every other client of the same server — the opposite of the
@@ -73,7 +91,8 @@ Mongo driver's tuning instinct.
73
91
 
74
92
  `db(alias?)` → `{ drizzle, pool, schema, database }` · `client(alias?)` / `clients` ·
75
93
  `qualify(resourceAlias, configAlias?)` · `query(text, params?, configAlias?)` ·
76
- `transaction(fn, configAlias?)` · `lock`/`unlock` (AES via `config.encryptionKey`) ·
94
+ `transaction(fn, configAlias?)` · `lock`/`unlock` (field encryption through
95
+ `makeKeyPairModel(config.encryptionKey)` from `@owlmeans/basic-keys`) ·
77
96
  `defer(configAlias, task)` / `drain(configAlias?)`.
78
97
 
79
98
  `defer` exists for work that can't run during one resource's `init()` because it points at another
@@ -81,6 +100,35 @@ resource that hasn't initialized yet — foreign keys, above all. Queue it; the
81
100
  `appendPostgres` registers drains it once the context is up. Do not reorder resource registrations
82
101
  to work around this.
83
102
 
103
+ ## Health
104
+
105
+ Do not hand-roll a health check, and never open a connection of your own for one: `pingDb` and
106
+ `checkDbHealth` resolve the service by alias exactly as any other consumer does, so a probe can
107
+ never report on a connection the app does not use. Both return `{ ok, summary, error? }` and never
108
+ throw.
109
+
110
+ ```typescript
111
+ const health = await checkDbHealth(context) // boot gate, after init()
112
+ if (!health.ok) throw new Error(health.error ?? health.summary)
113
+
114
+ const live = await pingDb(ctx) // request time, in a health handler
115
+ const boot = getLastDbHealth()
116
+ ```
117
+
118
+ `checkDbHealth` awaits `service.ready()`, runs `SELECT 1` and caches its verdict for
119
+ `getLastDbHealth`; `pingDb` is the bare `SELECT 1` and caches nothing. Neither inspects the schema
120
+ — structure is reconciled by the resources during `init()`, so a process that reached the check has
121
+ the tables it was built with and there is nothing left to assert beyond "the connection works".
122
+
123
+ The context is a parameter, not a module import: a handler is handed the context that actually
124
+ served the request (possibly an entity-scoped derivative), and passing it keeps these functions out
125
+ of the boot import cycle. Errors go through `formatDbError`, which walks the `cause` chain and
126
+ surfaces the `severity`/`code`/`detail`/`hint` a bare `.message` hides — that is where the real
127
+ FATAL/permission reason lives.
128
+
129
+ Pair it with the bind-first boot in [[server-app]]: `checkDbHealth` is the boot gate, and a failure
130
+ becomes `setBootPhase('failed', …)` on a port that is already bound.
131
+
84
132
  ## Bootstrap — the admin path
85
133
 
86
134
  Superuser credentials belong to a **separate config alias**. The application's own entry connects as
@@ -94,10 +142,13 @@ await context.service<PostgresService>('postgres').bootstrap('pg-admin', {
94
142
 
95
143
  Idempotent by design — every OwlMeans deployment calls it on each start and each rebuild. It probes
96
144
  `pg_roles` / `pg_database` before creating, rotates the password of an existing role (the caller
97
- generated the password it just passed in, so the role has to accept it), and with `leastPrivilege`
98
- applies `REVOKE CONNECT … FROM PUBLIC`, `REVOKE CREATE ON SCHEMA public FROM PUBLIC`,
99
- `GRANT CONNECT … TO <role>`, `ALTER ROLE … SET search_path`. The returned `BootstrapReport` says
100
- what actually changed. Identifiers are validated and quoted; the password is the only literal.
145
+ generated the password it just passed in, so the role has to accept it). The two grant branches are
146
+ independent: `leastPrivilege` applies `REVOKE CONNECT … FROM PUBLIC`,
147
+ `REVOKE CREATE ON SCHEMA public FROM PUBLIC` and `GRANT CONNECT … TO <role>`, while `schema`
148
+ applies `CREATE SCHEMA … AUTHORIZATION <role>`, `GRANT ALL ON SCHEMA … TO <role>` and
149
+ `ALTER ROLE … SET search_path`. **`search_path` comes from `schema`** — a call with
150
+ `leastPrivilege` and no `schema` sets none. The returned `BootstrapReport` says what actually
151
+ changed. Identifiers are validated and quoted; the password is the only literal.
101
152
 
102
153
  **This replaces hand-written bootstrap SQL.** If you find `CREATE ROLE` / `CREATE DATABASE` /
103
154
  `GRANT` strings in a deployment script, a provisioner, or a `DEPLOYMENT.md`, they are a duplicate of
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAS3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAIjD,KAAK,MAAM,GAAG,YAAY,CAAA;AAC1B,UAAU,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;CAAI;AAEzE,eAAO,MAAM,qBAAqB,WAAW,MAAM,KAAmB,eA2PrE,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,WACvE,CAAC,UAAS,MAAM,KACxB,CAOF,CAAA"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAS3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAIjD,KAAK,MAAM,GAAG,YAAY,CAAA;AAC1B,UAAU,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;CAAI;AAEzE,eAAO,MAAM,qBAAqB,WAAW,MAAM,KAAmB,eA2NrE,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,WACvE,CAAC,UAAS,MAAM,KACxB,CAOF,CAAA"}
package/build/service.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { makeKeyPairModel } from '@owlmeans/basic-keys';
2
- import { assertContext, Layer } from '@owlmeans/context';
2
+ import { assertContext } from '@owlmeans/context';
3
3
  import { makeTx, pgErrorToResourceError, pgIdentifier, refOf, resolvePlaceholders } from '@owlmeans/postgres-resource';
4
4
  import { createDbService } from '@owlmeans/resource';
5
5
  import { drizzle } from 'drizzle-orm/node-postgres';
@@ -14,7 +14,7 @@ export const makePostgresDbService = (alias = DEFAULT_ALIAS) => {
14
14
  /**
15
15
  * Handles are cached per config alias rather than rebuilt per call: `db()` runs on every
16
16
  * single resource operation, and both the Drizzle binding and the schema name are fixed
17
- * for the lifetime of a service instance — a layer switch produces a *new* instance.
17
+ * for the lifetime of the service — one instance per alias, for the whole process.
18
18
  */
19
19
  const handles = new Map();
20
20
  const deferred = new Map();
@@ -28,9 +28,9 @@ export const makePostgresDbService = (alias = DEFAULT_ALIAS) => {
28
28
  drizzle: drizzle(pool),
29
29
  pool,
30
30
  /**
31
- * `name()` is the layer aware namespace — the Postgres SCHEMA here, where Mongo
32
- * reads the same value as a database name. It can outgrow 63 bytes once a layer
33
- * suffix is appended, which Postgres would truncate silently.
31
+ * `name()` is the configured namespace — the Postgres SCHEMA here, where Mongo
32
+ * reads the same value as a database name. `pgIdentifier` holds it inside the
33
+ * 63 byte limit Postgres would otherwise truncate silently.
34
34
  */
35
35
  schema: pgIdentifier(service.name(configAlias)),
36
36
  database: poolDatabase(pool)
@@ -45,20 +45,6 @@ export const makePostgresDbService = (alias = DEFAULT_ALIAS) => {
45
45
  if (service.clients[configAlias] != null) {
46
46
  return;
47
47
  }
48
- if (service.layers == null) {
49
- service.layers = [Layer.Global];
50
- }
51
- /**
52
- * Sensitivity *adds* a layer the service is willing to serve. The Mongo copy of this
53
- * block tests `includes` where it means `!includes`, so it only ever appends a layer
54
- * that is already there — a latent no-op, corrected here.
55
- */
56
- if (config.serviceSensitive === true && !service.layers.includes(Layer.Service)) {
57
- service.layers.push(Layer.Service);
58
- }
59
- if (config.entitySensitive === true && !service.layers.includes(Layer.Entity)) {
60
- service.layers.push(Layer.Entity);
61
- }
62
48
  const meta = (config.meta ?? {});
63
49
  const pool = new Pool(prepareConfig(config));
64
50
  /**
@@ -188,19 +174,6 @@ export const makePostgresDbService = (alias = DEFAULT_ALIAS) => {
188
174
  /** A value that was never locked decrypts to itself rather than failing. */
189
175
  fields.includes(field) ? await key.decrypt(value).catch(() => value) : value
190
176
  ])));
191
- },
192
- reinitializeContext: (context) => {
193
- const _service = makePostgresDbService(alias);
194
- _service.ctx = context;
195
- _service.layers = service.layers;
196
- /**
197
- * Pools carry over instead of being reopened. A layer switch changes the Postgres
198
- * *schema*, not the server, and `max_connections` is a cluster wide budget — a pool
199
- * per tenant exhausts it long before the tenants run out. The handle cache is
200
- * deliberately *not* carried: its schema name is what the switch just changed.
201
- */
202
- Object.assign(_service.clients, service.clients);
203
- return _service;
204
177
  }
205
178
  }, service => async () => {
206
179
  const context = assertContext(service.ctx, location);
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/postgres",
3
- "version": "0.1.18-rc.2",
3
+ "version": "0.1.18-rc.20",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -22,18 +22,18 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@owlmeans/basic-keys": "^0.1.18-rc.2",
26
- "@owlmeans/context": "^0.1.18-rc.2",
27
- "@owlmeans/postgres-resource": "^0.1.18-rc.2",
28
- "@owlmeans/resource": "^0.1.18-rc.2",
29
- "@owlmeans/server-context": "^0.1.18-rc.2",
25
+ "@owlmeans/basic-keys": "^0.1.18-rc.18",
26
+ "@owlmeans/context": "^0.1.18-rc.14",
27
+ "@owlmeans/postgres-resource": "^0.1.18-rc.19",
28
+ "@owlmeans/resource": "^0.1.18-rc.15",
29
+ "@owlmeans/server-context": "^0.1.18-rc.18",
30
30
  "drizzle-orm": "~0.45.2",
31
31
  "pg": "^8.22.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@owlmeans/dep-config": "workspace:*",
35
- "@owlmeans/test-integration": "^0.1.18-rc.2",
36
- "@types/bun": "^1.3.14",
35
+ "@owlmeans/test-integration": "^0.1.18-rc.14",
36
+ "@types/bun": "^1.4.0",
37
37
  "@types/node": "^26.1.0",
38
38
  "@types/pg": "^8.20.4",
39
39
  "nodemon": "^3.1.14",
package/src/service.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { makeKeyPairModel } from '@owlmeans/basic-keys'
2
- import { assertContext, Layer } from '@owlmeans/context'
3
- import type { BasicContext } from '@owlmeans/context'
2
+ import { assertContext } from '@owlmeans/context'
4
3
  import {
5
4
  makeTx, pgErrorToResourceError, pgIdentifier, refOf, resolvePlaceholders
6
5
  } from '@owlmeans/postgres-resource'
@@ -28,7 +27,7 @@ export const makePostgresDbService = (alias: string = DEFAULT_ALIAS): PostgresSe
28
27
  /**
29
28
  * Handles are cached per config alias rather than rebuilt per call: `db()` runs on every
30
29
  * single resource operation, and both the Drizzle binding and the schema name are fixed
31
- * for the lifetime of a service instance — a layer switch produces a *new* instance.
30
+ * for the lifetime of the service — one instance per alias, for the whole process.
32
31
  */
33
32
  const handles: Map<string, PostgresDb> = new Map()
34
33
  const deferred: Map<string, Array<() => Promise<void>>> = new Map()
@@ -44,9 +43,9 @@ export const makePostgresDbService = (alias: string = DEFAULT_ALIAS): PostgresSe
44
43
  drizzle: drizzle(pool) as NodePgDatabase<Record<string, never>>,
45
44
  pool,
46
45
  /**
47
- * `name()` is the layer aware namespace — the Postgres SCHEMA here, where Mongo
48
- * reads the same value as a database name. It can outgrow 63 bytes once a layer
49
- * suffix is appended, which Postgres would truncate silently.
46
+ * `name()` is the configured namespace — the Postgres SCHEMA here, where Mongo
47
+ * reads the same value as a database name. `pgIdentifier` holds it inside the
48
+ * 63 byte limit Postgres would otherwise truncate silently.
50
49
  */
51
50
  schema: pgIdentifier(service.name(configAlias)),
52
51
  database: poolDatabase(pool)
@@ -65,21 +64,6 @@ export const makePostgresDbService = (alias: string = DEFAULT_ALIAS): PostgresSe
65
64
  return
66
65
  }
67
66
 
68
- if (service.layers == null) {
69
- service.layers = [Layer.Global]
70
- }
71
- /**
72
- * Sensitivity *adds* a layer the service is willing to serve. The Mongo copy of this
73
- * block tests `includes` where it means `!includes`, so it only ever appends a layer
74
- * that is already there — a latent no-op, corrected here.
75
- */
76
- if (config.serviceSensitive === true && !service.layers.includes(Layer.Service)) {
77
- service.layers.push(Layer.Service)
78
- }
79
- if (config.entitySensitive === true && !service.layers.includes(Layer.Entity)) {
80
- service.layers.push(Layer.Entity)
81
- }
82
-
83
67
  const meta = (config.meta ?? {}) as PostgresMeta
84
68
  const pool = new Pool(prepareConfig(config))
85
69
 
@@ -125,7 +109,7 @@ export const makePostgresDbService = (alias: string = DEFAULT_ALIAS): PostgresSe
125
109
  qualify: resourceAlias => {
126
110
  const context = assertContext<Config, Context>(service.ctx as Context, location)
127
111
 
128
- return refOf(context as unknown as BasicContext<any>, null, resourceAlias)
112
+ return refOf(context, null, resourceAlias)
129
113
  },
130
114
 
131
115
  query: async <Row extends QueryResultRow = QueryResultRow>(
@@ -135,7 +119,7 @@ export const makePostgresDbService = (alias: string = DEFAULT_ALIAS): PostgresSe
135
119
  const context = assertContext<Config, Context>(service.ctx as Context, location)
136
120
  try {
137
121
  const result = await db.pool.query<Row>(
138
- resolvePlaceholders(text, context as unknown as BasicContext<any>, null), params as never[]
122
+ resolvePlaceholders(text, context, null), params as never[]
139
123
  )
140
124
 
141
125
  return result.rows
@@ -152,8 +136,8 @@ export const makePostgresDbService = (alias: string = DEFAULT_ALIAS): PostgresSe
152
136
  await client.query('BEGIN')
153
137
  const result = await fn(makeTx(
154
138
  client,
155
- text => resolvePlaceholders(text, context as unknown as BasicContext<any>, null),
156
- resourceAlias => refOf(context as unknown as BasicContext<any>, null, resourceAlias)
139
+ text => resolvePlaceholders(text, context, null),
140
+ resourceAlias => refOf(context, null, resourceAlias)
157
141
  ))
158
142
  await client.query('COMMIT')
159
143
 
@@ -243,23 +227,6 @@ export const makePostgresDbService = (alias: string = DEFAULT_ALIAS): PostgresSe
243
227
  ]
244
228
  ))
245
229
  )
246
- },
247
-
248
- reinitializeContext: <T>(context: BasicContext<ServerConfig>) => {
249
- const _service = makePostgresDbService(alias)
250
-
251
- _service.ctx = context
252
- _service.layers = service.layers
253
-
254
- /**
255
- * Pools carry over instead of being reopened. A layer switch changes the Postgres
256
- * *schema*, not the server, and `max_connections` is a cluster wide budget — a pool
257
- * per tenant exhausts it long before the tenants run out. The handle cache is
258
- * deliberately *not* carried: its schema name is what the switch just changed.
259
- */
260
- Object.assign(_service.clients, service.clients)
261
-
262
- return _service as T
263
230
  }
264
231
  }, service => async () => {
265
232
  const context = assertContext<Config, Context>(service.ctx as Context, location)
@@ -1,7 +1,7 @@
1
1
  import { afterAll, beforeAll, describe, expect, test } from 'bun:test'
2
- import { makePostgresResource } from '@owlmeans/postgres-resource'
2
+ import { DEFAULT_PAGE_SIZE, makePostgresResource } from '@owlmeans/postgres-resource'
3
3
  import type { PostgresResource } from '@owlmeans/postgres-resource'
4
- import { RecordExists, UnknownRecordError } from '@owlmeans/resource'
4
+ import { RecordExists, UnknownRecordError, UnsupportedArgumentError } from '@owlmeans/resource'
5
5
  import type { ResourceRecord } from '@owlmeans/resource'
6
6
 
7
7
  import { gate, makeSuite, shapeOf } from './context.js'
@@ -146,16 +146,23 @@ describe('@owlmeans/postgres — resource CRUD against a real context', () => {
146
146
  expect(loaded.createdAt).toBeInstanceOf(Date)
147
147
  })
148
148
 
149
- it('loads by a field other than the primary key, and refuses an unknown record', async () => {
150
- const byEmail = await users.load('a@b.c', 'email')
149
+ /**
150
+ * One call, several fields — the shape a list-then-take-the-first was standing in for.
151
+ * `load` answers with a record or `null`; `get` answers or raises.
152
+ */
153
+ it('reads one record by criteria rather than by id, and refuses an unknown one', async () => {
154
+ const byEmail = await users.load({ email: 'a@b.c' })
151
155
  expect(byEmail?.id).toBe(seeded.id as string)
152
156
 
153
- expect(await users.load('nobody@nowhere', 'email')).toBeNull()
154
- await expect(users.get('nobody@nowhere', 'email')).rejects.toThrow(UnknownRecordError)
157
+ expect(await users.load({ email: 'a@b.c', status: 'active', age: 21 })).not.toBeNull()
158
+ expect(await users.load({ email: 'a@b.c', status: 'banned' })).toBeNull()
159
+
160
+ expect(await users.load({ email: 'nobody@nowhere' })).toBeNull()
161
+ await expect(users.get({ email: 'nobody@nowhere' })).rejects.toThrow(UnknownRecordError)
155
162
  })
156
163
 
157
164
  it('patches by merge where update replaces the whole record', async () => {
158
- const patched = await users.patch<User>({ id: seeded.id, age: 22 })
165
+ const patched = await users.patch({ id: seeded.id, age: 22 })
159
166
  expect(patched.age).toBe(22)
160
167
  expect(patched.email).toBe('a@b.c')
161
168
 
@@ -170,21 +177,55 @@ describe('@owlmeans/postgres — resource CRUD against a real context', () => {
170
177
  await users.create({ email: 'third@b.c', status: 'active' })
171
178
 
172
179
  const banned = await users.list({ status: 'banned' })
173
- expect(banned.pager?.total).toBe(2)
180
+ expect(banned.total).toBe(2)
174
181
  expect(banned.items.every(item => item.status === 'banned')).toBe(true)
175
182
 
176
- const paged = await users.list({}, { pager: { page: 0, size: 2 } })
183
+ const paged = await users.list({}, { page: 0, size: 2 })
177
184
  expect(paged.items).toHaveLength(2)
178
- expect(paged.pager?.total).toBe(3)
185
+ expect(paged.total).toBe(3)
186
+ expect(paged.page).toBe(0)
187
+ expect(paged.size).toBe(2)
188
+
189
+ /** A page past the end is empty, and the total still describes the whole match. */
190
+ const beyond = await users.list({}, { page: 5, size: 2 })
191
+ expect(beyond.items).toHaveLength(0)
192
+ expect(beyond.total).toBe(3)
179
193
 
180
- const sorted = await users.list({}, { pager: { sort: [['email', false]] } })
194
+ const sorted = await users.list({}, { sort: ['email'] })
181
195
  expect(sorted.items.map(item => item.email)).toEqual(['a@b.c', 'second@b.c', 'third@b.c'])
196
+
197
+ const descending = await users.list({}, { sort: [{ field: 'email', order: 'desc' }] })
198
+ expect(descending.items.map(item => item.email)).toEqual(['third@b.c', 'second@b.c', 'a@b.c'])
199
+
200
+ /** The same sort narrows a multi-record criteria down to one record in a single read. */
201
+ const last = await users.get({ status: 'banned' }, { sort: [{ field: 'email', order: 'desc' }] })
202
+ expect(last.email).toBe('second@b.c')
182
203
  })
183
204
 
184
- it('counts and purges by criteria', async () => {
205
+ /**
206
+ * A relational table is unbounded, so an unasked-for read is capped. Reading everything
207
+ * stays possible, but only by saying so.
208
+ */
209
+ it('caps an unpaged list at the default size and lifts the cap on size 0', async () => {
210
+ const capped = await users.list()
211
+ expect(capped.size).toBe(DEFAULT_PAGE_SIZE)
212
+ expect(capped.page).toBe(0)
213
+ expect(capped.items).toHaveLength(3)
214
+
215
+ const everything = await users.list({}, { size: 0 })
216
+ expect(everything.items).toHaveLength(3)
217
+ expect(everything.total).toBe(3)
218
+ expect(everything.size).toBeUndefined()
219
+ expect(everything.page).toBeUndefined()
220
+ })
221
+
222
+ it('counts and purges by criteria, and refuses to purge everything', async () => {
185
223
  expect(await users.count()).toBe(3)
186
224
  expect(await users.count({ status: 'banned' })).toBe(2)
187
225
 
226
+ /** An empty criteria object would truncate the table — it has to be said in SQL instead. */
227
+ await expect(users.purge({})).rejects.toThrow(UnsupportedArgumentError)
228
+
188
229
  expect(await users.purge({ email: 'third@b.c' })).toBe(1)
189
230
  expect(await users.count()).toBe(2)
190
231
  })
@@ -193,15 +234,22 @@ describe('@owlmeans/postgres — resource CRUD against a real context', () => {
193
234
  await expect(users.create({ id: '00000000-0000-4000-8000-000000000001', email: 'no@b.c' }))
194
235
  .rejects.toThrow(RecordExists)
195
236
 
196
- const inserted = await users.insert<User>({
237
+ const inserted = await users.insert({
197
238
  id: '00000000-0000-4000-8000-000000000002', email: 'explicit@b.c'
198
239
  })
199
240
  expect(inserted.id).toBe('00000000-0000-4000-8000-000000000002')
200
241
  await users.delete(inserted.id as string)
201
242
  })
202
243
 
244
+ it('refuses a TTL rather than dropping it — Postgres has no row expiry', async () => {
245
+ await expect(users.create({ email: 'ttl@b.c' }, { ttl: 60 }))
246
+ .rejects.toThrow(UnsupportedArgumentError)
247
+ await expect(users.save({ id: seeded.id, email: 'a@b.c' }, { ttl: 60 }))
248
+ .rejects.toThrow(UnsupportedArgumentError)
249
+ })
250
+
203
251
  it('upserts on an arbiter that is not the primary key', async () => {
204
- const upserted = await users.upsert<User>({ email: 'a@b.c', age: 44 }, ['email'])
252
+ const upserted = await users.upsert({ email: 'a@b.c', age: 44 }, ['email'])
205
253
 
206
254
  expect(upserted.id).toBe(seeded.id as string)
207
255
  expect(upserted.age).toBe(44)
@@ -212,16 +260,18 @@ describe('@owlmeans/postgres — resource CRUD against a real context', () => {
212
260
  await expect(users.create({ email: 'a@b.c' })).rejects.toThrow(RecordExists)
213
261
  })
214
262
 
215
- it('picks a record by deleting it, atomically', async () => {
263
+ it('takes a record by deleting it, atomically', async () => {
216
264
  const before = await users.count()
217
265
  expect(await posts.count()).toBe(1)
218
266
 
219
- const picked = await users.pick(seeded.id as string)
267
+ const taken = await users.take(seeded.id as string)
220
268
 
221
- expect(picked.email).toBe('a@b.c')
269
+ expect(taken.email).toBe('a@b.c')
222
270
  expect(await users.count()).toBe(before - 1)
223
271
  /** The cascade is the database's, not the resource's — the post goes with its owner. */
224
272
  expect(await posts.count()).toBe(0)
225
- await expect(users.pick(seeded.id as string)).rejects.toThrow(UnknownRecordError)
273
+ /** `delete` reports absence, `take` raises on it. */
274
+ expect(await users.delete(seeded.id as string)).toBeNull()
275
+ await expect(users.take(seeded.id as string)).rejects.toThrow(UnknownRecordError)
226
276
  })
227
277
  })
@@ -140,12 +140,13 @@ describe('@owlmeans/postgres — custom SQL with alias placeholders', () => {
140
140
  expect(raw?.email_address).toBe('jrr@shire.me')
141
141
  expect(raw?.email).toBeUndefined()
142
142
 
143
- const record = await authors.selectOne<Author>('SELECT * FROM {{}} WHERE "name" = $1', ['Tolkien'])
143
+ /** The resource is typed once, at the call to `resource()` — never per method. */
144
+ const record = await authors.selectOne('SELECT * FROM {{}} WHERE "name" = $1', ['Tolkien'])
144
145
  expect(record?.email).toBe('jrr@shire.me')
145
146
  expect(record?.id).toBe(tolkien.id as string)
146
147
 
147
148
  /** jsonb comes back parsed, not as a string, on both routes. */
148
- const [book] = await books.select<Book>(`SELECT * FROM {{}} WHERE "title" = $1`, ['The Silmarillion'])
149
+ const [book] = await books.select(`SELECT * FROM {{}} WHERE "title" = $1`, ['The Silmarillion'])
149
150
  expect(book.meta).toEqual({ posthumous: true })
150
151
  })
151
152