@fougere/defaults 0.5.0-alpha.1 → 0.6.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/storage.d.ts CHANGED
@@ -9,14 +9,17 @@
9
9
  * why an engine change looked like it touched seven files.
10
10
  */
11
11
  import type { App } from '@fougere/core';
12
- import { type DialectName, type Setup } from '@fougere/adapter-sql';
12
+ import { type Source } from '@fougere/core';
13
+ import '@fougere/adapter-sql/sqlite';
13
14
  /** The `db` field of fougere.config.ts, read structurally. */
14
15
  export type DbConfig = false | 'sqlite' | {
16
+ source?: string;
15
17
  dialect?: string;
16
18
  path?: string;
17
19
  } | undefined;
18
20
  /** A named source and the entities it holds — the `sources` field, read structurally. */
19
21
  export type SourcesConfig = Record<string, {
22
+ source?: string;
20
23
  dialect?: string;
21
24
  path?: string;
22
25
  entities: string[];
@@ -24,7 +27,7 @@ export type SourcesConfig = Record<string, {
24
27
  export interface ResolvedStorage {
25
28
  /** Opaque handle handed to auth providers. */
26
29
  db?: unknown;
27
- ormFactory: ((entity: any, name: string) => any) | undefined;
30
+ storageFactory: ((entity: any, name: string) => any) | undefined;
28
31
  /**
29
32
  * The source an entity's rows live in — what decides whether a frame gets a real
30
33
  * transaction or an unwind it replays itself.
@@ -42,8 +45,8 @@ export interface ResolvedStorage {
42
45
  dbOf?: (source: string) => unknown;
43
46
  /** Every source that has an engine, the default one first. */
44
47
  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>;
48
+ /** Run `fn` inside one transaction of that source, with a storage factory bound to it. */
49
+ transacted?: <R>(source: string, fn: (storageFactory: (entity: any, name: string) => any) => Promise<R>) => Promise<R>;
47
50
  /**
48
51
  * Brings the schema up to date once the app is scanned — the storage's `up`, handed to
49
52
  * `migrating()`. It was called `afterBoot`, a word that also meant the host's own
@@ -59,11 +62,6 @@ export interface ResolvedStorage {
59
62
  * ordinary rather than rare.
60
63
  */
61
64
  close?: () => Promise<void>;
62
- /** Raw synchronous handle, when the engine exposes one. */
63
- raw?: {
64
- exec(sql: string): void;
65
- };
66
- dialect?: DialectName;
67
65
  }
68
66
  /** Does this config ask for persistence at all? */
69
67
  export declare function declaresStorage(dbConf: DbConfig): boolean;
@@ -76,14 +74,14 @@ export declare function declaresStorage(dbConf: DbConfig): boolean;
76
74
  * exactly as before.
77
75
  */
78
76
  export declare function resolveStorage(dbConf: DbConfig, sources?: SourcesConfig): ResolvedStorage;
79
- /** One source: the engine that holds it, and the entities that live there. */
77
+ /** One source: what realizes it, and the entities that live there. */
80
78
  export interface Placement {
81
- setup: Setup;
79
+ source: Source;
82
80
  entities: string[];
83
81
  }
84
82
  export interface DeclaredStorage {
85
83
  /** The default source — where an entity no placement names lands. */
86
- db: Setup;
84
+ db: Source;
87
85
  /** The other places. Absent means one source, the way it always was. */
88
86
  sources?: Record<string, Placement>;
89
87
  }
@@ -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;;;;;;;;;GASG;AACH,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;;;;;;;;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,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;;;;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;CAE7B;AAED,mDAAmD;AACnD,wBAAgB,eAAe,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,CAGzD;AAoCD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,eAAe,CAWzF;AAyBD,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;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,eAAe,GAAG,eAAe,CA+DtE"}
package/dist/storage.js CHANGED
@@ -1,29 +1,14 @@
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
12
  /**
28
13
  * The app as ONE source sees it: the entities that live there, and the names of those
29
14
  * that do not.
@@ -36,7 +21,7 @@ function refuseUnresolvable(declared, field) {
36
21
  * Auth entities ride with the default source: a provider's tables are the app's own,
37
22
  * and nothing yet lets one declare where it lives.
38
23
  */
39
- function partition(app, holds, withAuth) {
24
+ function viewOf(app, holds, withAuth) {
40
25
  const typed = app;
41
26
  const fronds = typed.fronds
42
27
  .map((frond) => ({ ...frond, entities: frond.entities.filter((entry) => holds(entry.name)) }))
@@ -46,7 +31,7 @@ function partition(app, holds, withAuth) {
46
31
  auth: withAuth ? typed.auth : undefined,
47
32
  // Lifted, because this function reads its app structurally on purpose — a caller
48
33
  // 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)),
34
+ elsewhere: Fronds.hosting(typed.fronds).entityNames().filter((name) => !holds(name)),
50
35
  };
51
36
  }
52
37
  /**
@@ -59,18 +44,34 @@ function partition(app, holds, withAuth) {
59
44
  */
60
45
  export function resolveStorage(dbConf, sources) {
61
46
  if (!declaresStorage(dbConf))
62
- return { ormFactory: undefined };
63
- refuseUnresolvable(typeof dbConf === 'object' ? dbConf.dialect : (dbConf || undefined), 'db.dialect');
47
+ return { storageFactory: undefined };
64
48
  const named = {};
65
49
  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 };
50
+ named[name] = { source: built(conf, `sources.${name}`), entities: conf.entities };
68
51
  }
69
52
  return storageFrom({
70
- db: setupSqlite({ path: typeof dbConf === 'object' ? dbConf.path : undefined }),
53
+ db: built(typeof dbConf === 'object' ? dbConf : {}, 'db'),
71
54
  sources: named,
72
55
  });
73
56
  }
57
+ /**
58
+ * One config entry, resolved to the adapter it names.
59
+ *
60
+ * `source:` defaults to `'sql'` — the convention a first run meets, and the reason a config
61
+ * saying only `db: 'sqlite'` keeps working. What the entry says BELOW that key belongs to the
62
+ * adapter it named, which is also the only one that can refuse it: this function knows no
63
+ * dialect and no driver.
64
+ */
65
+ function built(conf, field) {
66
+ const name = conf.source ?? DEFAULT_ADAPTER;
67
+ if (!Sources.answers(name)) {
68
+ throw new Error(`${field}.source '${name}' is not answered — import the adapter that does. `
69
+ + `This process answers ${Sources.answered().join(', ') || 'nothing yet'}.`);
70
+ }
71
+ return Sources.resolve(name, conf);
72
+ }
73
+ /** What a config naming no adapter means. */
74
+ const DEFAULT_ADAPTER = 'sql';
74
75
  /**
75
76
  * The same routing `resolveStorage` performs, over engines the CALLER built.
76
77
  *
@@ -100,7 +101,7 @@ export function storageFrom(declared) {
100
101
  const home = new Map();
101
102
  const engines = new Map();
102
103
  for (const [name, placement] of Object.entries(sources ?? {})) {
103
- engines.set(name, placement.setup);
104
+ engines.set(name, placement.source);
104
105
  for (const entity of placement.entities) {
105
106
  const key = lowerFirst(entity);
106
107
  const claimed = home.get(key);
@@ -120,34 +121,37 @@ export function storageFrom(declared) {
120
121
  return (source && engines.get(source)) || base;
121
122
  };
122
123
  return {
124
+ // Opaque, and narrowed by whoever needs it — auth wants a handle, the CLI wants Kysely.
123
125
  db: base.db,
124
- ormFactory: (entity, name) => engineFor(name).ormFactory(entity, name),
126
+ storageFactory: (entity, name) => engineFor(name).storageFactory(entity, name),
125
127
  sourceOf,
126
128
  dbOf: (source) => engineOf(source)?.db,
127
129
  sources: () => [DEFAULT, ...engines.keys()],
128
- transacted: (source, fn) => {
130
+ // A source that hands out no transaction leaves the key absent, and a frame reads the
131
+ // absence: `boot/together.ts` compensates instead, and says which of the two it built.
132
+ transacted: base.transacted ? async (source, fn) => {
129
133
  const engine = engineOf(source);
130
134
  if (!engine)
131
135
  throw new Error(`No source named '${source}' — declared sources are ${[DEFAULT, ...engines.keys()].join(', ')}.`);
136
+ if (!engine.transacted)
137
+ throw new Error(`Source '${source}' hands out no transaction.`);
132
138
  return engine.transacted(fn);
133
- },
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.
139
+ } : undefined,
140
+ // One pass per source, each seeing only its own entities and the NAMES of the others —
141
+ // which is what makes a cross-source `ref()` a miss rather than a constraint against a
142
+ // stranger. What a pass DOES is the source's own: it knows its engine, this does not.
139
143
  migrate: async (app) => {
140
- await migrate(partition(app, (name) => !home.has(lowerFirst(name)), true), base.db);
144
+ await base.migrate?.(viewOf(app, (name) => !home.has(lowerFirst(name)), true));
141
145
  for (const [name, engine] of engines) {
142
- await migrate(partition(app, (e) => home.get(lowerFirst(e)) === name, false), engine.db);
146
+ await engine.migrate?.(viewOf(app, (e) => home.get(lowerFirst(e)) === name, false));
143
147
  }
144
148
  },
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.
149
+ // Every source, the default one last: a named source may hold what the default refers
150
+ // to, and closing in reverse of opening is the rule everywhere else.
147
151
  close: async () => {
148
152
  for (const engine of [...engines.values()].reverse())
149
- await engine.db.destroy();
150
- await base.db.destroy();
153
+ await engine.close?.();
154
+ await base.close?.();
151
155
  },
152
156
  };
153
157
  }
@@ -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.6.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.6.0-alpha.0",
33
+ "@fougere/container": "0.6.0-alpha.0",
34
+ "@fougere/adapter-sql": "0.6.0-alpha.0",
35
+ "@fougere/transport-http": "0.6.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.6.0-alpha.0"
45
45
  },
46
46
  "scripts": {
47
47
  "build": "rm -rf dist && tsc",
package/src/storage.ts CHANGED
@@ -11,23 +11,24 @@
11
11
  import type { App } from '@fougere/core';
12
12
  import { Fronds, type FrondDescriptor } from '@fougere/core';
13
13
  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';
14
+ import { Sources, type Source, type SourceView } from '@fougere/core';
15
+ // Imported for its side effect: it is what makes `source: 'sql'` an answered name.
16
+ import '@fougere/adapter-sql/sqlite';
16
17
 
17
18
  /** The `db` field of fougere.config.ts, read structurally. */
18
19
  export type DbConfig =
19
20
  | false
20
21
  | 'sqlite'
21
- | { dialect?: string; path?: string }
22
+ | { source?: string; dialect?: string; path?: string }
22
23
  | undefined;
23
24
 
24
25
  /** 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;
26
+ export type SourcesConfig = Record<string, { source?: string; dialect?: string; path?: string; entities: string[] }> | undefined;
26
27
 
27
28
  export interface ResolvedStorage {
28
29
  /** Opaque handle handed to auth providers. */
29
30
  db?: unknown;
30
- ormFactory: ((entity: any, name: string) => any) | undefined;
31
+ storageFactory: ((entity: any, name: string) => any) | undefined;
31
32
  /**
32
33
  * The source an entity's rows live in — what decides whether a frame gets a real
33
34
  * transaction or an unwind it replays itself.
@@ -45,8 +46,8 @@ export interface ResolvedStorage {
45
46
  dbOf?: (source: string) => unknown;
46
47
  /** Every source that has an engine, the default one first. */
47
48
  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>;
49
+ /** Run `fn` inside one transaction of that source, with a storage factory bound to it. */
50
+ transacted?: <R>(source: string, fn: (storageFactory: (entity: any, name: string) => any) => Promise<R>) => Promise<R>;
50
51
  /**
51
52
  * Brings the schema up to date once the app is scanned — the storage's `up`, handed to
52
53
  * `migrating()`. It was called `afterBoot`, a word that also meant the host's own
@@ -63,8 +64,6 @@ export interface ResolvedStorage {
63
64
  */
64
65
  close?: () => Promise<void>;
65
66
  /** Raw synchronous handle, when the engine exposes one. */
66
- raw?: { exec(sql: string): void };
67
- dialect?: DialectName;
68
67
  }
