@owlmeans/postgres-resource 0.1.18-rc.2 → 0.1.18-rc.21

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 (47) hide show
  1. package/README.md +27 -12
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/postgres-resource/SKILL.md +82 -18
  4. package/build/consts.d.ts +8 -1
  5. package/build/consts.d.ts.map +1 -1
  6. package/build/consts.js +8 -1
  7. package/build/consts.js.map +1 -1
  8. package/build/declarations.js +5 -5
  9. package/build/resource.d.ts +2 -2
  10. package/build/resource.d.ts.map +1 -1
  11. package/build/resource.js +95 -108
  12. package/build/resource.js.map +1 -1
  13. package/build/types.d.ts +27 -19
  14. package/build/types.d.ts.map +1 -1
  15. package/build/utils/criteria.d.ts +10 -6
  16. package/build/utils/criteria.d.ts.map +1 -1
  17. package/build/utils/criteria.js +13 -5
  18. package/build/utils/criteria.js.map +1 -1
  19. package/build/utils/diff.d.ts.map +1 -1
  20. package/build/utils/diff.js +31 -1
  21. package/build/utils/diff.js.map +1 -1
  22. package/build/utils/migrations.d.ts +2 -2
  23. package/build/utils/migrations.js +2 -2
  24. package/build/utils/name.d.ts +3 -3
  25. package/build/utils/name.js +3 -3
  26. package/build/utils/sql.d.ts +10 -2
  27. package/build/utils/sql.d.ts.map +1 -1
  28. package/build/utils/sql.js.map +1 -1
  29. package/build/utils/table.d.ts +4 -0
  30. package/build/utils/table.d.ts.map +1 -1
  31. package/build/utils/table.js +4 -0
  32. package/build/utils/table.js.map +1 -1
  33. package/package.json +7 -7
  34. package/src/consts.ts +8 -1
  35. package/src/declarations.ts +5 -5
  36. package/src/resource.ts +118 -135
  37. package/src/types.ts +29 -18
  38. package/src/utils/criteria.ts +25 -17
  39. package/src/utils/diff.ts +31 -1
  40. package/src/utils/migrations.ts +2 -2
  41. package/src/utils/name.ts +3 -3
  42. package/src/utils/sql.ts +0 -0
  43. package/src/utils/table.ts +5 -1
  44. package/tests/criteria.spec.ts +187 -0
  45. package/tests/diff.spec.ts +60 -0
  46. package/tests/name.spec.ts +3 -3
  47. package/tests/sql.spec.ts +4 -3
package/README.md CHANGED
@@ -5,7 +5,7 @@ code-registered migrations, and custom SQL with resource-alias placeholders.
5
5
 
6
6
  ## Overview
7
7
 
8
- - `makePostgresResource<R, T>(alias, dbAlias?, serviceAlias?, maker?, tableName?)` — factory for Postgres resources
8
+ - `makePostgresResource<R, T>(alias, dbAlias?, serviceAlias?, tableName?)` — factory for Postgres resources
9
9
  - `PostgresResource<T>` — extends `Resource<T>` with a Drizzle table, custom SQL, transactions, and field encryption
10
10
  - The resource's **AJV schema is the single source of truth for the table structure** — columns, types,
11
11
  nullability, defaults, primary key and indexes are all derived from it
@@ -16,7 +16,7 @@ code-registered migrations, and custom SQL with resource-alias placeholders.
16
16
  ## Installation
17
17
 
18
18
  ```bash
19
- bun add @owlmeans/postgres-resource @owlmeans/postgres pg
19
+ bun add @owlmeans/postgres-resource@^0.1.18-rc.12 @owlmeans/postgres@^0.1.18-rc.13 pg
20
20
  ```
21
21
 
22
22
  `pg` and `ajv` are peer dependencies of this package. `@owlmeans/postgres` provides the connection
@@ -35,7 +35,7 @@ export interface ProjectResource extends PostgresResource<ProjectRecord> {}
35
35
 
