@fougere/defaults 0.5.0-alpha.1 → 0.7.0-alpha.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/dist/index.d.ts CHANGED
@@ -1,13 +1,4 @@
1
- /**
2
- * @fougere/defaults — the conventional boot, declared once.
3
- *
4
- * Booting an app from `fougere.config.ts` means the same three opinionated
5
- * bindings every time: the container is `container`, `db: sqlite`
6
- * realizes through `schema-sql`, and `remotes:` are reached over
7
- * `transport-http`. core stays pure (it knows none of these); this layer-2
8
- * package supplies them on top of `core.boot()`. Nuxt's fallback, the CLI's
9
- * `serve`/`call`, and a standalone frond host are all projections of it.
10
- */
1
+ /** @fougere/defaults — the conventional boot, declared once. */
11
2
  import { type App, type CreateAppOptions } from '@fougere/core';
12
3
  export { resolveStorage, declaresStorage, storageFrom } from './storage.js';
13
4
  export type { DbConfig, SourcesConfig, DeclaredStorage, Placement, ResolvedStorage } from './storage.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAMhE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC5E,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEzG,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAC;CAC7C;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,GAAG,CAAC,CAe7F"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAMhE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC5E,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEzG,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAC;CAC7C;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,GAAG,CAAC,CAe7F"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAiB,MAAM,cAAc,CAAC;AAE7D,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAkB5E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAY,EAAE,IAAI,GAAmB,EAAE;IAC7E,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC;IACrC,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IAE9E,OAAO,IAAI,CAAC;QACV,IAAI;QACJ,eAAe;QACf,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;QACzC,eAAe,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS;QAC3E,wDAAwD;QACxD,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,EAAc,EAAG,GAA6B,CAAC,OAAgB,CAAC;QAChG,UAAU,EAAE,IAAI,CAAC,UAAU;KAC5B,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAiB,MAAM,cAAc,CAAC;AAE7D,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAkB5E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAY,EAAE,IAAI,GAAmB,EAAE;IAC7E,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC;IACrC,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IAE9E,OAAO,IAAI,CAAC;QACV,IAAI;QACJ,eAAe;QACf,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;QACzC,eAAe,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS;QAC3E,wDAAwD;QACxD,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,EAAc,EAAG,GAA6B,CAAC,OAAgB,CAAC;QAChG,UAAU,EAAE,IAAI,CAAC,UAAU;KAC5B,CAAC,CAAC;AACL,CAAC"}
package/dist/storage.d.ts CHANGED
@@ -1,22 +1,16 @@
1
- /**
2
- * Storage resolution — `config.db` → a working data layer.
3
- *
4
- * THE single place that names a storage package. Every host (the conventional
5
- * boot, the Nuxt fallback, the CLI's frond host) calls this instead of wiring an
6
- * engine itself; swapping the implementation is a change here and nowhere else.
7
- *
8
- * Before this existed, each host re-resolved `db: 'sqlite'` inline — which is
9
- * why an engine change looked like it touched seven files.
10
- */
1
+ /** Storage resolution — `config.db` → a working data layer. */
11
2
  import type { App } from '@fougere/core';
12
- import { type DialectName, type Setup } from '@fougere/adapter-sql';
3
+ import { type Source } from '@fougere/core';
4
+ import '@fougere/adapter-sql/sqlite';
13
5
  /** The `db` field of fougere.config.ts, read structurally. */
14
6
  export type DbConfig = false | 'sqlite' | {
7
+ source?: string;
15
8
  dialect?: string;
16
9
  path?: string;
17
10
  } | undefined;
18
11
  /** A named source and the entities it holds — the `sources` field, read structurally. */