69
68
 
70
69
  /** Does this config ask for persistence at all? */
@@ -73,23 +72,6 @@ export function declaresStorage(dbConf: DbConfig): boolean {
73
72
  return true;
74
73
  }
75
74
 
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
75
 
94
76
  /**
95
77
  * The app as ONE source sees it: the entities that live there, and the names of those
@@ -103,11 +85,11 @@ function refuseUnresolvable(declared: string | undefined, field: string): void {
103
85
  * Auth entities ride with the default source: a provider's tables are the app's own,
104
86
  * and nothing yet lets one declare where it lives.
105
87
  */
106
- function partition(
88
+ function viewOf(
107
89
  app: App,
108
90
  holds: (name: string) => boolean,
109
91
  withAuth: boolean,
110
- ): unknown {
92
+ ): SourceView {
111
93
  const typed = app as unknown as {
112
94
  fronds: { name: string; entities: { name: string }[] }[];
113
95
  auth?: unknown;
@@ -120,7 +102,7 @@ function partition(
120
102
  auth: withAuth ? typed.auth : undefined,
121
103
  // Lifted, because this function reads its app structurally on purpose — a caller
122
104
  // 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)),
105
+ elsewhere: Fronds.hosting(typed.fronds as FrondDescriptor[]).entityNames().filter((name) => !holds(name)),
124
106
  };
125
107
  }
126
108
 
@@ -133,29 +115,50 @@ function partition(
133
115
  * exactly as before.
134
116
  */
135
117
  export function resolveStorage(dbConf: DbConfig, sources?: SourcesConfig): ResolvedStorage {
136
- if (!declaresStorage(dbConf)) return { ormFactory: undefined };
118
+ if (!declaresStorage(dbConf)) return { storageFactory: undefined };
137
119
 
138
- refuseUnresolvable(typeof dbConf === 'object' ? dbConf.dialect : (dbConf || undefined), 'db.dialect');
139
120
  const named: Record<string, Placement> = {};
140
121
  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 };
122
+ named[name] = { source: built(conf, `sources.${name}`), entities: conf.entities };
143
123
  }
144
124
  return storageFrom({
145
- db: setupSqlite({ path: typeof dbConf === 'object' ? dbConf.path : undefined }),
125
+ db: built(typeof dbConf === 'object' ? dbConf : {}, 'db'),
146
126
  sources: named,
147
127
  });
148
128
  }
149
129
 
150
- /** One source: the engine that holds it, and the entities that live there. */
130
+ /**
131
+ * One config entry, resolved to the adapter it names.
132
+ *
133
+ * `source:` defaults to `'sql'` — the convention a first run meets, and the reason a config
134
+ * saying only `db: 'sqlite'` keeps working. What the entry says BELOW that key belongs to the
135
+ * adapter it named, which is also the only one that can refuse it: this function knows no
136
+ * dialect and no driver.
137
+ */
138
+ function built(conf: Record<string, unknown>, field: string): Source {
139
+ const name = (conf.source as string | undefined) ?? DEFAULT_ADAPTER;
140
+ if (!Sources.answers(name)) {
141
+ throw new Error(
142
+ `${field}.source '${name}' is not answered — import the adapter that does. `
143
+ + `This process answers ${Sources.answered().join(', ') || 'nothing yet'}.`,
144
+ );
145
+ }
146
+
147
+ return Sources.resolve(name, conf as never);
148
+ }
149
+
150
+ /** What a config naming no adapter means. */
151
+ const DEFAULT_ADAPTER = 'sql';
152
+
153
+ /** One source: what realizes it, and the entities that live there. */
151
154
  export interface Placement {
152
- setup: Setup;
155
+ source: Source;
153
156
  entities: string[];
154
157
  }
155
158
 
156
159
  export interface DeclaredStorage {
157
160
  /** The default source — where an entity no placement names lands. */
158
- db: Setup;
161
+ db: Source;
159
162
  /** The other places. Absent means one source, the way it always was. */
160
163
  sources?: Record<string, Placement>;
161
164
  }
@@ -188,9 +191,9 @@ export function storageFrom(declared: DeclaredStorage): ResolvedStorage {
188
191
  // both: the rows would be read from one and written to the other, by whichever
189
192
  // registration ran last — the same silent duplicate `remotes:` refuses one level up.
190
193
  const home = new Map<string, string>();
191
- const engines = new Map<string, Setup>();
194
+ const engines = new Map<string, Source>();
192
195
  for (const [name, placement] of Object.entries(sources ?? {})) {
193
- engines.set(name, placement.setup);
196
+ engines.set(name, placement.source);
194
197
  for (const entity of placement.entities) {
195
198
  const key = lowerFirst(entity);
196
199
  const claimed = home.get(key);
@@ -214,32 +217,34 @@ export function storageFrom(declared: DeclaredStorage): ResolvedStorage {
214
217
  };
215
218
 
216
219
  return {
217
- db: base.db,
218
- ormFactory: (entity: any, name: string) => engineFor(name).ormFactory(entity, name),
220
+ // Opaque, and narrowed by whoever needs it — auth wants a handle, the CLI wants Kysely.
221
+ db: (base as { db?: unknown }).db,
222
+ storageFactory: (entity: any, name: string) => engineFor(name).storageFactory(entity, name),
219
223
  sourceOf,
220
- dbOf: (source) => engineOf(source)?.db,
224
+ dbOf: (source) => (engineOf(source) as { db?: unknown } | undefined)?.db,
221
225
  sources: () => [DEFAULT, ...engines.keys()],
222
- transacted: (source, fn) => {
226
+ // A source that hands out no transaction leaves the key absent, and a frame reads the
227
+ // absence: `boot/together.ts` compensates instead, and says which of the two it built.
228
+ transacted: base.transacted ? async (source, fn) => {
223
229
  const engine = engineOf(source);
224
230
  if (!engine) throw new Error(`No source named '${source}' — declared sources are ${[DEFAULT, ...engines.keys()].join(', ')}.`);
231
+ if (!engine.transacted) throw new Error(`Source '${source}' hands out no transaction.`);
225
232
  return engine.transacted(fn);
226
- },
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.
233
+ } : undefined,
234
+ // One pass per source, each seeing only its own entities and the NAMES of the others —
235
+ // which is what makes a cross-source `ref()` a miss rather than a constraint against a
236
+ // stranger. What a pass DOES is the source's own: it knows its engine, this does not.
232
237
  migrate: async (app) => {
233
- await migrate(partition(app, (name) => !home.has(lowerFirst(name)), true) as never, base.db);
238
+ await base.migrate?.(viewOf(app, (name) => !home.has(lowerFirst(name)), true));
234
239
  for (const [name, engine] of engines) {
235
- await migrate(partition(app, (e) => home.get(lowerFirst(e)) === name, false) as never, engine.db);
240
+ await engine.migrate?.(viewOf(app, (e) => home.get(lowerFirst(e)) === name, false));
236
241
  }
237
242
  },
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.
243
+ // Every source, the default one last: a named source may hold what the default refers
244
+ // to, and closing in reverse of opening is the rule everywhere else.
240
245
  close: async () => {
241
- for (const engine of [...engines.values()].reverse()) await engine.db.destroy();
242
- await base.db.destroy();
246
+ for (const engine of [...engines.values()].reverse()) await engine.close?.();
247
+ await base.close?.();
243
248
  },
244
249
  };
245
250
  }