@owlmeans/resource 0.1.14 → 0.1.16-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -65,7 +65,7 @@ const result: ListResult<ProjectRecord> = await ctx.project().list({
65
65
  - `save(record, opts?)` — upsert by ID
66
66
  - `update(record, opts?)` — update; throws `UnknownRecordError` if missing
67
67
  - `delete(id, opts?)` — remove; returns the deleted record or `null`
68
- - `pick(id, opts?)` — like `get` but optimized for existence checks
68
+ - `pick(id, opts?)` — **delete and return** the record; throws `UnknownRecordError` if missing. Never use it for read-only checks
69
69
 
70
70
  ### `ResourceMaker<R, T>`
71
71
 
@@ -75,10 +75,31 @@ interface ResourceMaker<R extends ResourceRecord, T extends Resource<R> = Resour
75
75
  }
76
76
  ```
77
77
 
78
+ ### Migration framework
79
+
80
+ Storage-agnostic and optional — a backend that supports code migrations (mongo, postgres)
81
+ extends `MigratableResource<Tx>`; backends with nothing to migrate simply don't (the same way
82
+ pub/sub is an optional capability of redis resources). Migrations run **automatically during
83
+ resource initialization** (app setup) — registering one is all an app ever does.
84
+
85
+ - `MigratableResource<Tx>` — `migration(name, apply, stage?): this` + `migrations()`
86
+ - `createMigrationRegistry<Tx>()` — per-alias ledger of registered migrations; declaration
87
+ order is application order; identical re-registration is a no-op, a changed body under a
88
+ used name throws `MigrationConflict`
89
+ - `MigrationStore<Tx>` — the *migration register* a database implements to track applied
90
+ migrations (`ensure` / `applied` / `baseline` / `run`); both mongo and postgres persist it
91
+ as `_owlmeans_migrations`
92
+ - `runMigrations(alias, registry, store, opts?)` — the storage-agnostic runner; with
93
+ `baseline: true` migrations are recorded instead of run (used on just-created structures);
94
+ `strictChecksum` (default) rejects edited applied bodies
95
+ - `MigrationStage.Pre` / `MigrationStage.Post` — run before / after the backend's structure
96
+ reconciliation
97
+
78
98
  ### Helpers
79
99
 
80
100
  - `prepareListOptions(defPageSize, criteria?, opts?)` — normalize list criteria + pager
81
101
  - `filterObject(obj, keep?)` — strip null/undefined fields from a record before saving
102
+ - `createDbService(alias, override, init?)` — base plumbing for `ResourceDbService` implementations
82
103
 
83
104
  ### Error Classes
84
105
 
@@ -86,6 +107,8 @@ interface ResourceMaker<R extends ResourceRecord, T extends Resource<R> = Resour
86
107
  - `UnknownRecordError` — record not found (has `.id` getter)
87
108
  - `RecordExists` — duplicate record on `create`
88
109
  - `MisshapedRecord` — invalid record structure
110
+ - `MigrationError` — a migration body threw; initialization aborts
111
+ - `MigrationConflict` — an already applied migration's body changed, or a name was redeclared with a different body
89
112
 
90
113
  ## Related Packages
91
114
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "How to use @owlmeans/resource — generic resource abstraction (CRUD over a collection of records) used by mongo-resource, redis-resource, state, storage-resource. Use when implementing a custom resource."
2
+ description: "How to use @owlmeans/resource — generic resource abstraction (CRUD over records) plus the storage-agnostic migration framework (MigratableResource, registry/store/runner). Use when implementing a custom resource or adding migration support to a database backend."
3
3
  applyTo: "**/*.ts, **/*.tsx"
4
4
  ---
5
5
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
@@ -7,27 +7,37 @@ applyTo: "**/*.ts, **/*.tsx"
7
7
  # @owlmeans/resource
8
8
 
9
9
  **Layer:** Core
10
- **Install:** `"@owlmeans/resource": "^0.1.14"` in `dependencies`
10
+ **Install:** `"@owlmeans/resource": "^0.1.16-rc.0"` in `dependencies`
11
11
 
12
12
  ## Key Exports
13
13
 
14
14
  | Export | Description |
15
15
  |--------|-------------|
16
- | `Resource<T>` types | Generic resource interface (get/list/create/update/delete) |
17
- | Errors | Typed resource errors (NotFound, AlreadyExists, etc.) |
18
- | Constants | Default resource aliases |
19
- | Service helpers | Wrap a resource as a context service |
16
+ | `Resource<T>` | Generic contract: get/load/list/save/create/update/delete/pick |
17
+ | `MigratableResource<Tx>` | Optional migration capability — `migration(name, apply, stage?)` + `migrations()`. Optional like pub/sub on redis: mongo/postgres extend it, others don't |
18
+ | `MigrationRegistry`, `MigrationStore`, `createMigrationRegistry`, `runMigrations`, `MigrationStage` | The framework: per-alias registry (declaration order = application order), the applied-tracking register a db implements, the storage-agnostic runner (`baseline` for fresh structures, strict checksums) |
19
+ | Errors | `UnknownRecordError`, `MisshapedRecord`, `RecordExists`, `RecordUpdateFailed`, `UnsupportedArgumentError`, `MigrationError`, `MigrationConflict` |
20
+ | `createDbService`, `prepareListOptions`, `DbConfig` | Service base, list helpers, `cfg.dbs` config type |
20
21
 
21
22
  ## Usage
22
23
 
23
24
  ```typescript
24
25
  import type { Resource } from '@owlmeans/resource'
25
-
26
- interface Project { id: string; name: string; entityId: string }
27
- const projects = ctx.getResource<Resource<Project>>('projects')
26
+ const projects = ctx.resource<Resource<Project>>('projects')
28
27
  const { items } = await projects.list({ entityId: 'abc' })
29
28
  ```
30
29
 
30
+ ## Migration framework rules
31
+
32
+ - Migrations run **automatically on app setup** inside resource `init()` — apps only register.
33
+ - Each migration self-checks: registry skips ledger-recorded names; `baseline: true` records
34
+ instead of running on just-created structures; bodies are written idempotent.
35
+ - New backend support: extend the concrete interface with `MigratableResource<YourTx>`; keep
36
+ registrations in a module-scope declaration keyed by alias (context rebuilds must not lose
37
+ them); implement `MigrationStore` over a durable ledger; in `init()` probe → baseline|Pre →
38
+ reconcile structure → Post; make `run` atomic with the ledger where the db allows
39
+ (postgres tx), claim-then-complete where it doesn't (mongo).
40
+
31
41
  ## Depends On
32
42
 
33
- - `@owlmeans/error`, `@owlmeans/i18n`
43
+ - `@owlmeans/context`, `@noble/hashes` (body checksums)
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "package": "@owlmeans/resource",
4
- "version": "0.1.14",
5
- "generatedAt": "2026-08-05T16:56:53.373Z",
4
+ "version": "0.1.16-rc.0",
5
+ "generatedAt": "2026-08-11T14:02:34.972Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: resource
3
- description: How to use @owlmeans/resource — generic resource abstraction (CRUD over a collection of records) used by mongo-resource, redis-resource, state, storage-resource, etc. Auto-invoked when importing from this package or implementing a custom resource type.
3
+ description: How to use @owlmeans/resource — generic resource abstraction (CRUD over records) plus the storage-agnostic migration framework (MigratableResource, migration registry/store/runner) used by mongo-resource, postgres-resource, redis-resource, state, storage-resource, etc. Auto-invoked when importing from this package, implementing a custom resource type, or adding migration support to a database backend.
4
4
  user-invocable: false
5
5
  ---
6
6
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
@@ -8,30 +8,67 @@ user-invocable: false
8
8
  # @owlmeans/resource
9
9
 
10
10
  **Layer:** Core
11
- **Install:** `"@owlmeans/resource": "^0.1.14"` in `dependencies`
11
+ **Install:** `"@owlmeans/resource": "^0.1.16-rc.0"` in `dependencies`
12
12
 
13
13
  ## Key Exports
14
14
 
15
15
  | Export | Description |
16
16
  |--------|-------------|
17
- | `Resource<T>` types | Generic resource interface (get/list/create/update/delete) |
18
- | Errors | Typed resource errors (NotFound, AlreadyExists, etc.) |
19
- | Constants | Default resource aliases, operation names |
20
- | Service helpers | Wrap a resource as a context service |
17
+ | `Resource<T>` | Generic resource contract: `get`/`load`/`list`/`save`/`create`/`update`/`delete`/`pick`. |
18
+ | `MigratableResource<Tx>` | **Optional migration capability** — `migration(name, apply, stage?)` (chainable) + `migrations()`. Optional the way pub/sub is on redis resources: mongo and postgres extend it, backends with nothing to migrate don't. |
19
+ | `Migration`, `MigrationRegistry`, `MigrationStore`, `MigrationReport`, `MigrationRunOptions` | The framework's contracts. `MigrationStore` is the *migration register* a database implements to track applied migrations (`ensure`/`applied`/`baseline`/`run`). |
20
+ | `createMigrationRegistry<Tx>()` | Storage-agnostic ledger of code-registered migrations per alias. Declaration order = application order; re-registering an identical body is a no-op, a changed body under a used name throws `MigrationConflict`. |
21
+ | `runMigrations(alias, registry, store, opts?)` | Apply one stage's pending migrations through a store. `baseline: true` records instead of running (fresh structures); `strictChecksum` (default on) rejects edited applied bodies. |
22
+ | `MigrationStage` | `Pre` (before structure reconciliation — renames, casts, rescues) / `Post` (after — backfills into new structure). |
23
+ | `createDbService` | Base for `ResourceDbService` implementations (config/alias/name/client plumbing). |
24
+ | `prepareListOptions`, `filterObject`, `createListSchema` | List/criteria helpers. |
25
+ | Errors | `ResourceError`, `UnknownRecordError`, `MisshapedRecord`, `RecordExists`, `RecordUpdateFailed`, `UnsupportedArgumentError`, `UnsupportedMethodError`, `MigrationError`, `MigrationConflict`. |
26
+ | `DbConfig`, `Config`, `Context` | Database config (`cfg.dbs`) types. |
21
27
 
22
28
  ## Usage
23
29
 
24
- A resource is a typed CRUD-over-records abstraction. Concrete implementations come from `@owlmeans/mongo-resource`, `@owlmeans/redis-resource`, `@owlmeans/state`, etc. Consumers register a resource with the context and access it via `ctx.getResource<T>(alias)`.
30
+ A resource is a typed CRUD-over-records abstraction. Concrete implementations come from
31
+ `@owlmeans/mongo-resource`, `@owlmeans/postgres-resource`, `@owlmeans/redis-resource`,
32
+ `@owlmeans/state`, etc. Consumers register with the context and access via
33
+ `ctx.resource<T>(alias)`.
25
34
 
26
35
  ```typescript
27
36
  import type { Resource } from '@owlmeans/resource'
28
37
 
29
38
  interface Project { id: string; name: string; entityId: string }
30
- const projects = ctx.getResource<Resource<Project>>('projects')
39
+ const projects = ctx.resource<Resource<Project>>('projects')
31
40
  const { items } = await projects.list({ entityId: 'abc' })
32
41
  ```
33
42
 
43
+ ## The migration framework
44
+
45
+ Migrations are **automatic on app setup**: a backend runs them inside the resource's
46
+ `init()` — registering one is all an app ever does. Every migration self-describes when it
47
+ applies: the registry skips names the store has recorded, `baseline` covers structures
48
+ created already at head, and bodies are written idempotent so an interrupted run can repeat.
49
+
50
+ Adding migration support to a new database backend:
51
+
52
+ 1. Extend the concrete resource interface with `MigratableResource<YourTx>` and design the
53
+ `Tx` façade a migration receives.
54
+ 2. Keep registrations in a **module-scope declaration keyed by alias** (see either
55
+ implementation's `declarations.ts`) — `reinitializeContext` rebuilds resource objects, and
56
+ a registry lost to a rebuild silently loses data transformations.
57
+ 3. Implement `MigrationStore` over a durable ledger when the database can store one
58
+ (`_owlmeans_migrations` in both mongo and postgres); a store-less backend may run
59
+ migrations unconditionally if its bodies are self-checking.
60
+ 4. In the resource's `init()`: probe structure → absent: `runMigrations(..., { baseline: true })`;
61
+ present: run `Pre` → reconcile structure → run `Post`.
62
+ 5. Make `run` atomic with the ledger write where the database allows it (postgres: same
63
+ transaction). Where it doesn't (mongo: no multi-doc transactions on standalone), use
64
+ claim-then-complete on a unique `(alias, name)` key and require idempotent bodies.
65
+
34
66
  ## Depends On
35
67
 
36
- - `@owlmeans/error` — for typed errors
37
- - `@owlmeans/i18n` — error messages
68
+ - `@owlmeans/context` — service/resource lifecycle
69
+ - `@noble/hashes` — migration body checksums
70
+
71
+ ## Related
72
+
73
+ - [[mongo-resource]] · [[postgres-resource]] — the two `MigratableResource` implementations
74
+ - [[redis-resource]] — a non-migratable backend (pub/sub capability instead)
package/build/consts.d.ts CHANGED
@@ -1,4 +1,20 @@
1
1
  import type { JSONSchemaType } from 'ajv';
2
2
  import type { ListResult, ResourceRecord } from './types.js';
3
+ /**
4
+ * When a migration runs relative to the structure reconciliation a db implementation
5
+ * performs at resource initialization.
6
+ *
7
+ * - `pre` — before the structure is reconciled. This is where a change reconciliation
8
+ * cannot express correctly belongs: renames, casts that need a `USING` expression,
9
+ * backfills that have to precede a `NOT NULL`. Once the migration has reshaped the
10
+ * structure, reconciliation observes no drift and emits nothing — the two mechanisms
11
+ * never fight because the migration pre-empts the reconciler.
12
+ * - `post` — after reconciliation. For data backfills into columns reconciliation has
13
+ * just created.
14
+ */
15
+ export declare enum MigrationStage {
16
+ Pre = "pre",
17
+ Post = "post"
18
+ }
3
19
  export declare const createListSchema: <T extends ResourceRecord>(schema: JSONSchemaType<T>) => JSONSchemaType<ListResult<T>>;
4
20
  //# sourceMappingURL=consts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE5D,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,cAAc,EAAE,QAAQ,cAAc,CAAC,CAAC,CAAC,KAAG,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,CA6CjH,CAAA"}
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE5D;;;;;;;;;;;GAWG;AACH,oBAAY,cAAc;IACxB,GAAG,QAAQ;IACX,IAAI,SAAS;CACd;AAED,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,cAAc,EAAE,QAAQ,cAAc,CAAC,CAAC,CAAC,KAAG,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,CA6CjH,CAAA"}
package/build/consts.js CHANGED
@@ -1,3 +1,20 @@
1
+ /**
2
+ * When a migration runs relative to the structure reconciliation a db implementation
3
+ * performs at resource initialization.
4
+ *
5
+ * - `pre` — before the structure is reconciled. This is where a change reconciliation
6
+ * cannot express correctly belongs: renames, casts that need a `USING` expression,
7
+ * backfills that have to precede a `NOT NULL`. Once the migration has reshaped the
8
+ * structure, reconciliation observes no drift and emits nothing — the two mechanisms
9
+ * never fight because the migration pre-empts the reconciler.
10
+ * - `post` — after reconciliation. For data backfills into columns reconciliation has
11
+ * just created.
12
+ */
13
+ export var MigrationStage;
14
+ (function (MigrationStage) {
15
+ MigrationStage["Pre"] = "pre";
16
+ MigrationStage["Post"] = "post";
17
+ })(MigrationStage || (MigrationStage = {}));
1
18
  export const createListSchema = (schema) => ({
2
19
  type: 'object',
3
20
  properties: {
@@ -1 +1 @@
1
- {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAA2B,MAAyB,EAAiC,EAAE,CAAC,CAAC;IACvH,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE;QACV,KAAK,EAAE;YACL,IAAI,EAAE,OAAO;YACb,KAAK,EAAE;gBACL,GAAG,MAAM;gBACT,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;oBACtC,GAAG,MAAM,CAAC,UAAU;iBACrB;gBACD,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;gBACrE,oBAAoB,EAAE,KAAK;aAC5B;SACF;QACD,KAAK,EAAE;YACL,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,IAAI,EAAE;oBACJ,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE;wBACL,KAAK,EAAE;4BACL,EAAE,IAAI,EAAE,QAAQ,EAAE;4BAClB;gCACE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE;oCACpB,EAAE,IAAI,EAAE,QAAQ,EAAE;oCAClB,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;iCACpC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC;6BAC5B;yBACF;qBACF;oBACD,QAAQ,EAAE,IAAI;iBACf;gBACD,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;gBACxC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;gBACxC,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;aAC1C;YACD,QAAQ,EAAE,EAAE;YACZ,QAAQ,EAAE,IAAI;YACd,oBAAoB,EAAE,KAAK;SAC5B;KACF;IACD,QAAQ,EAAE,CAAC,OAAO,CAAC;IACnB,oBAAoB,EAAE,KAAK;CAC5B,CAAC,CAAA"}
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,MAAM,CAAN,IAAY,cAGX;AAHD,WAAY,cAAc;IACxB,6BAAW,CAAA;IACX,+BAAa,CAAA;AACf,CAAC,EAHW,cAAc,KAAd,cAAc,QAGzB;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAA2B,MAAyB,EAAiC,EAAE,CAAC,CAAC;IACvH,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE;QACV,KAAK,EAAE;YACL,IAAI,EAAE,OAAO;YACb,KAAK,EAAE;gBACL,GAAG,MAAM;gBACT,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;oBACtC,GAAG,MAAM,CAAC,UAAU;iBACrB;gBACD,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;gBACrE,oBAAoB,EAAE,KAAK;aAC5B;SACF;QACD,KAAK,EAAE;YACL,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,IAAI,EAAE;oBACJ,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE;wBACL,KAAK,EAAE;4BACL,EAAE,IAAI,EAAE,QAAQ,EAAE;4BAClB;gCACE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE;oCACpB,EAAE,IAAI,EAAE,QAAQ,EAAE;oCAClB,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;iCACpC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC;6BAC5B;yBACF;qBACF;oBACD,QAAQ,EAAE,IAAI;iBACf;gBACD,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;gBACxC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;gBACxC,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;aAC1C;YACD,QAAQ,EAAE,EAAE;YACZ,QAAQ,EAAE,IAAI;YACd,oBAAoB,EAAE,KAAK;SAC5B;KACF;IACD,QAAQ,EAAE,CAAC,OAAO,CAAC;IACnB,oBAAoB,EAAE,KAAK;CAC5B,CAAC,CAAA"}
package/build/errors.d.ts CHANGED
@@ -29,4 +29,22 @@ export declare class UnsupportedMethodError extends ResourceError {
29
29
  static typeName: string;
30
30
  constructor(method: string);
31
31
  }
32
+ /**
33
+ * A registered migration threw while being applied. Resource initialization aborts —
34
+ * a structure problem has to fail the boot loudly rather than leave the process
35
+ * running against a half-shaped database.
36
+ */
37
+ export declare class MigrationError extends ResourceError {
38
+ static typeName: string;
39
+ constructor(msg: string);
40
+ }
41
+ /**
42
+ * A migration name was registered twice with different bodies, or an already applied
43
+ * migration's body has changed since it ran. Write a new migration instead of editing
44
+ * one that has already been applied somewhere.
45
+ */
46
+ export declare class MigrationConflict extends ResourceError {
47
+ static typeName: string;
48
+ constructor(msg: string);
49
+ }
32
50
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AACA,OAAO,EAAG,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEjD,qBAAa,aAAc,SAAQ,cAAc;IAC/C,OAAuB,QAAQ,SAAkB;gBAErC,OAAO,GAAE,MAAgB;CAGtC;AAED,qBAAa,kBAAmB,SAAQ,aAAa;IACnD,OAAuB,QAAQ,SAAgD;IAE/E,gBAAuB,WAAW,EAAE,MAAM,CAAM;gBAEpC,EAAE,EAAE,MAAM;IAKtB,IAAI,EAAE,IAAI,MAAM,CAEf;CACF;AAED,qBAAa,eAAgB,SAAQ,aAAa;IAChD,OAAuB,QAAQ,SAA6C;gBAEhE,GAAG,EAAE,MAAM;CAIxB;AAED,qBAAa,YAAa,SAAQ,aAAa;IAC7C,OAAuB,QAAQ,SAA0C;gBAE7D,GAAG,EAAE,MAAM;CAIxB;AAED,qBAAa,kBAAmB,SAAQ,aAAa;IACnD,OAAuB,QAAQ,SAAgD;gBAEnE,GAAG,EAAE,MAAM;CAIxB;AAED,qBAAa,wBAAyB,SAAQ,aAAa;IACzD,OAAuB,QAAQ,SAAsD;gBAEzE,QAAQ,EAAE,MAAM;CAI7B;AAED,qBAAa,sBAAuB,SAAQ,aAAa;IACvD,OAAuB,QAAQ,SAAoD;gBAEvE,MAAM,EAAE,MAAM;CAI3B"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AACA,OAAO,EAAG,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEjD,qBAAa,aAAc,SAAQ,cAAc;IAC/C,OAAuB,QAAQ,SAAkB;gBAErC,OAAO,GAAE,MAAgB;CAGtC;AAED,qBAAa,kBAAmB,SAAQ,aAAa;IACnD,OAAuB,QAAQ,SAAgD;IAE/E,gBAAuB,WAAW,EAAE,MAAM,CAAM;gBAEpC,EAAE,EAAE,MAAM;IAKtB,IAAI,EAAE,IAAI,MAAM,CAEf;CACF;AAED,qBAAa,eAAgB,SAAQ,aAAa;IAChD,OAAuB,QAAQ,SAA6C;gBAEhE,GAAG,EAAE,MAAM;CAIxB;AAED,qBAAa,YAAa,SAAQ,aAAa;IAC7C,OAAuB,QAAQ,SAA0C;gBAE7D,GAAG,EAAE,MAAM;CAIxB;AAED,qBAAa,kBAAmB,SAAQ,aAAa;IACnD,OAAuB,QAAQ,SAAgD;gBAEnE,GAAG,EAAE,MAAM;CAIxB;AAED,qBAAa,wBAAyB,SAAQ,aAAa;IACzD,OAAuB,QAAQ,SAAsD;gBAEzE,QAAQ,EAAE,MAAM;CAI7B;AAED,qBAAa,sBAAuB,SAAQ,aAAa;IACvD,OAAuB,QAAQ,SAAoD;gBAEvE,MAAM,EAAE,MAAM;CAI3B;AAED;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,aAAa;IAC/C,OAAuB,QAAQ,SAA4C;gBAE/D,GAAG,EAAE,MAAM;CAIxB;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,aAAa;IAClD,OAAuB,QAAQ,SAA+C;gBAElE,GAAG,EAAE,MAAM;CAIxB"}
package/build/errors.js CHANGED
@@ -51,6 +51,30 @@ export class UnsupportedMethodError extends ResourceError {
51
51
  this.type = UnsupportedMethodError.typeName;
52
52
  }
53
53
  }
54
+ /**
55
+ * A registered migration threw while being applied. Resource initialization aborts —
56
+ * a structure problem has to fail the boot loudly rather than leave the process
57
+ * running against a half-shaped database.
58
+ */
59
+ export class MigrationError extends ResourceError {
60
+ static typeName = `${ResourceError.typeName}MigrationError`;
61
+ constructor(msg) {
62
+ super(`migration-failed:${msg}`);
63
+ this.type = MigrationError.typeName;
64
+ }
65
+ }
66
+ /**
67
+ * A migration name was registered twice with different bodies, or an already applied
68
+ * migration's body has changed since it ran. Write a new migration instead of editing
69
+ * one that has already been applied somewhere.
70
+ */
71
+ export class MigrationConflict extends ResourceError {
72
+ static typeName = `${ResourceError.typeName}MigrationConflict`;
73
+ constructor(msg) {
74
+ super(`migration-conflict:${msg}`);
75
+ this.type = MigrationConflict.typeName;
76
+ }
77
+ }
54
78
  ResilientError.registerErrorClass(ResourceError);
55
79
  ResilientError.registerErrorClass(UnknownRecordError);
56
80
  ResilientError.registerErrorClass(MisshapedRecord);
@@ -58,4 +82,6 @@ ResilientError.registerErrorClass(RecordExists);
58
82
  ResilientError.registerErrorClass(RecordUpdateFailed);
59
83
  ResilientError.registerErrorClass(UnsupportedArgumentError);
60
84
  ResilientError.registerErrorClass(UnsupportedMethodError);
85
+ ResilientError.registerErrorClass(MigrationError);
86
+ ResilientError.registerErrorClass(MigrationConflict);
61
87
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AACA,OAAO,EAAG,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEjD,MAAM,OAAO,aAAc,SAAQ,cAAc;IACxC,MAAM,CAAU,QAAQ,GAAG,eAAe,CAAA;IAEjD,YAAY,UAAkB,OAAO;QACnC,KAAK,CAAC,aAAa,CAAC,QAAQ,EAAE,YAAY,OAAO,EAAE,CAAC,CAAA;IACtD,CAAC;;AAGH,MAAM,OAAO,kBAAmB,SAAQ,aAAa;IAC5C,MAAM,CAAU,QAAQ,GAAG,GAAG,aAAa,CAAC,QAAQ,oBAAoB,CAAA;IAExE,MAAM,CAAU,WAAW,GAAW,GAAG,CAAA;IAEhD,YAAY,EAAU;QACpB,KAAK,CAAC,iBAAiB,kBAAkB,CAAC,WAAW,GAAG,EAAE,EAAE,CAAC,CAAA;QAC7D,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC,QAAQ,CAAA;IACzC,CAAC;IAED,IAAI,EAAE;QACJ,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;IAC9D,CAAC;;AAGH,MAAM,OAAO,eAAgB,SAAQ,aAAa;IACzC,MAAM,CAAU,QAAQ,GAAG,GAAG,aAAa,CAAC,QAAQ,iBAAiB,CAAA;IAE5E,YAAY,GAAW;QACrB,KAAK,CAAC,oBAAoB,GAAG,EAAE,CAAC,CAAA;QAChC,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAA;IACtC,CAAC;;AAGH,MAAM,OAAO,YAAa,SAAQ,aAAa;IACtC,MAAM,CAAU,QAAQ,GAAG,GAAG,aAAa,CAAC,QAAQ,cAAc,CAAA;IAEzE,YAAY,GAAW;QACrB,KAAK,CAAC,iBAAiB,GAAG,EAAE,CAAC,CAAA;QAC7B,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAA;IACnC,CAAC;;AAGH,MAAM,OAAO,kBAAmB,SAAQ,aAAa;IAC5C,MAAM,CAAU,QAAQ,GAAG,GAAG,aAAa,CAAC,QAAQ,oBAAoB,CAAA;IAE/E,YAAY,GAAW;QACrB,KAAK,CAAC,wBAAwB,GAAG,EAAE,CAAC,CAAA;QACpC,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC,QAAQ,CAAA;IACzC,CAAC;;AAGH,MAAM,OAAO,wBAAyB,SAAQ,aAAa;IAClD,MAAM,CAAU,QAAQ,GAAG,GAAG,aAAa,CAAC,QAAQ,0BAA0B,CAAA;IAErF,YAAY,QAAgB;QAC1B,KAAK,CAAC,wBAAwB,QAAQ,EAAE,CAAC,CAAA;QACzC,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC,QAAQ,CAAA;IAC/C,CAAC;;AAGH,MAAM,OAAO,sBAAuB,SAAQ,aAAa;IAChD,MAAM,CAAU,QAAQ,GAAG,GAAG,aAAa,CAAC,QAAQ,wBAAwB,CAAA;IAEnF,YAAY,MAAc;QACxB,KAAK,CAAC,sBAAsB,MAAM,EAAE,CAAC,CAAA;QACrC,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC,QAAQ,CAAA;IAC7C,CAAC;;AAGH,cAAc,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAA;AAChD,cAAc,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,CAAA;AACrD,cAAc,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAA;AAClD,cAAc,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAA;AAC/C,cAAc,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,CAAA;AACrD,cAAc,CAAC,kBAAkB,CAAC,wBAAwB,CAAC,CAAA;AAC3D,cAAc,CAAC,kBAAkB,CAAC,sBAAsB,CAAC,CAAA"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAa,MAAM,YAAY,CAAA;AAEtE,eAAO,MAAM,kBAAkB,GAAI,cAAa,MAAW,EAAE,WAAW,WAAW,GAAG,YAAY,EAAE,OAAO,WAAW,KAAG,WAsBxH,CAAA;AAED,eAAO,MAAM,YAAY,GAAI,CAAC,SAAS,EAAE,EAAE,KAAK,CAAC,EAAE,OAAO,MAAM,EAAE,KAAG,CAMxD,CAAA"}
1
+ {"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAa,MAAM,YAAY,CAAA;AAEtE,eAAO,MAAM,kBAAkB,GAAI,cAAa,MAAW,EAAE,WAAW,WAAW,GAAG,YAAY,EAAE,OAAO,WAAW,KAAG,WAwBxH,CAAA;AAED,eAAO,MAAM,YAAY,GAAI,CAAC,SAAS,EAAE,EAAE,KAAK,CAAC,EAAE,OAAO,MAAM,EAAE,KAAG,CAMxD,CAAA"}
package/build/helper.js CHANGED
@@ -4,13 +4,14 @@ export const prepareListOptions = (defPageSize = 10, criteria, opts) => {
4
4
  criteria = {};
5
5
  opts = {};
6
6
  }
7
- else if ('criteria' in criteria) {
8
- criteria = criteria.criteria;
9
- opts = { pager: criteria.pager };
10
- }
11
- else if ('pager' in criteria) {
12
- opts = { pager: criteria.pager };
13
- criteria = {};
7
+ else if ('criteria' in criteria || 'pager' in criteria) {
8
+ /**
9
+ * Both keys have to be read off the original object before it's reassigned —
10
+ * reading `pager` after narrowing to `.criteria` silently drops the pager.
11
+ */
12
+ const options = criteria;
13
+ opts = { pager: options.pager };
14
+ criteria = (options.criteria ?? {});
14
15
  }
15
16
  }
16
17
  const size = (opts?.pager?.size ?? defPageSize);
@@ -1 +1 @@
1
- {"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,cAAsB,EAAE,EAAE,QAAqC,EAAE,IAAkB,EAAe,EAAE;IACrI,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QACjB,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;YACrB,QAAQ,GAAG,EAAE,CAAA;YACb,IAAI,GAAG,EAAE,CAAA;QACX,CAAC;aAAM,IAAI,UAAU,IAAI,QAAQ,EAAE,CAAC;YAClC,QAAQ,GAAG,QAAQ,CAAC,QAAwB,CAAA;YAC5C,IAAI,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,KAA8B,EAAE,CAAA;QAC3D,CAAC;aAAM,IAAI,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,IAAI,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,KAA8B,EAAE,CAAA;YACzD,QAAQ,GAAG,EAAE,CAAA;QACf,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,WAAW,CAAC,CAAA;IAC/C,MAAM,KAAK,GAAc;QACvB,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI;QACvB,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC;QAC5B,IAAI,EAAE,KAAK,EAAE,CAAC;KACf,CAAA;IAED,OAAO,EAAE,QAAQ,EAAE,QAAwB,EAAE,KAAK,EAAE,CAAA;AACtD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,CAAe,GAAM,EAAE,IAAe,EAAK,EAAE,CACvE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;IAC/C,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1C,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,OAAO,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAA;AACjC,CAAC,EAAE,EAAO,CAAC,CAAA"}
1
+ {"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,cAAsB,EAAE,EAAE,QAAqC,EAAE,IAAkB,EAAe,EAAE;IACrI,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QACjB,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;YACrB,QAAQ,GAAG,EAAE,CAAA;YACb,IAAI,GAAG,EAAE,CAAA;QACX,CAAC;aAAM,IAAI,UAAU,IAAI,QAAQ,IAAI,OAAO,IAAI,QAAQ,EAAE,CAAC;YACzD;;;eAGG;YACH,MAAM,OAAO,GAAG,QAAuB,CAAA;YACvC,IAAI,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAA;YAC/B,QAAQ,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAiB,CAAA;QACrD,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,WAAW,CAAC,CAAA;IAC/C,MAAM,KAAK,GAAc;QACvB,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI;QACvB,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC;QAC5B,IAAI,EAAE,KAAK,EAAE,CAAC;KACf,CAAA;IAED,OAAO,EAAE,QAAQ,EAAE,QAAwB,EAAE,KAAK,EAAE,CAAA;AACtD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,CAAe,GAAM,EAAE,IAAe,EAAK,EAAE,CACvE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;IAC/C,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1C,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,OAAO,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAA;AACjC,CAAC,EAAE,EAAO,CAAC,CAAA"}
package/build/index.d.ts CHANGED
@@ -2,5 +2,6 @@ export * from './errors.js';
2
2
  export * from './consts.js';
3
3
  export * from './service.js';
4
4
  export * from './helper.js';
5
+ export * from './migration.js';
5
6
  export type * from './types.js';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,mBAAmB,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,mBAAmB,YAAY,CAAA"}
package/build/index.js CHANGED
@@ -2,4 +2,5 @@ export * from './errors.js';
2
2
  export * from './consts.js';
3
3
  export * from './service.js';
4
4
  export * from './helper.js';
5
+ export * from './migration.js';
5
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA"}
@@ -0,0 +1,21 @@
1
+ import type { MigrationRegistry, MigrationReport, MigrationRunOptions, MigrationStore } from './types.js';
2
+ /**
3
+ * Storage agnostic ledger of code registered migrations for a single resource alias.
4
+ *
5
+ * Declaration order is the application order — the registry never sorts by name, so a
6
+ * migration that depends on an earlier one only has to be registered after it.
7
+ */
8
+ export declare const createMigrationRegistry: <Tx = unknown>() => MigrationRegistry<Tx>;
9
+ /**
10
+ * Apply the pending migrations of one stage for one resource alias.
11
+ *
12
+ * With `baseline: true` the structure has just been created from the current schema, so
13
+ * every registered migration is already satisfied by it — they're recorded as applied
14
+ * without running. Replaying a historical `ADD COLUMN` against a table that was just
15
+ * created with that column would fail, and replaying a backfill would scan for nothing.
16
+ *
17
+ * @throws {MigrationError} a migration threw — initialization must abort.
18
+ * @throws {MigrationConflict} an already applied migration's body has changed.
19
+ */
20
+ export declare const runMigrations: <Tx = unknown>(alias: string, registry: MigrationRegistry<Tx>, store: MigrationStore<Tx>, opts?: MigrationRunOptions) => Promise<MigrationReport>;
21
+ //# sourceMappingURL=migration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACC,iBAAiB,EAAE,eAAe,EAAE,mBAAmB,EAAE,cAAc,EACnF,MAAM,YAAY,CAAA;AAqBnB;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,EAAE,GAAG,OAAO,OAAK,iBAAiB,CAAC,EAAE,CA+B5E,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,GAAU,EAAE,GAAG,OAAO,EAC9C,OAAO,MAAM,EACb,UAAU,iBAAiB,CAAC,EAAE,CAAC,EAC/B,OAAO,cAAc,CAAC,EAAE,CAAC,EACzB,OAAO,mBAAmB,KACzB,OAAO,CAAC,eAAe,CAqDzB,CAAA"}
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.js","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":"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"}
package/build/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { BasicConfig, BasicContext, BasicResource, LazyService } from '@owlmeans/context';
2
+ import type { MigrationStage } from './consts.js';
2
3
  export interface Resource<T extends ResourceRecord> extends BasicResource {
3
4
  /**
4
5
  * @throws {UnknownRecordError}
@@ -101,6 +102,73 @@ export interface DbConfig<P extends {} = {}> {
101
102
  encryptionKey?: string;
102
103
  meta?: P;
103
104
  }
105
+ /**
106
+ * Optional migration capability of a Resource implementation — optional the same way
107
+ * pub/sub is on redis resources: a backend that supports code migrations extends this
108
+ * interface (mongo, postgres), a backend with nothing to migrate simply never implements
109
+ * it. The base {@link Resource} contract stays migration free.
110
+ *
111
+ * The capability is automatic: implementations run the registered migrations during
112
+ * resource initialization (app setup), so a migration only has to be registered — never
113
+ * invoked. Backends with a durable structure also implement {@link MigrationStore}, the
114
+ * register that tracks which migrations have been applied.
115
+ */
116
+ export interface MigratableResource<Tx = unknown> {
117
+ /**
118
+ * Register a migration, applied once per database in declaration order.
119
+ *
120
+ * Chainable and idempotent: re-registering the same name with the same body is a no-op,
121
+ * which is what makes it safe to call from a resource maker that `reinitializeContext`
122
+ * re-runs. Re-registering a *changed* body under a used name throws
123
+ * {@link MigrationConflict}.
124
+ */
125
+ migration: (name: string, apply: (tx: Tx) => Promise<void>, stage?: MigrationStage) => this;
126
+ /** The registered migrations for this resource's alias. Read-only; use {@link MigratableResource.migration}. */
127
+ migrations: () => MigrationRegistry<Tx>;
128
+ }
129
+ export interface Migration<Tx = unknown> {
130
+ name: string;
131
+ stage: MigrationStage;
132
+ apply: (tx: Tx) => Promise<void>;
133
+ /** Stable hash of the migration body — detects edits to an already applied migration. */
134
+ checksum: string;
135
+ }
136
+ export interface MigrationRegistry<Tx = unknown> {
137
+ register: (name: string, apply: (tx: Tx) => Promise<void>, stage?: MigrationStage) => MigrationRegistry<Tx>;
138
+ list: (stage?: MigrationStage) => Migration<Tx>[];
139
+ has: (name: string) => boolean;
140
+ }
141
+ /**
142
+ * The database specific half of the migration subsystem. Postgres and Mongo each
143
+ * provide an implementation; {@link runMigrations} stays storage agnostic.
144
+ */
145
+ export interface MigrationStore<Tx = unknown> {
146
+ /** Create the tracking structure if it's absent. */
147
+ ensure: () => Promise<void>;
148
+ /** Names already applied for the given resource alias, mapped to their recorded checksum. */
149
+ applied: (alias: string) => Promise<Record<string, string | null>>;
150
+ /** Record migrations as applied *without* running them — used on freshly created structures. */
151
+ baseline: (alias: string, migrations: Migration<Tx>[]) => Promise<void>;
152
+ /** Run one migration and record it atomically. */
153
+ run: (alias: string, migration: Migration<Tx>) => Promise<void>;
154
+ }
155
+ export interface MigrationRunOptions {
156
+ stage?: MigrationStage;
157
+ /**
158
+ * The structure has just been created, so every registered migration is already
159
+ * satisfied by it — record them instead of running them.
160
+ */
161
+ baseline?: boolean;
162
+ /** Throw when an already applied migration's body has changed. Defaults to `true`. */
163
+ strictChecksum?: boolean;
164
+ }
165
+ export interface MigrationReport {
166
+ alias: string;
167
+ stage: MigrationStage;
168
+ applied: string[];
169
+ baselined: string[];
170
+ skipped: string[];
171
+ }
104
172
  export interface Config extends BasicConfig {
105
173
  dbs?: DbConfig[];
106
174
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE9F,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,cAAc,CAAE,SAAQ,aAAa;IACvE;;OAEG;IACH,GAAG,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3F,IAAI,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAA;IACnG,IAAI,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,QAAQ,CAAC,EAAE,WAAW,GAAG,YAAY,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;IAC9G,IAAI,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7E;;OAEG;IACH,MAAM,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACzF;;OAEG;IACH,MAAM,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/E,MAAM,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAA;IAC/E;;OAEG;IACH,IAAI,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACvE;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AAED,KAAK,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAA;AAC5B,MAAM,WAAW,YAAa,SAAQ,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;CAAI;AAEhH,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,QAAQ,CAAC,EAAE,YAAY,CAAA;CACxB;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAElD,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,cAAc;IAClD,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB;AAED,KAAK,MAAM,GAAG,MAAM,GAAG,aAAa,CAAA;AAEpC,MAAM,WAAW,aAAc,SAAQ,gBAAgB;IACrD,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,cAAc,EAAE,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;IAC1F,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,CAAC,CAAA;CAC7C;AAED,MAAM,WAAW,iBAAiB,CAAC,EAAE,EAAE,MAAM,CAAE,SAAQ,WAAW;IAChE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B;;OAEG;IACH,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,EAAE,CAAC,CAAA;IAEnC;;OAEG;IACH,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,QAAQ,CAAA;IAEpC;;OAEG;IACH,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAE7C;;OAEG;IACH,iBAAiB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,KAAK,MAAM,CAAA;IAExD;;OAEG;IACH,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,KAAK,MAAM,CAAA;IAE3C;;OAEG;IACH,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC5C;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,cAAc;IAChD,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;IACzE,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;CAC5E;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,cAAc;IACtD,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;IAC3D,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;CAC9D;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,IAAI,CAAC,EAAE,CAAC,CAAA;CACT;AAED,MAAM,WAAW,MAAO,SAAQ,WAAW;IACzC,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAA;CACjB;AAED,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,YAAY,CAAC,CAAC,CAAC;CAC1E"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC9F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,cAAc,CAAE,SAAQ,aAAa;IACvE;;OAEG;IACH,GAAG,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3F,IAAI,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAA;IACnG,IAAI,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,QAAQ,CAAC,EAAE,WAAW,GAAG,YAAY,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;IAC9G,IAAI,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7E;;OAEG;IACH,MAAM,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACzF;;OAEG;IACH,MAAM,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/E,MAAM,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAA;IAC/E;;OAEG;IACH,IAAI,EAAE,CAAC,IAAI,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACvE;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AAED,KAAK,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAA;AAC5B,MAAM,WAAW,YAAa,SAAQ,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;CAAI;AAEhH,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,QAAQ,CAAC,EAAE,YAAY,CAAA;CACxB;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAElD,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,cAAc;IAClD,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB;AAED,KAAK,MAAM,GAAG,MAAM,GAAG,aAAa,CAAA;AAEpC,MAAM,WAAW,aAAc,SAAQ,gBAAgB;IACrD,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,cAAc,EAAE,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;IAC1F,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,CAAC,CAAA;CAC7C;AAED,MAAM,WAAW,iBAAiB,CAAC,EAAE,EAAE,MAAM,CAAE,SAAQ,WAAW;IAChE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B;;OAEG;IACH,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,EAAE,CAAC,CAAA;IAEnC;;OAEG;IACH,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,QAAQ,CAAA;IAEpC;;OAEG;IACH,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAE7C;;OAEG;IACH,iBAAiB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,KAAK,MAAM,CAAA;IAExD;;OAEG;IACH,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,KAAK,MAAM,CAAA;IAE3C;;OAEG;IACH,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC5C;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,cAAc;IAChD,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;IACzE,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;CAC5E;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,cAAc;IACtD,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;IAC3D,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;CAC9D;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,IAAI,CAAC,EAAE,CAAC,CAAA;CACT;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,kBAAkB,CAAC,EAAE,GAAG,OAAO;IAC9C;;;;;;;OAOG;IACH,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE,cAAc,KAAK,IAAI,CAAA;IAC3F,gHAAgH;IAChH,UAAU,EAAE,MAAM,iBAAiB,CAAC,EAAE,CAAC,CAAA;CACxC;AAED,MAAM,WAAW,SAAS,CAAC,EAAE,GAAG,OAAO;IACrC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAChC,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,iBAAiB,CAAC,EAAE,GAAG,OAAO;IAC7C,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE,cAAc,KAAK,iBAAiB,CAAC,EAAE,CAAC,CAAA;IAC3G,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,cAAc,KAAK,SAAS,CAAC,EAAE,CAAC,EAAE,CAAA;IACjD,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;CAC/B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc,CAAC,EAAE,GAAG,OAAO;IAC1C,oDAAoD;IACpD,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,6FAA6F;IAC7F,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAA;IAClE,gGAAgG;IAChG,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACvE,kDAAkD;IAClD,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAChE;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,cAAc,CAAA;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,sFAAsF;IACtF,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,cAAc,CAAA;IACrB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,MAAM,WAAW,MAAO,SAAQ,WAAW;IACzC,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAA;CACjB;AAED,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,YAAY,CAAC,CAAC,CAAC;CAC1E"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/resource",
3
- "version": "0.1.14",
3
+ "version": "0.1.16-rc.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -22,8 +22,8 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@noble/hashes": "^1.5.0",
25
- "@owlmeans/context": "^0.1.14",
26
- "@owlmeans/error": "^0.1.14",
25
+ "@owlmeans/context": "^0.1.16-rc.0",
26
+ "@owlmeans/error": "^0.1.16-rc.0",
27
27
  "@scure/base": "^1.1.9"
28
28
  },
29
29
  "peerDependencies": {
package/src/consts.ts CHANGED
@@ -1,6 +1,23 @@
1
1
  import type { JSONSchemaType } from 'ajv'
2
2
  import type { ListResult, ResourceRecord } from './types.js'
3
3
 
4
+ /**
5
+ * When a migration runs relative to the structure reconciliation a db implementation
6
+ * performs at resource initialization.
7
+ *
8
+ * - `pre` — before the structure is reconciled. This is where a change reconciliation
9
+ * cannot express correctly belongs: renames, casts that need a `USING` expression,
10
+ * backfills that have to precede a `NOT NULL`. Once the migration has reshaped the
11
+ * structure, reconciliation observes no drift and emits nothing — the two mechanisms
12
+ * never fight because the migration pre-empts the reconciler.
13
+ * - `post` — after reconciliation. For data backfills into columns reconciliation has
14
+ * just created.
15
+ */
16
+ export enum MigrationStage {
17
+ Pre = 'pre',
18
+ Post = 'post'
19
+ }
20
+
4
21
  export const createListSchema = <T extends ResourceRecord>(schema: JSONSchemaType<T>): JSONSchemaType<ListResult<T>> => ({
5
22
  type: 'object',
6
23
  properties: {
package/src/errors.ts CHANGED
@@ -69,6 +69,34 @@ export class UnsupportedMethodError extends ResourceError {
69
69
  }
70
70
  }
71
71
 
72
+ /**
73
+ * A registered migration threw while being applied. Resource initialization aborts —
74
+ * a structure problem has to fail the boot loudly rather than leave the process
75
+ * running against a half-shaped database.
76
+ */
77
+ export class MigrationError extends ResourceError {
78
+ public static override typeName = `${ResourceError.typeName}MigrationError`
79
+
80
+ constructor(msg: string) {
81
+ super(`migration-failed:${msg}`)
82
+ this.type = MigrationError.typeName
83
+ }
84
+ }
85
+
86
+ /**
87
+ * A migration name was registered twice with different bodies, or an already applied
88
+ * migration's body has changed since it ran. Write a new migration instead of editing
89
+ * one that has already been applied somewhere.
90
+ */
91
+ export class MigrationConflict extends ResourceError {
92
+ public static override typeName = `${ResourceError.typeName}MigrationConflict`
93
+
94
+ constructor(msg: string) {
95
+ super(`migration-conflict:${msg}`)
96
+ this.type = MigrationConflict.typeName
97
+ }
98
+ }
99
+
72
100
  ResilientError.registerErrorClass(ResourceError)
73
101
  ResilientError.registerErrorClass(UnknownRecordError)
74
102
  ResilientError.registerErrorClass(MisshapedRecord)
@@ -76,3 +104,5 @@ ResilientError.registerErrorClass(RecordExists)
76
104
  ResilientError.registerErrorClass(RecordUpdateFailed)
77
105
  ResilientError.registerErrorClass(UnsupportedArgumentError)
78
106
  ResilientError.registerErrorClass(UnsupportedMethodError)
107
+ ResilientError.registerErrorClass(MigrationError)
108
+ ResilientError.registerErrorClass(MigrationConflict)
package/src/helper.ts CHANGED
@@ -5,12 +5,14 @@ export const prepareListOptions = (defPageSize: number = 10, criteria?: ListOpti
5
5
  if (criteria == null) {
6
6
  criteria = {}
7
7
  opts = {}
8
- } else if ('criteria' in criteria) {
9
- criteria = criteria.criteria as ListCriteria
10
- opts = { pager: criteria.pager as ListPager | undefined }
11
- } else if ('pager' in criteria) {
12
- opts = { pager: criteria.pager as ListPager | undefined }
13
- criteria = {}
8
+ } else if ('criteria' in criteria || 'pager' in criteria) {
9
+ /**
10
+ * Both keys have to be read off the original object before it's reassigned —
11
+ * reading `pager` after narrowing to `.criteria` silently drops the pager.
12
+ */
13
+ const options = criteria as ListOptions
14
+ opts = { pager: options.pager }
15
+ criteria = (options.criteria ?? {}) as ListCriteria
14
16
  }
15
17
  }
16
18
 
package/src/index.ts CHANGED
@@ -3,4 +3,5 @@ export * from './errors.js'
3
3
  export * from './consts.js'
4
4
  export * from './service.js'
5
5
  export * from './helper.js'
6
+ export * from './migration.js'
6
7
  export type * from './types.js'
Binary file
package/src/types.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { BasicConfig, BasicContext, BasicResource, LazyService } from '@owlmeans/context'
2
+ import type { MigrationStage } from './consts.js'
2
3
 
3
4
  export interface Resource<T extends ResourceRecord> extends BasicResource {
4
5
  /**
@@ -121,6 +122,79 @@ export interface DbConfig<P extends {} = {}> {
121
122
  meta?: P
122
123
  }
123
124
 
125
+ /**
126
+ * Optional migration capability of a Resource implementation — optional the same way
127
+ * pub/sub is on redis resources: a backend that supports code migrations extends this
128
+ * interface (mongo, postgres), a backend with nothing to migrate simply never implements
129
+ * it. The base {@link Resource} contract stays migration free.
130
+ *
131
+ * The capability is automatic: implementations run the registered migrations during
132
+ * resource initialization (app setup), so a migration only has to be registered — never
133
+ * invoked. Backends with a durable structure also implement {@link MigrationStore}, the
134
+ * register that tracks which migrations have been applied.
135
+ */
136
+ export interface MigratableResource<Tx = unknown> {
137
+ /**
138
+ * Register a migration, applied once per database in declaration order.
139
+ *
140
+ * Chainable and idempotent: re-registering the same name with the same body is a no-op,
141
+ * which is what makes it safe to call from a resource maker that `reinitializeContext`
142
+ * re-runs. Re-registering a *changed* body under a used name throws
143
+ * {@link MigrationConflict}.
144
+ */
145
+ migration: (name: string, apply: (tx: Tx) => Promise<void>, stage?: MigrationStage) => this
146
+ /** The registered migrations for this resource's alias. Read-only; use {@link MigratableResource.migration}. */
147
+ migrations: () => MigrationRegistry<Tx>
148
+ }
149
+
150
+ export interface Migration<Tx = unknown> {
151
+ name: string
152
+ stage: MigrationStage
153
+ apply: (tx: Tx) => Promise<void>
154
+ /** Stable hash of the migration body — detects edits to an already applied migration. */
155
+ checksum: string
156
+ }
157
+
158
+ export interface MigrationRegistry<Tx = unknown> {
159
+ register: (name: string, apply: (tx: Tx) => Promise<void>, stage?: MigrationStage) => MigrationRegistry<Tx>
160
+ list: (stage?: MigrationStage) => Migration<Tx>[]
161
+ has: (name: string) => boolean
162
+ }
163
+
164
+ /**
165
+ * The database specific half of the migration subsystem. Postgres and Mongo each
166
+ * provide an implementation; {@link runMigrations} stays storage agnostic.
167
+ */
168
+ export interface MigrationStore<Tx = unknown> {
169
+ /** Create the tracking structure if it's absent. */
170
+ ensure: () => Promise<void>
171
+ /** Names already applied for the given resource alias, mapped to their recorded checksum. */
172
+ applied: (alias: string) => Promise<Record<string, string | null>>
173
+ /** Record migrations as applied *without* running them — used on freshly created structures. */
174
+ baseline: (alias: string, migrations: Migration<Tx>[]) => Promise<void>
175
+ /** Run one migration and record it atomically. */
176
+ run: (alias: string, migration: Migration<Tx>) => Promise<void>
177
+ }
178
+
179
+ export interface MigrationRunOptions {
180
+ stage?: MigrationStage
181
+ /**
182
+ * The structure has just been created, so every registered migration is already
183
+ * satisfied by it — record them instead of running them.
184
+ */
185
+ baseline?: boolean
186
+ /** Throw when an already applied migration's body has changed. Defaults to `true`. */
187
+ strictChecksum?: boolean
188
+ }
189
+
190
+ export interface MigrationReport {
191
+ alias: string
192
+ stage: MigrationStage
193
+ applied: string[]
194
+ baselined: string[]
195
+ skipped: string[]
196
+ }
197
+
124
198
  export interface Config extends BasicConfig {
125
199
  dbs?: DbConfig[]
126
200
  }