19
12
  export type SourcesConfig = Record<string, {
13
+ source?: string;
20
14
  dialect?: string;
21
15
  path?: string;
22
16
  entities: string[];
@@ -24,15 +18,10 @@ export type SourcesConfig = Record<string, {
24
18
  export interface ResolvedStorage {
25
19
  /** Opaque handle handed to auth providers. */
26
20
  db?: unknown;
27
- ormFactory: ((entity: any, name: string) => any) | undefined;
21
+ storageFactory: ((entity: any, name: string) => any) | undefined;
28
22
  /**
29
- * The source an entity's rows live in — what decides whether a frame gets a real
30
- * transaction or an unwind it replays itself.
31
- *
32
- * Absent when the caller built a `ResolvedStorage` by hand: there is then one factory
33
- * and no routing, so every entity IS on one engine — but nothing here can reach into it
34
- * for a transaction, and a frame falls back to compensating. Not knowing and promising
35
- * atomicity are two different things.
23
+ * The source an entity's rows live in — what decides whether a frame gets a real transaction or
24
+ * an unwind it replays itself.
36
25
  */
37
26
  sourceOf?: (entityName: string) => string;
38
27
  /**
@@ -42,71 +31,33 @@ export interface ResolvedStorage {
42
31
  dbOf?: (source: string) => unknown;
43
32
  /** Every source that has an engine, the default one first. */
44
33
  sources?: () => string[];
45
- /** Run `fn` inside one transaction of that source, with an ORM factory bound to it. */
46
- transacted?: <R>(source: string, fn: (ormFactory: (entity: any, name: string) => any) => Promise<R>) => Promise<R>;
34
+ /** Whether that source hands out a transaction the dual of running one in it. */
35
+ transacts?: (source: string) => boolean;
36
+ /** Run `fn` inside one transaction of that source, with a storage factory bound to it. */
37
+ transacted?: <R>(source: string, fn: (storageFactory: (entity: any, name: string) => any) => Promise<R>) => Promise<R>;
47
38
  /**
48
39
  * Brings the schema up to date once the app is scanned — the storage's `up`, handed to
49
- * `migrating()`. It was called `afterBoot`, a word that also meant the host's own
50
- * post-boot and was read in four places under the two senses.
40
+ * `migrating()`.
51
41
  */
52
42
  migrate?: (app: App) => Promise<void> | void;
53
- /**
54
- * Close every engine this opened — the dual of opening them, declared by whoever did.
55
- *
56
- * `boot()` calls the factory that lands here, so `boot()` is what owns closing it: a
57
- * container disposes what IT built, and this connection was handed in. Without this
58
- * the pool of a discarded app stayed open, which is what turning the ring makes
59
- * ordinary rather than rare.
60
- */
43
+ /** Close every engine this opened — the dual of opening them, declared by whoever did. */
61
44
  close?: () => Promise<void>;
62
- /** Raw synchronous handle, when the engine exposes one. */
63
- raw?: {
64
- exec(sql: string): void;
65
- };
66
- dialect?: DialectName;
67
45
  }
68
46
  /** Does this config ask for persistence at all? */
69
47
  export declare function declaresStorage(dbConf: DbConfig): boolean;
70
- /**
71
- * Resolve the data layer. `db: false` (or absent) means a frond with no
72
- * persistence of its own — the caller decides what to fall back to.
73
- *
74
- * `sources` names the places that are NOT the default one. An entity it does not
75
- * name stays in `db`, so an app with one database declares nothing and behaves
76
- * exactly as before.
77
- */
48
+ /** Resolve the data layer. */
78
49
  export declare function resolveStorage(dbConf: DbConfig, sources?: SourcesConfig): ResolvedStorage;
79
- /** One source: the engine that holds it, and the entities that live there. */
50
+ /** One source: what realizes it, and the entities that live there. */
80
51
  export interface Placement {
81
- setup: Setup;
52
+ source: Source;
82
53
  entities: string[];
83
54
  }
84
55
  export interface DeclaredStorage {
85
56
  /** The default source — where an entity no placement names lands. */
86
- db: Setup;
57
+ db: Source;
87
58
  /** The other places. Absent means one source, the way it always was. */
88
59
  sources?: Record<string, Placement>;
89
60
  }
90
- /**
91
- * The same routing `resolveStorage` performs, over engines the CALLER built.
92
- *
93
- * `resolveStorage` reads a config file, and a config file cannot hold a live Kysely
94
- * dialect — so it can only ever resolve sqlite, the one driver this package depends
95
- * on. That was fine while `db:` was alone, because the escape hatch was to abandon
96
- * the convention entirely and hand `configureFougere` your own factory. With several
97
- * sources that stopped being an escape: a user wanting Postgres for ONE of them had
98
- * to re-implement the routing and the per-source migration to keep the other.
99
- *
100
- * So the two jobs are separated. Resolving a NAME into an engine is sqlite-only and
101
- * stays there; placing entities and migrating each source is engine-agnostic and
102
- * lives here, where any `Setup` is welcome:
103
- *
104
- * ```ts
105
- * configureFougere(storageFrom({
106
- * db: setupSqlite({ path: '.data/app.db' }),
107
- * sources: { legacy: { setup: setupKysely(pgDialect, 'postgres'), entities: ['Book'] } },
108
- * }));
109
- * ```
110
- */
61
+ /** The same routing `resolveStorage` performs, over engines the CALLER built. */
111
62
  export declare function storageFrom(declared: DeclaredStorage): ResolvedStorage;
112
63
  //# sourceMappingURL=storage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAAW,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAG7E,8DAA8D;AAC9D,MAAM,MAAM,QAAQ,GAChB,KAAK,GACL,QAAQ,GACR;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACnC,SAAS,CAAC;AAEd,yFAAyF;AACzF,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,CAAC;AAEhH,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,GAAG,CAAC,GAAG,SAAS,CAAC;IAC7D;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C;;;OAGG;IACH,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IACnC,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,MAAM,EAAE,CAAC;IACzB,uFAAuF;IACvF,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACnH;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7C;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,2DAA2D;IAC3D,GAAG,CAAC,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAClC,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED,mDAAmD;AACnD,wBAAgB,eAAe,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,CAGzD;AAqDD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,eAAe,CAazF;AAED,8EAA8E;AAC9E,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,EAAE,EAAE,KAAK,CAAC;IACV,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,eAAe,GAAG,eAAe,CA6DtE"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAAW,KAAK,MAAM,EAAmB,MAAM,eAAe,CAAC;AAEtE,OAAO,6BAA6B,CAAC;AAErC,8DAA8D;AAC9D,MAAM,MAAM,QAAQ,GAChB,KAAK,GACL,QAAQ,GACR;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACpD,SAAS,CAAC;AAEd,yFAAyF;AACzF,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,CAAC;AAEjI,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,GAAG,CAAC,GAAG,SAAS,CAAC;IACjE;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C;;;OAGG;IACH,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IACnC,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,MAAM,EAAE,CAAC;IACzB,mFAAmF;IACnF,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,0FAA0F;IAC1F,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,cAAc,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACvH;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7C,0FAA0F;IAC1F,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAE7B;AAED,mDAAmD;AACnD,wBAAgB,eAAe,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,CAGzD;AAyBD,8BAA8B;AAC9B,wBAAgB,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,eAAe,CAWzF;AAYD,sEAAsE;AACtE,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACrC;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,QAAQ,EAAE,eAAe,GAAG,eAAe,CAkEtE"}
package/dist/storage.js CHANGED
@@ -1,42 +1,16 @@
1
1
  import { Fronds } from '@fougere/core';
2
2
  import { lowerFirst } from '@fougere/core/contract';
3
- import { migrate } from '@fougere/adapter-sql';
4
- import { setupSqlite } from '@fougere/adapter-sql/sqlite';
3
+ import { Sources } from '@fougere/core';
4
+ // Imported for its side effect: it is what makes `source: 'sql'` an answered name.
5
+ import '@fougere/adapter-sql/sqlite';
5
6
  /** Does this config ask for persistence at all? */
6
7
  export function declaresStorage(dbConf) {
7
8
  if (dbConf === false || dbConf === undefined)
8
9
  return false;
9
10
  return true;
10
11
  }
11
- /**
12
- * Only sqlite is resolvable from a NAME — its driver is a dependency here. Any other
13
- * engine needs a Kysely dialect INSTANCE, which only the host can build because only
14
- * the host has its driver. So the name is refused, and the way in is named.
15
- *
16
- * `dialect` used to be declared and never read: `db: { dialect: 'postgres' }` started
17
- * SQLite and said nothing — a config whose central word was ignored.
18
- */
19
- function refuseUnresolvable(declared, field) {
20
- if (declared === undefined || declared === 'sqlite')
21
- return;
22
- throw new Error(`${field} '${declared}' cannot be resolved from its name — only 'sqlite' can, ` +
23
- `because it is the one driver this package depends on. For ${declared}, build the ` +
24
- `Kysely dialect yourself and call setupKysely(dialect, '${declared}') ` +
25
- `from @fougere/adapter-sql.`);
26
- }
27
- /**
28
- * The app as ONE source sees it: the entities that live there, and the names of those
29
- * that do not.
30
- *
31
- * The second half is what lets the DDL stop lying. A batch holding every entity could
32
- * never tell a cross-source target from a typo, so it derived a table name and emitted
33
- * a foreign key against a table that may not exist. Cut per source, `elsewhere` says
34
- * which misses are legitimate — and a target in neither list is a mistake, out loud.
35
- *
36
- * Auth entities ride with the default source: a provider's tables are the app's own,
37
- * and nothing yet lets one declare where it lives.
38
- */
39
- function partition(app, holds, withAuth) {
12
+ /** The app as ONE source sees it. */
13
+ function viewOf(app, holds, withAuth) {
40
14
  const typed = app;
41
15
  const fronds = typed.fronds
42
16
  .map((frond) => ({ ...frond, entities: frond.entities.filter((entry) => holds(entry.name)) }))
@@ -46,52 +20,30 @@ function partition(app, holds, withAuth) {
46
20
  auth: withAuth ? typed.auth : undefined,
47
21
  // Lifted, because this function reads its app structurally on purpose — a caller
48
22
  // may hand it a shape that is app-LIKE, and the question is still the same one.
49
- elsewhere: Fronds.scanned(typed.fronds).entityNames().filter((name) => !holds(name)),
23
+ elsewhere: Fronds.hosting(typed.fronds).entityNames().filter((name) => !holds(name)),
50
24
  };
51
25
  }
52
- /**
53
- * Resolve the data layer. `db: false` (or absent) means a frond with no
54
- * persistence of its own — the caller decides what to fall back to.
55
- *
56
- * `sources` names the places that are NOT the default one. An entity it does not
57
- * name stays in `db`, so an app with one database declares nothing and behaves
58
- * exactly as before.
59
- */
26
+ /** Resolve the data layer. */
60
27
  export function resolveStorage(dbConf, sources) {
61
28
  if (!declaresStorage(dbConf))
62
- return { ormFactory: undefined };
63
- refuseUnresolvable(typeof dbConf === 'object' ? dbConf.dialect : (dbConf || undefined), 'db.dialect');
29
+ return { storageFactory: undefined };
64
30
  const named = {};
65
31
  for (const [name, conf] of Object.entries(sources ?? {})) {
66
- refuseUnresolvable(conf.dialect, `sources.${name}.dialect`);
67
- named[name] = { setup: setupSqlite({ path: conf.path }), entities: conf.entities };
32
+ named[name] = { source: built(conf, `sources.${name}`), entities: conf.entities };
68
33
  }
69
34
  return storageFrom({
70
- db: setupSqlite({ path: typeof dbConf === 'object' ? dbConf.path : undefined }),
35
+ db: built(typeof dbConf === 'object' ? dbConf : {}, 'db'),
71
36
  sources: named,
72
37
  });
73
38
  }
74
- /**
75
- * The same routing `resolveStorage` performs, over engines the CALLER built.
76
- *
77
- * `resolveStorage` reads a config file, and a config file cannot hold a live Kysely
78
- * dialect — so it can only ever resolve sqlite, the one driver this package depends
79
- * on. That was fine while `db:` was alone, because the escape hatch was to abandon
80
- * the convention entirely and hand `configureFougere` your own factory. With several
81
- * sources that stopped being an escape: a user wanting Postgres for ONE of them had
82
- * to re-implement the routing and the per-source migration to keep the other.
83
- *
84
- * So the two jobs are separated. Resolving a NAME into an engine is sqlite-only and
85
- * stays there; placing entities and migrating each source is engine-agnostic and
86
- * lives here, where any `Setup` is welcome:
87
- *
88
- * ```ts
89
- * configureFougere(storageFrom({
90
- * db: setupSqlite({ path: '.data/app.db' }),
91
- * sources: { legacy: { setup: setupKysely(pgDialect, 'postgres'), entities: ['Book'] } },
92
- * }));
93
- * ```
94
- */
39
+ /** One config entry, resolved to the adapter it names. */
40
+ function built(conf, field) {
41
+ const name = conf.source ?? DEFAULT_ADAPTER;
42
+ return Sources.open(name, conf, `${field}.source`);
43
+ }
44
+ /** What a config naming no adapter means. */
45
+ const DEFAULT_ADAPTER = 'sql';
46
+ /** The same routing `resolveStorage` performs, over engines the CALLER built. */
95
47
  export function storageFrom(declared) {
96
48
  const { db: base, sources } = declared;
97
49
  // Where each named entity lives. An entity claimed by two sources is refused naming
@@ -100,7 +52,7 @@ export function storageFrom(declared) {
100
52
  const home = new Map();
101
53
  const engines = new Map();
102
54
  for (const [name, placement] of Object.entries(sources ?? {})) {
103
- engines.set(name, placement.setup);
55
+ engines.set(name, placement.source);
104
56
  for (const entity of placement.entities) {
105
57
  const key = lowerFirst(entity);
106
58
  const claimed = home.get(key);
@@ -120,34 +72,40 @@ export function storageFrom(declared) {
120
72
  return (source && engines.get(source)) || base;
121
73
  };
122
74
  return {
75
+ // Opaque, and narrowed by whoever needs it — auth wants a handle, the CLI wants Kysely.
123
76
  db: base.db,
124
- ormFactory: (entity, name) => engineFor(name).ormFactory(entity, name),
77
+ storageFactory: (entity, name) => engineFor(name).storageFactory(entity, name),
125
78
  sourceOf,
126
79
  dbOf: (source) => engineOf(source)?.db,
127
80
  sources: () => [DEFAULT, ...engines.keys()],
128
- transacted: (source, fn) => {
81
+ // Asked of the source that will run the work, never of the default one: a frame whose
82
+ // members all live in a transactional source gets its transaction even when `db:` has
83
+ // none. A source that hands out none leaves the answer `false`, and a frame reads it:
84
+ // `boot/together.ts` compensates instead, and says which of the two it built.
85
+ transacts: (source) => engineOf(source)?.transacted !== undefined,
86
+ transacted: async (source, fn) => {
129
87
  const engine = engineOf(source);
130
88
  if (!engine)
131
89
  throw new Error(`No source named '${source}' — declared sources are ${[DEFAULT, ...engines.keys()].join(', ')}.`);
90
+ if (!engine.transacted)
91
+ throw new Error(`Source '${source}' hands out no transaction.`);
132
92
  return engine.transacted(fn);
133
93
  },
134
- raw: base.sqlite,
135
- dialect: base.dialect,
136
- // Additive migration: creates missing tables AND adds columns an entity gained.
137
- // One pass per source, each seeing only its own tables — which is what makes a
138
- // cross-source `ref()` a miss rather than a constraint against a stranger.
94
+ // One pass per source, each seeing only its own entities and the NAMES of the others —
95
+ // which is what makes a cross-source `ref()` a miss rather than a constraint against a
96
+ // stranger. What a pass DOES is the source's own: it knows its engine, this does not.
139
97
  migrate: async (app) => {
140
- await migrate(partition(app, (name) => !home.has(lowerFirst(name)), true), base.db);
98
+ await base.migrate?.(viewOf(app, (name) => !home.has(lowerFirst(name)), true));
141
99
  for (const [name, engine] of engines) {
142
- await migrate(partition(app, (e) => home.get(lowerFirst(e)) === name, false), engine.db);
100
+ await engine.migrate?.(viewOf(app, (e) => home.get(lowerFirst(e)) === name, false));
143
101
  }
144
102
  },
145
- // Every engine, the default one last: a named source may hold what the default
146
- // refers to, and closing in reverse of opening is the rule everywhere else.
103
+ // Every source, the default one last: a named source may hold what the default refers
104
+ // to, and closing in reverse of opening is the rule everywhere else.
147
105
  close: async () => {
148
106
  for (const engine of [...engines.values()].reverse())
149
- await engine.db.destroy();
150
- await base.db.destroy();
107
+ await engine.close?.();
108
+ await base.close?.();
151
109
  },
152
110
  };
153
111
  }
@@ -1 +1 @@
1
- {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fougere/defaults",
3
- "version": "0.5.0-alpha.1",
3
+ "version": "0.7.0-alpha.0",
4
4
  "description": "The conventional boot of a Fougere app — the one place storage is wired.",
5
5
  "keywords": [
6
6
  "fougere",
@@ -29,10 +29,10 @@
29
29
  "src"
30
30
  ],
31
31
  "dependencies": {
32
- "@fougere/core": "0.5.0-alpha.1",
33
- "@fougere/adapter-sql": "0.5.0-alpha.1",
34
- "@fougere/container": "0.5.0-alpha.1",
35
- "@fougere/transport-http": "0.5.0-alpha.1"
32
+ "@fougere/core": "0.7.0-alpha.0",
33
+ "@fougere/adapter-sql": "0.7.0-alpha.0",
34
+ "@fougere/container": "0.7.0-alpha.0",
35
+ "@fougere/transport-http": "0.7.0-alpha.0"
36
36
  },
37
37
  "publishConfig": {
38
38
  "access": "public"
@@ -41,7 +41,7 @@
41
41
  "kysely": "^0.28.17",
42
42
  "better-sqlite3": "^13.0.3",
43
43
  "@types/better-sqlite3": "^7.6.13",
44
- "@fougere/schema": "0.5.0-alpha.1"
44
+ "@fougere/schema": "0.7.0-alpha.0"
45
45
  },
46
46
  "scripts": {
47
47
  "build": "rm -rf dist && tsc",
package/src/index.ts CHANGED
@@ -1,13 +1,4 @@
1
- /**
2
- * @fougere/defaults — the conventional boot, declared once.
3
- *
4
- * Booting an app from `fougere.config.ts` means the same three opinionated
5
- * bindings every time: the container is `container`, `db: sqlite`
6
- * realizes through `schema-sql`, and `remotes:` are reached over
7
- * `transport-http`. core stays pure (it knows none of these); this layer-2
8
- * package supplies them on top of `core.boot()`. Nuxt's fallback, the CLI's
9
- * `serve`/`call`, and a standalone frond host are all projections of it.
10
- */
1
+ /** @fougere/defaults — the conventional boot, declared once. */
11
2
  import { type App, type CreateAppOptions } from '@fougere/core';
12
3
  import { boot, loadConfig } from '@fougere/core/node';
13
4
  import { createContainer } from '@fougere/container';
package/src/storage.ts CHANGED
@@ -1,41 +1,28 @@
1
- /**
2
- * Storage resolution — `config.db` → a working data layer.
3
- *
4
- * THE single place that names a storage package. Every host (the conventional
5
- * boot, the Nuxt fallback, the CLI's frond host) calls this instead of wiring an
6
- * engine itself; swapping the implementation is a change here and nowhere else.
7
- *
8
- * Before this existed, each host re-resolved `db: 'sqlite'` inline — which is
9
- * why an engine change looked like it touched seven files.
10
- */
1
+ /** Storage resolution — `config.db` → a working data layer. */
11
2
  import type { App } from '@fougere/core';
12
3
  import { Fronds, type FrondDescriptor } from '@fougere/core';
13
4
  import { lowerFirst } from '@fougere/core/contract';
14
- import { migrate, type DialectName, type Setup } from '@fougere/adapter-sql';
15
- import { setupSqlite } from '@fougere/adapter-sql/sqlite';
5
+ import { Sources, type Source, type SourceView } from '@fougere/core';
6
+ // Imported for its side effect: it is what makes `source: 'sql'` an answered name.
7
+ import '@fougere/adapter-sql/sqlite';
16
8
 
17
9
  /** The `db` field of fougere.config.ts, read structurally. */
18
10
  export type DbConfig =
19
11
  | false
20
12
  | 'sqlite'
21
- | { dialect?: string; path?: string }
13
+ | { source?: string; dialect?: string; path?: string }
22
14
  | undefined;
23
15
 
24
16
  /** A named source and the entities it holds — the `sources` field, read structurally. */
25
- export type SourcesConfig = Record<string, { dialect?: string; path?: string; entities: string[] }> | undefined;
17
+ export type SourcesConfig = Record<string, { source?: string; dialect?: string; path?: string; entities: string[] }> | undefined;
26
18
 
27
19
  export interface ResolvedStorage {
28
20
  /** Opaque handle handed to auth providers. */
29
21
  db?: unknown;
30
- ormFactory: ((entity: any, name: string) => any) | undefined;
22
+ storageFactory: ((entity: any, name: string) => any) | undefined;
31
23
  /**
32
- * The source an entity's rows live in — what decides whether a frame gets a real
33
- * transaction or an unwind it replays itself.
34
- *
35
- * Absent when the caller built a `ResolvedStorage` by hand: there is then one factory
36
- * and no routing, so every entity IS on one engine — but nothing here can reach into it
37
- * for a transaction, and a frame falls back to compensating. Not knowing and promising
38
- * atomicity are two different things.
24
+ * The source an entity's rows live in — what decides whether a frame gets a real transaction or
25
+ * an unwind it replays itself.
39
26
  */
40
27
  sourceOf?: (entityName: string) => string;
41
28
  /**
@@ -45,26 +32,18 @@ export interface ResolvedStorage {
45
32
  dbOf?: (source: string) => unknown;
46
33
  /** Every source that has an engine, the default one first. */
47
34
  sources?: () => string[];
48
- /** Run `fn` inside one transaction of that source, with an ORM factory bound to it. */
49
- transacted?: <R>(source: string, fn: (ormFactory: (entity: any, name: string) => any) => Promise<R>) => Promise<R>;
35
+ /** Whether that source hands out a transaction the dual of running one in it. */
36
+ transacts?: (source: string) => boolean;
37
+ /** Run `fn` inside one transaction of that source, with a storage factory bound to it. */
38
+ transacted?: <R>(source: string, fn: (storageFactory: (entity: any, name: string) => any) => Promise<R>) => Promise<R>;
50
39
  /**
51
40
  * Brings the schema up to date once the app is scanned — the storage's `up`, handed to
52
- * `migrating()`. It was called `afterBoot`, a word that also meant the host's own
53
- * post-boot and was read in four places under the two senses.
41
+ * `migrating()`.
54
42
  */
55
43
  migrate?: (app: App) => Promise<void> | void;
56
- /**
57
- * Close every engine this opened — the dual of opening them, declared by whoever did.
58
- *
59
- * `boot()` calls the factory that lands here, so `boot()` is what owns closing it: a
60
- * container disposes what IT built, and this connection was handed in. Without this
61
- * the pool of a discarded app stayed open, which is what turning the ring makes
62
- * ordinary rather than rare.
63
- */
44
+ /** Close every engine this opened — the dual of opening them, declared by whoever did. */
64
45
  close?: () => Promise<void>;
65
46
  /** Raw synchronous handle, when the engine exposes one. */
66
- raw?: { exec(sql: string): void };
67
- dialect?: DialectName;
68
47
  }
69
48
 
70
49
  /** Does this config ask for persistence at all? */
@@ -73,41 +52,13 @@ export function declaresStorage(dbConf: DbConfig): boolean {
73
52
  return true;
74
53
  }
75
54
 
76
- /**
77
- * Only sqlite is resolvable from a NAME — its driver is a dependency here. Any other
78
- * engine needs a Kysely dialect INSTANCE, which only the host can build because only
79
- * the host has its driver. So the name is refused, and the way in is named.
80
- *
81
- * `dialect` used to be declared and never read: `db: { dialect: 'postgres' }` started
82
- * SQLite and said nothing — a config whose central word was ignored.
83
- */
84
- function refuseUnresolvable(declared: string | undefined, field: string): void {
85
- if (declared === undefined || declared === 'sqlite') return;
86
- throw new Error(
87
- `${field} '${declared}' cannot be resolved from its name — only 'sqlite' can, ` +
88
- `because it is the one driver this package depends on. For ${declared}, build the ` +
89
- `Kysely dialect yourself and call setupKysely(dialect, '${declared}') ` +
90
- `from @fougere/adapter-sql.`,
91
- );
92
- }
93
55
 
94
- /**
95
- * The app as ONE source sees it: the entities that live there, and the names of those
96
- * that do not.
97
- *
98
- * The second half is what lets the DDL stop lying. A batch holding every entity could
99
- * never tell a cross-source target from a typo, so it derived a table name and emitted
100
- * a foreign key against a table that may not exist. Cut per source, `elsewhere` says
101
- * which misses are legitimate — and a target in neither list is a mistake, out loud.
102
- *
103
- * Auth entities ride with the default source: a provider's tables are the app's own,
104
- * and nothing yet lets one declare where it lives.
105
- */
106
- function partition(
56
+ /** The app as ONE source sees it. */
57
+ function viewOf(
107
58
  app: App,
108
59
  holds: (name: string) => boolean,
109
60
  withAuth: boolean,
110
- ): unknown {
61
+ ): SourceView {
111
62
  const typed = app as unknown as {
112
63
  fronds: { name: string; entities: { name: string }[] }[];
113
64
  auth?: unknown;
@@ -120,67 +71,48 @@ function partition(
120
71
  auth: withAuth ? typed.auth : undefined,
121
72
  // Lifted, because this function reads its app structurally on purpose — a caller
122
73
  // may hand it a shape that is app-LIKE, and the question is still the same one.
123
- elsewhere: Fronds.scanned(typed.fronds as FrondDescriptor[]).entityNames().filter((name) => !holds(name)),
74
+ elsewhere: Fronds.hosting(typed.fronds as FrondDescriptor[]).entityNames().filter((name) => !holds(name)),
124
75
  };
125
76
  }
126
77
 
127
- /**
128
- * Resolve the data layer. `db: false` (or absent) means a frond with no
129
- * persistence of its own — the caller decides what to fall back to.
130
- *
131
- * `sources` names the places that are NOT the default one. An entity it does not
132
- * name stays in `db`, so an app with one database declares nothing and behaves
133
- * exactly as before.
134
- */
78
+ /** Resolve the data layer. */
135
79
  export function resolveStorage(dbConf: DbConfig, sources?: SourcesConfig): ResolvedStorage {
136
- if (!declaresStorage(dbConf)) return { ormFactory: undefined };
80
+ if (!declaresStorage(dbConf)) return { storageFactory: undefined };
137
81
 
138
- refuseUnresolvable(typeof dbConf === 'object' ? dbConf.dialect : (dbConf || undefined), 'db.dialect');
139
82
  const named: Record<string, Placement> = {};
140
83
  for (const [name, conf] of Object.entries(sources ?? {})) {
141
- refuseUnresolvable(conf.dialect, `sources.${name}.dialect`);
142
- named[name] = { setup: setupSqlite({ path: conf.path }), entities: conf.entities };
84
+ named[name] = { source: built(conf, `sources.${name}`), entities: conf.entities };
143
85
  }
144
86
  return storageFrom({
145
- db: setupSqlite({ path: typeof dbConf === 'object' ? dbConf.path : undefined }),
87
+ db: built(typeof dbConf === 'object' ? dbConf : {}, 'db'),
146
88
  sources: named,
147
89
  });
148
90
  }
149
91
 
150
- /** One source: the engine that holds it, and the entities that live there. */
92
+ /** One config entry, resolved to the adapter it names. */
93
+ function built(conf: Record<string, unknown>, field: string): Source {
94
+ const name = (conf.source as string | undefined) ?? DEFAULT_ADAPTER;
95
+
96
+ return Sources.open(name, conf as never, `${field}.source`);
97
+ }
98
+
99
+ /** What a config naming no adapter means. */
100
+ const DEFAULT_ADAPTER = 'sql';
101
+
102
+ /** One source: what realizes it, and the entities that live there. */
151
103
  export interface Placement {
152
- setup: Setup;
104
+ source: Source;
153
105
  entities: string[];
154
106
  }
155
107
 
156
108
  export interface DeclaredStorage {
157
109
  /** The default source — where an entity no placement names lands. */
158
- db: Setup;
110
+ db: Source;
159
111
  /** The other places. Absent means one source, the way it always was. */
160
112
  sources?: Record<string, Placement>;
161
113
  }
162
114
 
163
- /**
164
- * The same routing `resolveStorage` performs, over engines the CALLER built.
165
- *
166
- * `resolveStorage` reads a config file, and a config file cannot hold a live Kysely
167
- * dialect — so it can only ever resolve sqlite, the one driver this package depends
168
- * on. That was fine while `db:` was alone, because the escape hatch was to abandon
169
- * the convention entirely and hand `configureFougere` your own factory. With several
170
- * sources that stopped being an escape: a user wanting Postgres for ONE of them had
171
- * to re-implement the routing and the per-source migration to keep the other.
172
- *
173
- * So the two jobs are separated. Resolving a NAME into an engine is sqlite-only and
174
- * stays there; placing entities and migrating each source is engine-agnostic and
175
- * lives here, where any `Setup` is welcome:
176
- *
177
- * ```ts
178
- * configureFougere(storageFrom({
179
- * db: setupSqlite({ path: '.data/app.db' }),
180
- * sources: { legacy: { setup: setupKysely(pgDialect, 'postgres'), entities: ['Book'] } },
181
- * }));
182
- * ```
183
- */
115
+ /** The same routing `resolveStorage` performs, over engines the CALLER built. */
184
116
  export function storageFrom(declared: DeclaredStorage): ResolvedStorage {
185
117
  const { db: base, sources } = declared;
186
118
 
@@ -188,9 +120,9 @@ export function storageFrom(declared: DeclaredStorage): ResolvedStorage {
188
120
  // both: the rows would be read from one and written to the other, by whichever
189
121
  // registration ran last — the same silent duplicate `remotes:` refuses one level up.
190
122
  const home = new Map<string, string>();
191
- const engines = new Map<string, Setup>();
123
+ const engines = new Map<string, Source>();
192
124
  for (const [name, placement] of Object.entries(sources ?? {})) {
193
- engines.set(name, placement.setup);
125
+ engines.set(name, placement.source);
194
126
  for (const entity of placement.entities) {
195
127
  const key = lowerFirst(entity);
196
128
  const claimed = home.get(key);
@@ -214,32 +146,37 @@ export function storageFrom(declared: DeclaredStorage): ResolvedStorage {
214
146
  };
215
147
 
216
148
  return {
217
- db: base.db,
218
- ormFactory: (entity: any, name: string) => engineFor(name).ormFactory(entity, name),
149
+ // Opaque, and narrowed by whoever needs it — auth wants a handle, the CLI wants Kysely.
150
+ db: (base as { db?: unknown }).db,
151
+ storageFactory: (entity: any, name: string) => engineFor(name).storageFactory(entity, name),
219
152
  sourceOf,
220
- dbOf: (source) => engineOf(source)?.db,
153
+ dbOf: (source) => (engineOf(source) as { db?: unknown } | undefined)?.db,
221
154
  sources: () => [DEFAULT, ...engines.keys()],
222
- transacted: (source, fn) => {
155
+ // Asked of the source that will run the work, never of the default one: a frame whose
156
+ // members all live in a transactional source gets its transaction even when `db:` has
157
+ // none. A source that hands out none leaves the answer `false`, and a frame reads it:
158
+ // `boot/together.ts` compensates instead, and says which of the two it built.
159
+ transacts: (source) => engineOf(source)?.transacted !== undefined,
160
+ transacted: async (source, fn) => {
223
161
  const engine = engineOf(source);
224
162
  if (!engine) throw new Error(`No source named '${source}' — declared sources are ${[DEFAULT, ...engines.keys()].join(', ')}.`);
163
+ if (!engine.transacted) throw new Error(`Source '${source}' hands out no transaction.`);
225
164
  return engine.transacted(fn);
226
165
  },
227
- raw: (base as { sqlite?: { exec(sql: string): void } }).sqlite,
228
- dialect: base.dialect,
229
- // Additive migration: creates missing tables AND adds columns an entity gained.
230
- // One pass per source, each seeing only its own tables — which is what makes a
231
- // cross-source `ref()` a miss rather than a constraint against a stranger.
166
+ // One pass per source, each seeing only its own entities and the NAMES of the others —
167
+ // which is what makes a cross-source `ref()` a miss rather than a constraint against a
168
+ // stranger. What a pass DOES is the source's own: it knows its engine, this does not.
232
169
  migrate: async (app) => {
233
- await migrate(partition(app, (name) => !home.has(lowerFirst(name)), true) as never, base.db);
170
+ await base.migrate?.(viewOf(app, (name) => !home.has(lowerFirst(name)), true));
234
171
  for (const [name, engine] of engines) {
235
- await migrate(partition(app, (e) => home.get(lowerFirst(e)) === name, false) as never, engine.db);
172
+ await engine.migrate?.(viewOf(app, (e) => home.get(lowerFirst(e)) === name, false));
236
173
  }
237
174
  },
238
- // Every engine, the default one last: a named source may hold what the default
239
- // refers to, and closing in reverse of opening is the rule everywhere else.
175
+ // Every source, the default one last: a named source may hold what the default refers
176
+ // to, and closing in reverse of opening is the rule everywhere else.
240
177
  close: async () => {
241
- for (const engine of [...engines.values()].reverse()) await engine.db.destroy();
242
- await base.db.destroy();
178
+ for (const engine of [...engines.values()].reverse()) await engine.close?.();
179
+ await base.close?.();
243
180
  },
244
181
  };
245
182
  }