@fougere/defaults 0.6.0-alpha.0 → 0.8.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 CHANGED
@@ -18,7 +18,7 @@ export async function bootAppFromConfig(root, opts = {}) {
18
18
  remotes: useRemotes ? remotes : undefined,
19
19
  remoteTransport: useRemotes ? (url) => createHttpTransport(url) : undefined,
20
20
  // One resolver, one place that knows a storage package.
21
- db: (cfg) => resolveStorage(cfg.db, cfg.sources),
21
+ db: (cfg) => resolveStorage(cfg.db, cfg.sources, root),
22
22
  extensions: opts.extensions,
23
23
  });
24
24
  }
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,EAAE,IAAI,CAAC;QACtG,UAAU,EAAE,IAAI,CAAC,UAAU;KAC5B,CAAC,CAAC;AACL,CAAC"}
package/dist/storage.d.ts CHANGED
@@ -1,15 +1,6 @@
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 Source } from '@fougere/core';
3
+ import { type Constraint, type Source } from '@fougere/core';
13
4
  import '@fougere/adapter-sql/sqlite';
14
5
  /** The `db` field of fougere.config.ts, read structurally. */
15
6
  export type DbConfig = false | 'sqlite' | {
@@ -29,13 +20,8 @@ export interface ResolvedStorage {
29
20
  db?: unknown;
30
21
  storageFactory: ((entity: any, name: string) => any) | undefined;
31
22
  /**
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.
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.
39
25
  */
40
26
  sourceOf?: (entityName: string) => string;
41
27
  /**
@@ -45,35 +31,34 @@ export interface ResolvedStorage {
45
31
  dbOf?: (source: string) => unknown;
46
32
  /** Every source that has an engine, the default one first. */
47
33
  sources?: () => string[];
34
+ /** Whether that source hands out a transaction — the dual of running one in it. */
35
+ transacts?: (source: string) => boolean;
36
+ /** Whether that source refuses a constraint at the rows — the dual of an entity declaring one. */
37
+ enforces?: (source: string, constraint: Constraint) => boolean;
48
38
  /** Run `fn` inside one transaction of that source, with a storage factory bound to it. */
49
39
  transacted?: <R>(source: string, fn: (storageFactory: (entity: any, name: string) => any) => Promise<R>) => Promise<R>;
50
40
  /**
51
41
  * 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.
42
+ * `migrating()`.
54
43
  */
55
44
  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
- */
45
+ /** Close every engine this opened — the dual of opening them, declared by whoever did. */
64
46
  close?: () => Promise<void>;
65
47
  }
66
48
  /** Does this config ask for persistence at all? */
67
49
  export declare function declaresStorage(dbConf: DbConfig): boolean;
50
+ /** Resolve the data layer. */
51
+ export declare function resolveStorage(dbConf: DbConfig, sources?: SourcesConfig,
68
52
  /**
69
- * Resolve the data layer. `db: false` (or absent) means a frond with no
70
- * persistence of its own — the caller decides what to fall back to.
53
+ * Where a relative `path:` is counted from the directory of the config that named it.
71
54
  *
72
- * `sources` names the places that are NOT the default one. An entity it does not
73
- * name stays in `db`, so an app with one database declares nothing and behaves
74
- * exactly as before.
55
+ * Absent, it is counted from the current directory, which is where an app in a
56
+ * workspace parts ways with the file that configured it: measured, `apps/nuxt` ran from
57
+ * its own directory and made a SECOND, empty database beside itself while the workspace
58
+ * held the real one. Nothing was said; the additive migration created every table, the
59
+ * seeds ran, and the app served an empty domain with a green boot.
75
60
  */
76
- export declare function resolveStorage(dbConf: DbConfig, sources?: SourcesConfig): ResolvedStorage;
61
+ root?: string): ResolvedStorage;
77
62
  /** One source: what realizes it, and the entities that live there. */
78
63
  export interface Placement {
79
64
  source: Source;
@@ -85,26 +70,6 @@ export interface DeclaredStorage {
85
70
  /** The other places. Absent means one source, the way it always was. */
86
71
  sources?: Record<string, Placement>;
87
72
  }
88
- /**
89
- * The same routing `resolveStorage` performs, over engines the CALLER built.
90
- *
91
- * `resolveStorage` reads a config file, and a config file cannot hold a live Kysely
92
- * dialect — so it can only ever resolve sqlite, the one driver this package depends
93
- * on. That was fine while `db:` was alone, because the escape hatch was to abandon
94
- * the convention entirely and hand `configureFougere` your own factory. With several
95
- * sources that stopped being an escape: a user wanting Postgres for ONE of them had
96
- * to re-implement the routing and the per-source migration to keep the other.
97
- *
98
- * So the two jobs are separated. Resolving a NAME into an engine is sqlite-only and
99
- * stays there; placing entities and migrating each source is engine-agnostic and
100
- * lives here, where any `Setup` is welcome:
101
- *
102
- * ```ts
103
- * configureFougere(storageFrom({
104
- * db: setupSqlite({ path: '.data/app.db' }),
105
- * sources: { legacy: { setup: setupKysely(pgDialect, 'postgres'), entities: ['Book'] } },
106
- * }));
107
- * ```
108
- */
73
+ /** The same routing `resolveStorage` performs, over engines the CALLER built. */
109
74
  export declare function storageFrom(declared: DeclaredStorage): ResolvedStorage;
110
75
  //# 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,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"}
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;AAMzC,OAAO,EAAW,KAAK,UAAU,EAAE,KAAK,MAAM,EAAmB,MAAM,eAAe,CAAC;AAEvF,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,kGAAkG;IAClG,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,KAAK,OAAO,CAAC;IAC/D,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,CAC5B,MAAM,EAAE,QAAQ,EAChB,OAAO,CAAC,EAAE,aAAa;AACvB;;;;;;;;GAQG;AACH,IAAI,CAAC,EAAE,MAAM,GACZ,eAAe,CAWjB;AAoCD,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,CAqEtE"}
package/dist/storage.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { Fronds } from '@fougere/core';
2
2
  import { lowerFirst } from '@fougere/core/contract';
3
+ import { existsSync } from 'node:fs';
4
+ import { isAbsolute, resolve } from 'node:path';
3
5
  import { Sources } from '@fougere/core';
4
6
  // Imported for its side effect: it is what makes `source: 'sql'` an answered name.
5
7
  import '@fougere/adapter-sql/sqlite';
@@ -9,18 +11,7 @@ export function declaresStorage(dbConf) {
9
11
  return false;
10
12
  return true;
11
13
  }
12
- /**
13
- * The app as ONE source sees it: the entities that live there, and the names of those
14
- * that do not.
15
- *
16
- * The second half is what lets the DDL stop lying. A batch holding every entity could
17
- * never tell a cross-source target from a typo, so it derived a table name and emitted
18
- * a foreign key against a table that may not exist. Cut per source, `elsewhere` says
19
- * which misses are legitimate — and a target in neither list is a mistake, out loud.
20
- *
21
- * Auth entities ride with the default source: a provider's tables are the app's own,
22
- * and nothing yet lets one declare where it lives.
23
- */
14
+ /** The app as ONE source sees it. */
24
15
  function viewOf(app, holds, withAuth) {
25
16
  const typed = app;
26
17
  const fronds = typed.fronds
@@ -34,65 +25,57 @@ function viewOf(app, holds, withAuth) {
34
25
  elsewhere: Fronds.hosting(typed.fronds).entityNames().filter((name) => !holds(name)),
35
26
  };
36
27
  }
28
+ /** Resolve the data layer. */
29
+ export function resolveStorage(dbConf, sources,
37
30
  /**
38
- * Resolve the data layer. `db: false` (or absent) means a frond with no
39
- * persistence of its own — the caller decides what to fall back to.
31
+ * Where a relative `path:` is counted from the directory of the config that named it.
40
32
  *
41
- * `sources` names the places that are NOT the default one. An entity it does not
42
- * name stays in `db`, so an app with one database declares nothing and behaves
43
- * exactly as before.
33
+ * Absent, it is counted from the current directory, which is where an app in a
34
+ * workspace parts ways with the file that configured it: measured, `apps/nuxt` ran from
35
+ * its own directory and made a SECOND, empty database beside itself while the workspace
36
+ * held the real one. Nothing was said; the additive migration created every table, the
37
+ * seeds ran, and the app served an empty domain with a green boot.
44
38
  */
45
- export function resolveStorage(dbConf, sources) {
39
+ root) {
46
40
  if (!declaresStorage(dbConf))
47
41
  return { storageFactory: undefined };
48
42
  const named = {};
49
43
  for (const [name, conf] of Object.entries(sources ?? {})) {
50
- named[name] = { source: built(conf, `sources.${name}`), entities: conf.entities };
44
+ named[name] = { source: built(conf, `sources.${name}`, root), entities: conf.entities };
51
45
  }
52
46
  return storageFrom({
53
- db: built(typeof dbConf === 'object' ? dbConf : {}, 'db'),
47
+ db: built(typeof dbConf === 'object' ? dbConf : {}, 'db', root),
54
48
  sources: named,
55
49
  });
56
50
  }
51
+ /** One config entry, resolved to the adapter it names. */
52
+ function built(conf, field, root) {
53
+ const name = conf.source ?? DEFAULT_ADAPTER;
54
+ return Sources.open(name, anchored(conf, root), `${field}.source`);
55
+ }
57
56
  /**
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.
57
+ * A relative path, counted from the config that named it. `:memory:` names no file and an
58
+ * absolute path already says where it is — both pass through untouched, which is what
59
+ * keeps this from being a second reader of what a path means.
64
60
  */
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'}.`);
61
+ function anchored(conf, root) {
62
+ const path = conf.path;
63
+ if (!root || typeof path !== 'string' || path === ':memory:' || isAbsolute(path))
64
+ return conf;
65
+ const anchoredPath = resolve(root, path);
66
+ // Anchoring MOVES a database that a deployment has been running against, and the driver
67
+ // would create the new one without a word: additive migration, seeds, an empty domain
68
+ // and a green boot. So the one case where the two disagree is named.
69
+ const beside = resolve(process.cwd(), path);
70
+ if (beside !== anchoredPath && existsSync(beside) && !existsSync(anchoredPath)) {
71
+ process.emitWarning(`${path} resolves to ${anchoredPath} beside the config that named it, and a database `
72
+ + `already sits at ${beside}. Fougere opens the first; move the file, or write the path you mean.`);
70
73
  }
71
- return Sources.resolve(name, conf);
74
+ return { ...conf, path: anchoredPath };
72
75
  }
73
76
  /** What a config naming no adapter means. */
74
77
  const DEFAULT_ADAPTER = 'sql';
75
- /**
76
- * The same routing `resolveStorage` performs, over engines the CALLER built.
77
- *
78
- * `resolveStorage` reads a config file, and a config file cannot hold a live Kysely
79
- * dialect — so it can only ever resolve sqlite, the one driver this package depends
80
- * on. That was fine while `db:` was alone, because the escape hatch was to abandon
81
- * the convention entirely and hand `configureFougere` your own factory. With several
82
- * sources that stopped being an escape: a user wanting Postgres for ONE of them had
83
- * to re-implement the routing and the per-source migration to keep the other.
84
- *
85
- * So the two jobs are separated. Resolving a NAME into an engine is sqlite-only and
86
- * stays there; placing entities and migrating each source is engine-agnostic and
87
- * lives here, where any `Setup` is welcome:
88
- *
89
- * ```ts
90
- * configureFougere(storageFrom({
91
- * db: setupSqlite({ path: '.data/app.db' }),
92
- * sources: { legacy: { setup: setupKysely(pgDialect, 'postgres'), entities: ['Book'] } },
93
- * }));
94
- * ```
95
- */
78
+ /** The same routing `resolveStorage` performs, over engines the CALLER built. */
96
79
  export function storageFrom(declared) {
97
80
  const { db: base, sources } = declared;
98
81
  // Where each named entity lives. An entity claimed by two sources is refused naming
@@ -127,16 +110,22 @@ export function storageFrom(declared) {
127
110
  sourceOf,
128
111
  dbOf: (source) => engineOf(source)?.db,
129
112
  sources: () => [DEFAULT, ...engines.keys()],
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) => {
113
+ // Asked of the source that will run the work, never of the default one: a frame whose
114
+ // members all live in a transactional source gets its transaction even when `db:` has
115
+ // none. A source that hands out none leaves the answer `false`, and a frame reads it:
116
+ // `boot/together.ts` compensates instead, and says which of the two it built.
117
+ transacts: (source) => engineOf(source)?.transacted !== undefined,
118
+ // Absent means the judge is alone with it: a source that keeps no constraint of its own
119
+ // says nothing, the way one that hands out no transaction says nothing.
120
+ enforces: (source, constraint) => engineOf(source)?.enforces?.includes(constraint) ?? false,
121
+ transacted: async (source, fn) => {
133
122
  const engine = engineOf(source);
134
123
  if (!engine)
135
124
  throw new Error(`No source named '${source}' — declared sources are ${[DEFAULT, ...engines.keys()].join(', ')}.`);
136
125
  if (!engine.transacted)
137
126
  throw new Error(`Source '${source}' hands out no transaction.`);
138
127
  return engine.transacted(fn);
139
- } : undefined,
128
+ },
140
129
  // One pass per source, each seeing only its own entities and the NAMES of the others —
141
130
  // which is what makes a cross-source `ref()` a miss rather than a constraint against a
142
131
  // stranger. What a pass DOES is the source's own: it knows its engine, this does not.
@@ -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.6.0-alpha.0",
3
+ "version": "0.8.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.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"
32
+ "@fougere/adapter-sql": "0.8.0-alpha.0",
33
+ "@fougere/core": "0.8.0-alpha.0",
34
+ "@fougere/transport-http": "0.8.0-alpha.0",
35
+ "@fougere/container": "0.8.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.6.0-alpha.0"
44
+ "@fougere/schema": "0.8.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';
@@ -48,7 +39,7 @@ export async function bootAppFromConfig(root: string, opts: BootAppOptions = {})
48
39
  remotes: useRemotes ? remotes : undefined,
49
40
  remoteTransport: useRemotes ? (url) => createHttpTransport(url) : undefined,
50
41
  // One resolver, one place that knows a storage package.
51
- db: (cfg) => resolveStorage(cfg.db as DbConfig, (cfg as { sources?: unknown }).sources as never),
42
+ db: (cfg) => resolveStorage(cfg.db as DbConfig, (cfg as { sources?: unknown }).sources as never, root),
52
43
  extensions: opts.extensions,
53
44
  });
54
45
  }
package/src/storage.ts CHANGED
@@ -1,17 +1,11 @@
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 { Sources, type Source, type SourceView } from '@fougere/core';
5
+ import { existsSync } from 'node:fs';
6
+ import { isAbsolute, resolve } from 'node:path';
7
+
8
+ import { Sources, type Constraint, type Source, type SourceView } from '@fougere/core';
15
9
  // Imported for its side effect: it is what makes `source: 'sql'` an answered name.
16
10
  import '@fougere/adapter-sql/sqlite';
17
11
 
@@ -30,13 +24,8 @@ export interface ResolvedStorage {
30
24
  db?: unknown;
31
25
  storageFactory: ((entity: any, name: string) => any) | undefined;
32
26
  /**
33
- * The source an entity's rows live in — what decides whether a frame gets a real
34
- * transaction or an unwind it replays itself.
35
- *
36
- * Absent when the caller built a `ResolvedStorage` by hand: there is then one factory
37
- * and no routing, so every entity IS on one engine — but nothing here can reach into it
38
- * for a transaction, and a frame falls back to compensating. Not knowing and promising
39
- * atomicity are two different things.
27
+ * The source an entity's rows live in — what decides whether a frame gets a real transaction or
28
+ * an unwind it replays itself.
40
29
  */
41
30
  sourceOf?: (entityName: string) => string;
42
31
  /**
@@ -46,22 +35,18 @@ export interface ResolvedStorage {
46
35
  dbOf?: (source: string) => unknown;
47
36
  /** Every source that has an engine, the default one first. */
48
37
  sources?: () => string[];
38
+ /** Whether that source hands out a transaction — the dual of running one in it. */
39
+ transacts?: (source: string) => boolean;
40
+ /** Whether that source refuses a constraint at the rows — the dual of an entity declaring one. */
41
+ enforces?: (source: string, constraint: Constraint) => boolean;
49
42
  /** Run `fn` inside one transaction of that source, with a storage factory bound to it. */
50
43
  transacted?: <R>(source: string, fn: (storageFactory: (entity: any, name: string) => any) => Promise<R>) => Promise<R>;
51
44
  /**
52
45
  * Brings the schema up to date once the app is scanned — the storage's `up`, handed to
53
- * `migrating()`. It was called `afterBoot`, a word that also meant the host's own
54
- * post-boot and was read in four places under the two senses.
46
+ * `migrating()`.
55
47
  */
56
48
  migrate?: (app: App) => Promise<void> | void;
57
- /**
58
- * Close every engine this opened — the dual of opening them, declared by whoever did.
59
- *
60
- * `boot()` calls the factory that lands here, so `boot()` is what owns closing it: a
61
- * container disposes what IT built, and this connection was handed in. Without this
62
- * the pool of a discarded app stayed open, which is what turning the ring makes
63
- * ordinary rather than rare.
64
- */
49
+ /** Close every engine this opened — the dual of opening them, declared by whoever did. */
65
50
  close?: () => Promise<void>;
66
51
  /** Raw synchronous handle, when the engine exposes one. */
67
52
  }
@@ -73,18 +58,7 @@ export function declaresStorage(dbConf: DbConfig): boolean {
73
58
  }
74
59
 
75
60
 
76
- /**
77
- * The app as ONE source sees it: the entities that live there, and the names of those
78
- * that do not.
79
- *
80
- * The second half is what lets the DDL stop lying. A batch holding every entity could
81
- * never tell a cross-source target from a typo, so it derived a table name and emitted
82
- * a foreign key against a table that may not exist. Cut per source, `elsewhere` says
83
- * which misses are legitimate — and a target in neither list is a mistake, out loud.
84
- *
85
- * Auth entities ride with the default source: a provider's tables are the app's own,
86
- * and nothing yet lets one declare where it lives.
87
- */
61
+ /** The app as ONE source sees it. */
88
62
  function viewOf(
89
63
  app: App,
90
64
  holds: (name: string) => boolean,
@@ -106,45 +80,62 @@ function viewOf(
106
80
  };
107
81
  }
108
82
 
109
- /**
110
- * Resolve the data layer. `db: false` (or absent) means a frond with no
111
- * persistence of its own — the caller decides what to fall back to.
112
- *
113
- * `sources` names the places that are NOT the default one. An entity it does not
114
- * name stays in `db`, so an app with one database declares nothing and behaves
115
- * exactly as before.
116
- */
117
- export function resolveStorage(dbConf: DbConfig, sources?: SourcesConfig): ResolvedStorage {
83
+ /** Resolve the data layer. */
84
+ export function resolveStorage(
85
+ dbConf: DbConfig,
86
+ sources?: SourcesConfig,
87
+ /**
88
+ * Where a relative `path:` is counted from the directory of the config that named it.
89
+ *
90
+ * Absent, it is counted from the current directory, which is where an app in a
91
+ * workspace parts ways with the file that configured it: measured, `apps/nuxt` ran from
92
+ * its own directory and made a SECOND, empty database beside itself while the workspace
93
+ * held the real one. Nothing was said; the additive migration created every table, the
94
+ * seeds ran, and the app served an empty domain with a green boot.
95
+ */
96
+ root?: string,
97
+ ): ResolvedStorage {
118
98
  if (!declaresStorage(dbConf)) return { storageFactory: undefined };
119
99
 
120
100
  const named: Record<string, Placement> = {};
121
101
  for (const [name, conf] of Object.entries(sources ?? {})) {
122
- named[name] = { source: built(conf, `sources.${name}`), entities: conf.entities };
102
+ named[name] = { source: built(conf, `sources.${name}`, root), entities: conf.entities };
123
103
  }
124
104
  return storageFrom({
125
- db: built(typeof dbConf === 'object' ? dbConf : {}, 'db'),
105
+ db: built(typeof dbConf === 'object' ? dbConf : {}, 'db', root),
126
106
  sources: named,
127
107
  });
128
108
  }
129
109
 
110
+ /** One config entry, resolved to the adapter it names. */
111
+ function built(conf: Record<string, unknown>, field: string, root?: string): Source {
112
+ const name = (conf.source as string | undefined) ?? DEFAULT_ADAPTER;
113
+
114
+ return Sources.open(name, anchored(conf, root) as never, `${field}.source`);
115
+ }
116
+
130
117
  /**
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.
118
+ * A relative path, counted from the config that named it. `:memory:` names no file and an
119
+ * absolute path already says where it is — both pass through untouched, which is what
120
+ * keeps this from being a second reader of what a path means.
137
121
  */
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'}.`,
122
+ function anchored(conf: Record<string, unknown>, root?: string): Record<string, unknown> {
123
+ const path = conf.path;
124
+ if (!root || typeof path !== 'string' || path === ':memory:' || isAbsolute(path)) return conf;
125
+
126
+ const anchoredPath = resolve(root, path);
127
+ // Anchoring MOVES a database that a deployment has been running against, and the driver
128
+ // would create the new one without a word: additive migration, seeds, an empty domain
129
+ // and a green boot. So the one case where the two disagree is named.
130
+ const beside = resolve(process.cwd(), path);
131
+ if (beside !== anchoredPath && existsSync(beside) && !existsSync(anchoredPath)) {
132
+ process.emitWarning(
133
+ `${path} resolves to ${anchoredPath} beside the config that named it, and a database `
134
+ + `already sits at ${beside}. Fougere opens the first; move the file, or write the path you mean.`,
144
135
  );
145
136
  }
146
137
 
147
- return Sources.resolve(name, conf as never);
138
+ return { ...conf, path: anchoredPath };
148
139
  }
149
140
 
150
141
  /** What a config naming no adapter means. */
@@ -163,27 +154,7 @@ export interface DeclaredStorage {
163
154
  sources?: Record<string, Placement>;
164
155
  }
165
156
 
166
- /**
167
- * The same routing `resolveStorage` performs, over engines the CALLER built.
168
- *
169
- * `resolveStorage` reads a config file, and a config file cannot hold a live Kysely
170
- * dialect — so it can only ever resolve sqlite, the one driver this package depends
171
- * on. That was fine while `db:` was alone, because the escape hatch was to abandon
172
- * the convention entirely and hand `configureFougere` your own factory. With several
173
- * sources that stopped being an escape: a user wanting Postgres for ONE of them had
174
- * to re-implement the routing and the per-source migration to keep the other.
175
- *
176
- * So the two jobs are separated. Resolving a NAME into an engine is sqlite-only and
177
- * stays there; placing entities and migrating each source is engine-agnostic and
178
- * lives here, where any `Setup` is welcome:
179
- *
180
- * ```ts
181
- * configureFougere(storageFrom({
182
- * db: setupSqlite({ path: '.data/app.db' }),
183
- * sources: { legacy: { setup: setupKysely(pgDialect, 'postgres'), entities: ['Book'] } },
184
- * }));
185
- * ```
186
- */
157
+ /** The same routing `resolveStorage` performs, over engines the CALLER built. */
187
158
  export function storageFrom(declared: DeclaredStorage): ResolvedStorage {
188
159
  const { db: base, sources } = declared;
189
160
 
@@ -223,14 +194,20 @@ export function storageFrom(declared: DeclaredStorage): ResolvedStorage {
223
194
  sourceOf,
224
195
  dbOf: (source) => (engineOf(source) as { db?: unknown } | undefined)?.db,
225
196
  sources: () => [DEFAULT, ...engines.keys()],
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) => {
197
+ // Asked of the source that will run the work, never of the default one: a frame whose
198
+ // members all live in a transactional source gets its transaction even when `db:` has
199
+ // none. A source that hands out none leaves the answer `false`, and a frame reads it:
200
+ // `boot/together.ts` compensates instead, and says which of the two it built.
201
+ transacts: (source) => engineOf(source)?.transacted !== undefined,
202
+ // Absent means the judge is alone with it: a source that keeps no constraint of its own
203
+ // says nothing, the way one that hands out no transaction says nothing.
204
+ enforces: (source, constraint) => engineOf(source)?.enforces?.includes(constraint) ?? false,
205
+ transacted: async (source, fn) => {
229
206
  const engine = engineOf(source);
230
207
  if (!engine) throw new Error(`No source named '${source}' — declared sources are ${[DEFAULT, ...engines.keys()].join(', ')}.`);
231
208
  if (!engine.transacted) throw new Error(`Source '${source}' hands out no transaction.`);
232
209
  return engine.transacted(fn);
233
- } : undefined,
210
+ },
234
211
  // One pass per source, each seeing only its own entities and the NAMES of the others —
235
212
  // which is what makes a cross-source `ref()` a miss rather than a constraint against a
236
213
  // stranger. What a pass DOES is the source's own: it knows its engine, this does not.