36
36
  export const makeProjectResource: ResourceMaker<ProjectRecord, ProjectResource> = (dbAlias, serviceAlias) => {
37
37
  const resource = makePostgresResource<ProjectRecord, ProjectResource>(
38
- RES_PROJECT, dbAlias, serviceAlias, makeProjectResource
38
+ RES_PROJECT, dbAlias, serviceAlias
39
39
  )
40
40
  resource.schema = ProjectSchema
41
41
  resource.index('idx_project_entity', { columns: ['entityId'] })
@@ -55,9 +55,17 @@ Use in a handler:
55
55
  ```typescript
56
56
  const projects = context.resource<ProjectResource>(RES_PROJECT)
57
57
  const record = await projects.create({ entityId, alias, title })
58
- const list = await projects.list({ criteria: { entityId } })
58
+
59
+ const one = await projects.load({ entityId, alias }) // null when absent
60
+ const { items, total } = await projects.list(
61
+ { entityId, status: ['draft', 'active'] }, // an array means "any of these"
62
+ { page: 0, size: 20, sort: [{ field: 'createdAt', order: 'desc' }] }
63
+ )
59
64
  ```
60
65
 
66
+ `size` defaults to `DEFAULT_PAGE_SIZE` (100); `list(where, { size: 0 })` lifts the limit. `total`
67
+ always describes the whole match, independently of the page.
68
+
61
69
  ### Schema → table
62
70
 
63
71
  ```typescript
@@ -150,7 +158,7 @@ substituted blindly.
150
158
 
151
159
  ## API
152
160
 
153
- ### `makePostgresResource<R, T>(alias, dbAlias?, serviceAlias?, maker?, tableName?): T`
161
+ ### `makePostgresResource<R, T>(alias, dbAlias?, serviceAlias?, tableName?): T`
154
162
 
155
163
  Creates a Postgres resource. `dbAlias` and `serviceAlias` default to `DEFAULT_DB_ALIAS`
156
164
  (`'postgres'`). `tableName` overrides the physical table name, which otherwise derives from the alias.
@@ -162,21 +170,28 @@ Extends `Resource<T>` with:
162
170
  - `schema?: AnySchema` — the source of truth for the table structure
163
171
  - `table: TableSpec` / `entity: PgRuntimeTable` — the compiled spec and the runtime Drizzle table
164
172
  - `db(): Promise<PostgresDb>` / `client(): Promise<Pool>`
165
- - `index(name, spec): this` / `migration(name, apply, stage?): this` — chainable declarations
173
+ - `index(name, spec)` / `migration(name, apply, stage?)` — chainable declarations
166
174
  - `query` / `queryOne` / `execute` — raw rows and affected counts
167
175
  - `select` / `selectOne` — rows marshalled back into `T`
168
176
  - `ref(alias?): string` — fully qualified identifier of this or another resource
169
177
  - `transaction(fn)` — a `PostgresTx` with the same placeholder-aware helpers
170
- - `insert` / `upsert` / `patch` / `purge` / `count` — beyond the base contract
178
+ - `insert` / `upsert` / `patch` — beyond the base contract
171
179
  - `lock(record, fields?)` / `unlock(record, fields?)` — encrypt/decrypt `secure` fields
172
180
  - `getDefaults(): Partial<T>`
173
181
 
182
+ Type the resource once, where it is resolved — `context.resource<PostgresResource<Project>>(alias)` —
183
+ rather than passing a record type per method call.
184
+
174
185
  ### `Resource<T>` methods (all implemented)
175
186
 
176
- `get`, `load`, `create`, `update`, `save`, `delete`, `pick`, `list`
187
+ `get`, `load`, `list`, `count`, `create`, `update`, `save`, `delete`, `take`, `purge`
177
188
 
178
- `create` refuses a caller-supplied id (use `insert`); `update` replaces the whole record (use `patch`
179
- to merge); `pick` deletes the record it returns, atomically.
189
+ `get` and `load` take either an id or a `Criteria<T>` (with an optional `sort`), so reading one
190
+ record by several fields is a single call. `create` refuses a caller-supplied id (use `insert`);
191
+ `update` replaces the whole record (use `patch` to merge); `take` deletes the record it returns,
192
+ atomically, and raises when there is nothing to take where `delete` answers `null`; `purge` refuses
193
+ an empty criteria rather than emptying the table. Every write option is rejected here except
194
+ absence: `ttl` raises `UnsupportedArgumentError`, since Postgres has no row expiry.
180
195
 
181
196
  ### Errors
182
197
 
@@ -190,7 +205,7 @@ surface as `RecordExists` and not-null violations as `MisshapedRecord`.
190
205
  ### Constants
191
206
 
192
207
  - `DEFAULT_DB_ALIAS` — `'postgres'`
193
- - `DEFAULT_PAGE_SIZE` — `10`
208
+ - `DEFAULT_PAGE_SIZE` — `100`, the cap `list()` applies when no `size` is named
194
209
  - `DEF_MIGRATIONS_TABLE` — `'_owlmeans_migrations'`
195
210
  - `PG_KEYWORD` — `'pg'`; `PG_MAX_IDENTIFIER` — `63`
196
211
  - `PgAutoSync`, `PgIndexMethod`, `PgReferentialAction`, `PgErrorCode`
@@ -209,7 +224,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
209
224
  your project's skill store (`.agents/skills/`):
210
225
 
211
226
  ```sh
212
- npx @owlmeans/agent-skills
227
+ npx @owlmeans/agent-skills@^0.1.18-rc.20
213
228
  ```
214
229
 
215
230
  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-resource",
4
- "version": "0.1.18-rc.0",
5
- "generatedAt": "2026-08-16T22:20:50.509Z",
4
+ "version": "0.1.18-rc.21",
5
+ "generatedAt": "2026-09-12T14:21:25.465Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/postgres-resource
9
9
 
10
10
  **Layer:** Infra
11
- **Install:** `"@owlmeans/postgres-resource": "^0.1.18-rc.0"` in `dependencies` (peers `pg`, `ajv`)
11
+ **Install:** `"@owlmeans/postgres-resource": "^0.1.18-rc.21"` in `dependencies` (peers `pg`, `ajv`)
12
12
 
13
13
  The Postgres counterpart of [[mongo-resource]]. The difference that governs everything else: a
14
14
  Mongo collection has no structure, a Postgres table does — so **the resource layer owns the DDL**
@@ -18,15 +18,18 @@ and derives it from the resource's AJV schema.
18
18
 
19
19
  | Export | Description |
20
20
  |--------|-------------|
21
- | `makePostgresResource<R, T>(alias, dbAlias?, serviceAlias?, maker?, tableName?)` | The resource factory. Aliases default to `DEFAULT_DB_ALIAS` (`'postgres'`). |
22
- | `PostgresResource<T>` | `Resource<T>` + `table`/`entity`, custom SQL, transactions, `insert`/`upsert`/`patch`/`purge`/`count`, `lock`/`unlock`. |
23
- | `PostgresDbService`, `PostgresDb`, `PostgresTx` | Service contract implemented by `@owlmeans/postgres`; the db handle `{ drizzle, pool, schema, database }`; the transaction façade. |
21
+ | `makePostgresResource<R, T>(alias, dbAlias?, serviceAlias?, tableName?)` | The resource factory. Aliases default to `DEFAULT_DB_ALIAS` (`'postgres'`); `tableName` overrides the physical table (else the sanitized alias). |
22
+ | `PostgresResource<T>` | `Resource<T>` + `table`/`entity`, `db()`/`client()`, `index`, `ref`, `getDefaults`, custom SQL (`query`/`queryOne`/`execute`/`select`/`selectOne`), `transaction`, `insert`/`upsert`/`patch`, `lock`/`unlock`, `migration`/`migrations`. |
23
+ | `PostgresDbService`, `PostgresDb`, `PostgresTx` | Service contract implemented by `@owlmeans/postgres`; the db handle `{ drizzle, pool, schema, database }`; the transaction façade (`client`, `query`/`queryOne`/`execute`, `ref`). |
24
+ | `PostgresMeta` | The `DbConfig.meta` shape this package reads — `database`, `autoSync`, `url`, and the pool/probe knobs. |
24
25
  | `TableSpec`, `ColumnSpec`, `PgPropertyOverride`, `PgRootOverride`, `DdlPlan` | The compiled table description and the `pg:` vocabulary types. |
26
+ | `PgIndexSpec`, `PgUniqueSpec`, `PgCheckSpec`, `PgReferenceSpec` | What `resource.index()` and the `pg:` overrides are written with. |
27
+ | `criteriaToSql`, `sortToSql` | `Criteria<T>` → a WHERE clause and `Sort<T>` → the ORDER BY, for code that builds its own statement over the same table. |
25
28
  | `pgKeyword` | `{ keyword: 'pg', valid: true }` — register it when running AJV in strict mode. |
26
29
  | `schemaToTableSpec`, `pgTableName`, `pgIdentifier`, `quoteIdent`, `qualify`, `advisoryKey` | The compiler and identifier helpers. |
27
30
  | `refOf`, `resolvePlaceholders` | `{{alias}}` resolution — identifiers only. |
28
31
  | `PostgresError` family, `pgErrorToResourceError`, `describePgError` | Driver-error translation. |
29
- | `getDeclaration`, `resetDeclarations` | Module-scope index/migration declarations, keyed by alias. |
32
+ | `getDeclaration`, `resetDeclarations` | Module-scope schema/index/migration declarations, keyed by alias. |
30
33
  | `DEFAULT_DB_ALIAS`, `DEFAULT_PAGE_SIZE`, `DEF_MIGRATIONS_TABLE`, `PgAutoSync`, `PgIndexMethod`, `PgReferentialAction`, `PgErrorCode` | Constants. |
31
34
 
32
35
  ## The schema is the table — never write DDL
@@ -34,7 +37,7 @@ and derives it from the resource's AJV schema.
34
37
  ```typescript
35
38
  export const makeProjectResource: ResourceMaker<ProjectRecord, ProjectResource> = (dbAlias, serviceAlias) => {
36
39
  const resource = makePostgresResource<ProjectRecord, ProjectResource>(
37
- RES_PROJECT, dbAlias, serviceAlias, makeProjectResource
40
+ RES_PROJECT, dbAlias, serviceAlias
38
41
  )
39
42
  resource.schema = ProjectSchema
40
43
  resource.index('idx_project_entity', { columns: ['entityId'] })
@@ -48,12 +51,18 @@ Never call `pgTable`/`pgSchema`, never call `drizzle()`, never run `drizzle-kit`
48
51
  `CREATE TABLE`. Two owners of the same DDL is the failure mode this package exists to remove:
49
52
  reconciliation would drop what the other owner added.
50
53
 
54
+ `schema`, `index()` and `migration()` all land in the module-scope declaration for the alias rather
55
+ than on the resource object, so a maker that runs more than once for the same alias — a custom
56
+ maker wrapping the built-in one, a spec calling it again — reads and extends the same declaration
57
+ instead of starting a fresh, emptier one.
58
+
51
59
  | JSON Schema | Postgres |
52
60
  |---|---|
53
61
  | `string` · `string`+`format:'uuid'` | `text` · `uuid` |
54
62
  | `DateSchema` (`{type:'object', format:'date-time'}`) | `timestamptz` |
55
63
  | `integer` · `number` · `boolean` | `integer` · `double precision` · `boolean` |
56
- | `array`, nested `object` | `jsonb` |
64
+ | `array` of plain `string`/`integer`/`number`/`boolean` items (no `format`, no `enum`) | native `<scalar>[]`, e.g. `text[]` |
65
+ | any other `array` (objects, enums, formatted items, no `items`), nested `object` | `jsonb` |
57
66
  | string `enum` | `text` + `CHECK` |
58
67
  | `nullable: true` · in `required[]` | nullable · `NOT NULL` |
59
68
  | `secure: true` | ciphertext column, `lock`/`unlock` aware |
@@ -90,24 +99,41 @@ declaration sites merge into one `TableSpec` — the schema root, a per-property
90
99
  not an error. Two entries under one name would emit the same `CREATE INDEX` twice in a single DDL
91
100
  transaction, and Postgres answers the second with `42P07`, rolling back the plan that created the
92
101
  table: the resource then fails every boot with an error naming an index that does not exist.
93
- `byName` in `utils/schema.ts` collapses them and warns.
102
+ The compiler collapses the duplicates itself, keeps the first declaration and warns on the console.
94
103
 
95
104
  ## Reconciliation is authoritative — `PgAutoSync`
96
105
 
97
- At `init()`: introspect → diff → apply the whole `DdlPlan` in one transaction under
98
- `pg_advisory_xact_lock`, so concurrent replicas don't race. `DbConfig.meta.autoSync`:
106
+ At `init()`: take a **session** advisory lock on the qualified table name → introspect → diff →
107
+ apply the whole `DdlPlan` in one transaction → release the lock. The lock is session level, not
108
+ `xact`, because it has to span the migrations as well as the DDL, and those run in transactions of
109
+ their own inside it — only the plan itself is one `BEGIN`/`COMMIT`. Concurrent replicas therefore
110
+ serialize on the whole initialization, not just on the DDL. `DbConfig.meta.autoSync`:
99
111
 
100
112
  | Value | Behaviour |
101
113
  |---|---|
102
114
  | `Full` (default) | add / retype+backfill / drop columns, reconcile indexes and constraints |
103
115
  | `Additive` | add only — never retypes, never drops |
104
- | `Off` | create if absent, otherwise leave alone |
116
+ | `Off` | **no table DDL** — the structure is never diffed and no statement is emitted, so a missing table is never created and the first query dies with `42P01`. Only for a table something else already provisions |
117
+
118
+ The schema root override `pg: { autoSync }` is a boolean and wins per table: it turns reconciliation
119
+ back on for one table under `meta.autoSync: 'off'`, and off for one table under the other two modes.
120
+ `Full` versus `Additive` still comes from the config.
105
121
 
106
122
  **`Full` DROPs columns the schema doesn't declare.** Adopting a table this package didn't create:
107
123
  boot once with `Additive`, confirm the plan comes out empty, then flip to `Full`. Columns listed in
108
124
  `pg.unmanaged` stay outside reconciliation's authority permanently. A cast Postgres cannot perform
109
125
  raises `PostgresCastRequired` instead of truncating.
110
126
 
127
+ **A retype drops the column's default first and restores it after.** Postgres refuses
128
+ `ALTER COLUMN … TYPE` outright when the column carries a DEFAULT it cannot cast to the new type
129
+ (`42804 default for column "x" cannot be cast automatically`), and it refuses before reading a
130
+ single row, so `USING` never gets a chance to help. Since the plan is one transaction, that refusal
131
+ aborts the whole reconciliation and the resource then fails *every* boot with an error naming the
132
+ column it is trying to fix. The plan therefore emits `DROP DEFAULT` → `ALTER … TYPE … USING` →
133
+ `SET DEFAULT`, restoring the default from the spec rather than from what the column was carrying.
134
+ The `id` column is where this shows up in practice: it is created with a `gen_random_uuid()::text`
135
+ default, so any change to its declared type takes this path.
136
+
111
137
  ## Migrations bracket the sync
112
138
 
113
139
  `migration`/`migrations()` implement the shared `MigratableResource` capability from
@@ -141,10 +167,16 @@ resource.migration('0001-rescue-legacy', async tx => {
141
167
 
142
168
  ```typescript
143
169
  await projects.select(
144
- `SELECT p.* FROM {{}} p JOIN {{users}} u ON u.id = {{self.ownerId}} WHERE u.active = $1`, [true]
170
+ `SELECT {{}}.* FROM {{}} JOIN {{users}} u ON u.id = {{self.ownerId}} WHERE u.active = $1`, [true]
145
171
  )
146
172
  ```
147
173
 
174
+ **Do not alias a table a placeholder still names.** Every `{{…}}` expands to the *qualified*
175
+ `"schema"."table"` — `{{self.ownerId}}` to `"schema"."table"."ownerId"` — so aliasing the same table
176
+ as `p` in the `FROM` makes Postgres reject the expansion with `invalid reference to FROM-clause
177
+ entry`. Either write the qualified form throughout, as above, or alias the table and stop using
178
+ `{{self.…}}` for its columns.
179
+
148
180
  Postgres cannot bind an identifier as a parameter — that is the entire reason this mechanism exists.
149
181
  Values have no such excuse: they stay in `params` as `$1..$n`. An unknown alias or property raises
150
182
  `PostgresPlaceholderError` at parse time.
@@ -158,13 +190,45 @@ middleware `appendPostgres` installs — use that rather than reordering registr
158
190
 
159
191
  | Method | Semantics |
160
192
  |---|---|
161
- | `create` | refuses a caller-supplied id (`RecordExists`) — use `insert` |
193
+ | `create` | refuses a caller-supplied id (`RecordExists`) — use `insert`; rejects `opts.ttl` with `UnsupportedArgumentError` (mongo parity) |
162
194
  | `update` | **replaces** the whole record — use `patch` to merge |
163
- | `pick` | `DELETE … RETURNING`: it deletes the record it returns, atomically |
164
- | `load` | rejects `opts.ttl` with `UnsupportedArgumentError` (mongo parity) |
195
+ | `load(where, { sort })` / `get(where, { sort })` | one `SELECT … ORDER BY … LIMIT 1`, so "the newest matching row" is one statement rather than a list whose head is taken |
196
+ | `delete` / `take` | one `DELETE … RETURNING`: the row is handed back by the statement that removed it. `take` **deletes** and throws `UnknownRecordError` on a miss |
197
+ | `purge` | `DELETE … RETURNING` over the criteria; refuses an empty criteria object (`UnsupportedArgumentError('purge:no-criteria')`) rather than truncating the table |
198
+ | `count` | `count(*)` over the criteria, no rows carried back |
165
199
  | `upsert` | `INSERT … ON CONFLICT DO UPDATE`, conflicting on the primary key by default |
166
200
  | `select`/`selectOne` | custom SQL marshalled back into `T`; `query`/`queryOne` return raw rows |
167
201
 
202
+ ## Paging
203
+
204
+ Postgres is **PAGED**: `DEFAULT_PAGE_SIZE` is `100`, so `list(where)` with no `size` returns the
205
+ first 100 rows — a table is unbounded, and an unpaged read is a production incident waiting for the
206
+ row count to grow. `total` is counted separately, so it describes the whole match rather than the
207
+ window, and `list(where, { size: 0 })` lifts the limit: the explicit, greppable way to read a whole
208
+ table.
209
+
210
+ `sort` becomes the `ORDER BY`, a bare field name ascending, and **the primary key is always
211
+ appended as a tiebreak**. Postgres has no implicit row order, so paginating on a non-unique sort
212
+ key silently duplicates and skips rows between pages — a difference from mongo that would surface
213
+ as a data bug rather than an error.
214
+
215
+ ## Criteria against a table
216
+
217
+ `criteriaToSql` answers the shared vocabulary ([[resource]]) in SQL, so one criteria object selects
218
+ the same rows here as it does against a collection or in memory. What is specific to a table:
219
+
220
+ - **A key naming no column raises `UnsupportedArgumentError`.** A typo that silently widened a
221
+ query to the whole table is worth being loud about — the schemaless stores cannot detect one.
222
+ - **A dotted key reaches into a jsonb column** (`#>>`), and a criteria object against a jsonb
223
+ column becomes containment (`@>`). A dotted key over a non-jsonb column is refused, and `sort`
224
+ refuses dotted paths outright: ORDER BY names a column the caller actually declared.
225
+ - `$contains`/`$contained`/`$overlaps` are the array operators `@>`, `<@` and `&&`;
226
+ `$like`/`$ilike` are `LIKE`/`ILIKE`; `$exists: true` and `$null: false` are `IS NOT NULL`, their
227
+ negations `IS NULL`.
228
+ - **`{ $in: [null, …] }` is widened explicitly.** SQL `IN` never matches NULL, so a null in the
229
+ list would silently disappear; the condition becomes `IN (…) OR IS NULL` (and the `$nin` form
230
+ `NOT IN (…) AND IS NOT NULL`), which is what the other stores answer.
231
+
168
232
  ## Errors
169
233
 
170
234
  Drizzle raises `DrizzleQueryError` and hangs the `pg` error off `cause`, so the driver `code` is not
@@ -175,9 +239,9 @@ translation — consumers classify retryable DDL races on `42P01`/`42703`.
175
239
 
176
240
  ## Config
177
241
 
178
- `DbConfig.schema` is the Postgres **SCHEMA** (layer-suffixed by `dbName()`); the **DATABASE** comes
179
- from `meta.database` and is never suffixed. Values starting with `/` are read as files by the
180
- existing `fileConfigReader` middleware.
242
+ `DbConfig.schema` is the Postgres **SCHEMA** (the service's `name(alias?)` returns it as given); the
243
+ **DATABASE** comes from `meta.database`. Values starting with `/` are read as files by the existing
244
+ `fileConfigReader` middleware.
181
245
 
182
246
  ## Tests
183
247
 
package/build/consts.d.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  export declare const DEFAULT_DB_ALIAS = "postgres";
2
- export declare const DEFAULT_PAGE_SIZE = 10;
2
+ /**
3
+ * Rows `list()` returns when the caller names no `size`.
4
+ *
5
+ * A relational table is unbounded, so an unpaged read is a production incident waiting for
6
+ * the row count to grow. Asking for everything stays possible — and greppable — as
7
+ * `list(where, { size: 0 })`.
8
+ */
9
+ export declare const DEFAULT_PAGE_SIZE = 100;
3
10
  /**
4
11
  * Table that records which code-registered migrations have already been applied.
5
12
  * Lives in the same Postgres schema as the resources it tracks, so dropping the
@@ -1 +1 @@
1
- {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,gBAAgB,aAAa,CAAA;AAE1C,eAAO,MAAM,iBAAiB,KAAK,CAAA;AAEnC;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,yBAAyB,CAAA;AAE1D,oEAAoE;AACpE,eAAO,MAAM,UAAU,OAAO,CAAA;AAE9B,8FAA8F;AAC9F,eAAO,MAAM,iBAAiB,KAAK,CAAA;AAEnC,qFAAqF;AACrF,eAAO,MAAM,QAAQ,OAAO,CAAA;AAE5B,iFAAiF;AACjF,eAAO,MAAM,YAAY,SAAS,CAAA;AAElC,eAAO,MAAM,aAAa,UAAU,CAAA;AAEpC,sEAAsE;AACtE,eAAO,MAAM,cAAc,4BAA4B,CAAA;AAEvD;;;;;;GAMG;AACH,oBAAY,UAAU;IACpB,IAAI,SAAS;IACb,QAAQ,aAAa;IACrB,GAAG,QAAQ;CACZ;AAED,oBAAY,aAAa;IACvB,KAAK,UAAU;IACf,IAAI,SAAS;IACb,GAAG,QAAQ;IACX,IAAI,SAAS;IACb,IAAI,SAAS;IACb,MAAM,WAAW;CAClB;AAED,oBAAY,mBAAmB;IAC7B,QAAQ,cAAc;IACtB,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,OAAO,aAAa;IACpB,UAAU,gBAAgB;CAC3B;AAED;;;GAGG;AACH,oBAAY,WAAW;IACrB,eAAe,UAAU;IACzB,mBAAmB,UAAU;IAC7B,gBAAgB,UAAU;IAC1B,cAAc,UAAU;IACxB,cAAc,UAAU;IACxB,eAAe,UAAU;IACzB,cAAc,UAAU;IACxB,eAAe,UAAU;IACzB,eAAe,UAAU;IACzB,YAAY,UAAU;IACtB,gBAAgB,UAAU;IAC1B,yBAAyB,UAAU;IACnC,yBAAyB,UAAU;IACnC,sBAAsB,UAAU;IAChC,oBAAoB,UAAU;IAC9B,gBAAgB,UAAU;IAC1B,mBAAmB,UAAU;CAC9B;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,YAAI,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAU,CAAA;AAEhG,uFAAuF;AACvF,oBAAY,SAAS;IACnB,IAAI,OAAO;IACX,SAAS,OAAO;IAChB,WAAW,OAAO;CACnB"}
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,gBAAgB,aAAa,CAAA;AAE1C;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,MAAM,CAAA;AAEpC;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,yBAAyB,CAAA;AAE1D,oEAAoE;AACpE,eAAO,MAAM,UAAU,OAAO,CAAA;AAE9B,8FAA8F;AAC9F,eAAO,MAAM,iBAAiB,KAAK,CAAA;AAEnC,qFAAqF;AACrF,eAAO,MAAM,QAAQ,OAAO,CAAA;AAE5B,iFAAiF;AACjF,eAAO,MAAM,YAAY,SAAS,CAAA;AAElC,eAAO,MAAM,aAAa,UAAU,CAAA;AAEpC,sEAAsE;AACtE,eAAO,MAAM,cAAc,4BAA4B,CAAA;AAEvD;;;;;;GAMG;AACH,oBAAY,UAAU;IACpB,IAAI,SAAS;IACb,QAAQ,aAAa;IACrB,GAAG,QAAQ;CACZ;AAED,oBAAY,aAAa;IACvB,KAAK,UAAU;IACf,IAAI,SAAS;IACb,GAAG,QAAQ;IACX,IAAI,SAAS;IACb,IAAI,SAAS;IACb,MAAM,WAAW;CAClB;AAED,oBAAY,mBAAmB;IAC7B,QAAQ,cAAc;IACtB,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,OAAO,aAAa;IACpB,UAAU,gBAAgB;CAC3B;AAED;;;GAGG;AACH,oBAAY,WAAW;IACrB,eAAe,UAAU;IACzB,mBAAmB,UAAU;IAC7B,gBAAgB,UAAU;IAC1B,cAAc,UAAU;IACxB,cAAc,UAAU;IACxB,eAAe,UAAU;IACzB,cAAc,UAAU;IACxB,eAAe,UAAU;IACzB,eAAe,UAAU;IACzB,YAAY,UAAU;IACtB,gBAAgB,UAAU;IAC1B,yBAAyB,UAAU;IACnC,yBAAyB,UAAU;IACnC,sBAAsB,UAAU;IAChC,oBAAoB,UAAU;IAC9B,gBAAgB,UAAU;IAC1B,mBAAmB,UAAU;CAC9B;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,YAAI,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAU,CAAA;AAEhG,uFAAuF;AACvF,oBAAY,SAAS;IACnB,IAAI,OAAO;IACX,SAAS,OAAO;IAChB,WAAW,OAAO;CACnB"}
package/build/consts.js CHANGED
@@ -1,5 +1,12 @@
1
1
  export const DEFAULT_DB_ALIAS = 'postgres';
2
- export const DEFAULT_PAGE_SIZE = 10;
2
+ /**
3
+ * Rows `list()` returns when the caller names no `size`.
4
+ *
5
+ * A relational table is unbounded, so an unpaged read is a production incident waiting for
6
+ * the row count to grow. Asking for everything stays possible — and greppable — as
7
+ * `list(where, { size: 0 })`.
8
+ */
9
+ export const DEFAULT_PAGE_SIZE = 100;
3
10
  /**
4
11
  * Table that records which code-registered migrations have already been applied.
5
12
  * Lives in the same Postgres schema as the resources it tracks, so dropping the
@@ -1 +1 @@
1
- {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAA;AAE1C,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAA;AAEnC;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAA;AAE1D,oEAAoE;AACpE,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,CAAA;AAE9B,8FAA8F;AAC9F,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAA;AAEnC,qFAAqF;AACrF,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAA;AAE5B,iFAAiF;AACjF,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAA;AAElC,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAA;AAEpC,sEAAsE;AACtE,MAAM,CAAC,MAAM,cAAc,GAAG,yBAAyB,CAAA;AAEvD;;;;;;GAMG;AACH,MAAM,CAAN,IAAY,UAIX;AAJD,WAAY,UAAU;IACpB,2BAAa,CAAA;IACb,mCAAqB,CAAA;IACrB,yBAAW,CAAA;AACb,CAAC,EAJW,UAAU,KAAV,UAAU,QAIrB;AAED,MAAM,CAAN,IAAY,aAOX;AAPD,WAAY,aAAa;IACvB,gCAAe,CAAA;IACf,8BAAa,CAAA;IACb,4BAAW,CAAA;IACX,8BAAa,CAAA;IACb,8BAAa,CAAA;IACb,kCAAiB,CAAA;AACnB,CAAC,EAPW,aAAa,KAAb,aAAa,QAOxB;AAED,MAAM,CAAN,IAAY,mBAMX;AAND,WAAY,mBAAmB;IAC7B,6CAAsB,CAAA;IACtB,4CAAqB,CAAA;IACrB,0CAAmB,CAAA;IACnB,2CAAoB,CAAA;IACpB,iDAA0B,CAAA;AAC5B,CAAC,EANW,mBAAmB,KAAnB,mBAAmB,QAM9B;AAED;;;GAGG;AACH,MAAM,CAAN,IAAY,WAkBX;AAlBD,WAAY,WAAW;IACrB,wCAAyB,CAAA;IACzB,4CAA6B,CAAA;IAC7B,yCAA0B,CAAA;IAC1B,uCAAwB,CAAA;IACxB,uCAAwB,CAAA;IACxB,wCAAyB,CAAA;IACzB,uCAAwB,CAAA;IACxB,wCAAyB,CAAA;IACzB,wCAAyB,CAAA;IACzB,qCAAsB,CAAA;IACtB,yCAA0B,CAAA;IAC1B,kDAAmC,CAAA;IACnC,kDAAmC,CAAA;IACnC,+CAAgC,CAAA;IAChC,6CAA8B,CAAA;IAC9B,yCAA0B,CAAA;IAC1B,4CAA6B,CAAA;AAC/B,CAAC,EAlBW,WAAW,KAAX,WAAW,QAkBtB;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAU,CAAA;AAEhG,uFAAuF;AACvF,MAAM,CAAN,IAAY,SAIX;AAJD,WAAY,SAAS;IACnB,4CAAW,CAAA;IACX,sDAAgB,CAAA;IAChB,0DAAkB,CAAA;AACpB,CAAC,EAJW,SAAS,KAAT,SAAS,QAIpB"}
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAA;AAE1C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAA;AAEpC;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAA;AAE1D,oEAAoE;AACpE,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,CAAA;AAE9B,8FAA8F;AAC9F,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAA;AAEnC,qFAAqF;AACrF,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAA;AAE5B,iFAAiF;AACjF,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAA;AAElC,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAA;AAEpC,sEAAsE;AACtE,MAAM,CAAC,MAAM,cAAc,GAAG,yBAAyB,CAAA;AAEvD;;;;;;GAMG;AACH,MAAM,CAAN,IAAY,UAIX;AAJD,WAAY,UAAU;IACpB,2BAAa,CAAA;IACb,mCAAqB,CAAA;IACrB,yBAAW,CAAA;AACb,CAAC,EAJW,UAAU,KAAV,UAAU,QAIrB;AAED,MAAM,CAAN,IAAY,aAOX;AAPD,WAAY,aAAa;IACvB,gCAAe,CAAA;IACf,8BAAa,CAAA;IACb,4BAAW,CAAA;IACX,8BAAa,CAAA;IACb,8BAAa,CAAA;IACb,kCAAiB,CAAA;AACnB,CAAC,EAPW,aAAa,KAAb,aAAa,QAOxB;AAED,MAAM,CAAN,IAAY,mBAMX;AAND,WAAY,mBAAmB;IAC7B,6CAAsB,CAAA;IACtB,4CAAqB,CAAA;IACrB,0CAAmB,CAAA;IACnB,2CAAoB,CAAA;IACpB,iDAA0B,CAAA;AAC5B,CAAC,EANW,mBAAmB,KAAnB,mBAAmB,QAM9B;AAED;;;GAGG;AACH,MAAM,CAAN,IAAY,WAkBX;AAlBD,WAAY,WAAW;IACrB,wCAAyB,CAAA;IACzB,4CAA6B,CAAA;IAC7B,yCAA0B,CAAA;IAC1B,uCAAwB,CAAA;IACxB,uCAAwB,CAAA;IACxB,wCAAyB,CAAA;IACzB,uCAAwB,CAAA;IACxB,wCAAyB,CAAA;IACzB,wCAAyB,CAAA;IACzB,qCAAsB,CAAA;IACtB,yCAA0B,CAAA;IAC1B,kDAAmC,CAAA;IACnC,kDAAmC,CAAA;IACnC,+CAAgC,CAAA;IAChC,6CAA8B,CAAA;IAC9B,yCAA0B,CAAA;IAC1B,4CAA6B,CAAA;AAC/B,CAAC,EAlBW,WAAW,KAAX,WAAW,QAkBtB;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAU,CAAA;AAEhG,uFAAuF;AACvF,MAAM,CAAN,IAAY,SAIX;AAJD,WAAY,SAAS;IACnB,4CAAW,CAAA;IACX,sDAAgB,CAAA;IAChB,0DAAkB,CAAA;AACpB,CAAC,EAJW,SAAS,KAAT,SAAS,QAIpB"}
@@ -2,11 +2,11 @@ import { createMigrationRegistry } from '@owlmeans/resource';
2
2
  /**
3
3
  * Per-alias declaration store, held at module scope rather than on the resource object.
4
4
  *
5
- * `reinitializeContext` rebuilds every resource, which drops anything a caller attached
6
- * by chaining. Mongo lives with that by requiring the app to pass `makeCustomResource`
7
- * and re-run the whole maker; migrations can't depend on that discipline, because losing
8
- * one silently means a data transformation never runs. Keying the declarations by alias
9
- * makes them survive any number of context switches.
5
+ * A maker may run more than once for the same alias — a custom maker wrapping the built-in
6
+ * one, a maker called again by an app or a spec. Keying the declarations by alias makes that
7
+ * a no-op: every run reads and extends the same schema, indexes and migrations, so nothing a
8
+ * caller chained onto an earlier resource object is lost. Losing a migration is silent — the
9
+ * data transformation simply never runs — which is why the store cannot live on the object.
10
10
  */
11
11
  const declarations = new Map();
12
12
  export const getDeclaration = (alias) => {
@@ -1,4 +1,4 @@
1
- import type { ResourceMaker, ResourceRecord } from '@owlmeans/resource';
1
+ import type { ResourceRecord } from '@owlmeans/resource';
2
2
  import type { PostgresResource } from './types.js';
3
- export declare const makePostgresResource: <R extends ResourceRecord, T extends PostgresResource<R> = PostgresResource<R>>(alias: string, dbAlias?: string, serviceAlias?: string, makeCustomResource?: ResourceMaker<R, T>, tableName?: string) => T;
3
+ export declare const makePostgresResource: <R extends ResourceRecord, T extends PostgresResource<R> = PostgresResource<R>>(alias: string, dbAlias?: string, serviceAlias?: string, tableName?: string) => T;
4
4
  //# sourceMappingURL=resource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACiB,aAAa,EAAE,cAAc,EACzD,MAAM,oBAAoB,CAAA;AAY3B,OAAO,KAAK,EAC8D,gBAAgB,EAEzF,MAAM,YAAY,CAAA;AA4BnB,eAAO,MAAM,oBAAoB,GAC/B,CAAC,SAAS,cAAc,EAAE,CAAC,SAAS,gBAAgB,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,SAEtE,MAAM,YAAW,MAAM,iBAAmC,MAAM,uBAClD,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,cAAc,MAAM,KAC3D,CA+cF,CAAA"}
1
+ {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACuD,cAAc,EAChF,MAAM,oBAAoB,CAAA;AAW3B,OAAO,KAAK,EAC8D,gBAAgB,EAEzF,MAAM,YAAY,CAAA;AAiBnB,eAAO,MAAM,oBAAoB,GAC/B,CAAC,SAAS,cAAc,EAAE,CAAC,SAAS,gBAAgB,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,SAEtE,MAAM,YAAW,MAAM,iBAAmC,MAAM,cAC3D,MAAM,KACjB,CA4cF,CAAA"}