@fougere/defaults 0.6.0-alpha.0 → 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 +1 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/storage.d.ts +9 -56
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +12 -58
- package/dist/storage.js.map +1 -1
- package/package.json +6 -6
- package/src/index.ts +1 -10
- package/src/storage.ts +19 -87
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
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":"
|
|
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,13 +1,4 @@
|
|
|
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 { type Source } from '@fougere/core';
|
|
13
4
|
import '@fougere/adapter-sql/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
|
-
*
|
|
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,34 +31,21 @@ 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;
|
|
48
36
|
/** Run `fn` inside one transaction of that source, with a storage factory bound to it. */
|
|
49
37
|
transacted?: <R>(source: string, fn: (storageFactory: (entity: any, name: string) => any) => Promise<R>) => Promise<R>;
|
|
50
38
|
/**
|
|
51
39
|
* Brings the schema up to date once the app is scanned — the storage's `up`, handed to
|
|
52
|
-
* `migrating()`.
|
|
53
|
-
* post-boot and was read in four places under the two senses.
|
|
40
|
+
* `migrating()`.
|
|
54
41
|
*/
|
|
55
42
|
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
|
-
*/
|
|
43
|
+
/** Close every engine this opened — the dual of opening them, declared by whoever did. */
|
|
64
44
|
close?: () => Promise<void>;
|
|
65
45
|
}
|
|
66
46
|
/** Does this config ask for persistence at all? */
|
|
67
47
|
export declare function declaresStorage(dbConf: DbConfig): boolean;
|
|
68
|
-
/**
|
|
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.
|
|
71
|
-
*
|
|
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.
|
|
75
|
-
*/
|
|
48
|
+
/** Resolve the data layer. */
|
|
76
49
|
export declare function resolveStorage(dbConf: DbConfig, sources?: SourcesConfig): ResolvedStorage;
|
|
77
50
|
/** One source: what realizes it, and the entities that live there. */
|
|
78
51
|
export interface Placement {
|
|
@@ -85,26 +58,6 @@ export interface DeclaredStorage {
|
|
|
85
58
|
/** The other places. Absent means one source, the way it always was. */
|
|
86
59
|
sources?: Record<string, Placement>;
|
|
87
60
|
}
|
|
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
|
-
*/
|
|
61
|
+
/** The same routing `resolveStorage` performs, over engines the CALLER built. */
|
|
109
62
|
export declare function storageFrom(declared: DeclaredStorage): ResolvedStorage;
|
|
110
63
|
//# sourceMappingURL=storage.d.ts.map
|
package/dist/storage.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA
|
|
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
|
@@ -9,18 +9,7 @@ export function declaresStorage(dbConf) {
|
|
|
9
9
|
return false;
|
|
10
10
|
return true;
|
|
11
11
|
}
|
|
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
|
-
*/
|
|
12
|
+
/** The app as ONE source sees it. */
|
|
24
13
|
function viewOf(app, holds, withAuth) {
|
|
25
14
|
const typed = app;
|
|
26
15
|
const fronds = typed.fronds
|
|
@@ -34,14 +23,7 @@ function viewOf(app, holds, withAuth) {
|
|
|
34
23
|
elsewhere: Fronds.hosting(typed.fronds).entityNames().filter((name) => !holds(name)),
|
|
35
24
|
};
|
|
36
25
|
}
|
|
37
|
-
/**
|
|
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.
|
|
40
|
-
*
|
|
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.
|
|
44
|
-
*/
|
|
26
|
+
/** Resolve the data layer. */
|
|
45
27
|
export function resolveStorage(dbConf, sources) {
|
|
46
28
|
if (!declaresStorage(dbConf))
|
|
47
29
|
return { storageFactory: undefined };
|
|
@@ -54,45 +36,14 @@ export function resolveStorage(dbConf, sources) {
|
|
|
54
36
|
sources: named,
|
|
55
37
|
});
|
|
56
38
|
}
|
|
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
|
-
*/
|
|
39
|
+
/** One config entry, resolved to the adapter it names. */
|
|
65
40
|
function built(conf, field) {
|
|
66
41
|
const name = conf.source ?? DEFAULT_ADAPTER;
|
|
67
|
-
|
|
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);
|
|
42
|
+
return Sources.open(name, conf, `${field}.source`);
|
|
72
43
|
}
|
|
73
44
|
/** What a config naming no adapter means. */
|
|
74
45
|
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
|
-
*/
|
|
46
|
+
/** The same routing `resolveStorage` performs, over engines the CALLER built. */
|
|
96
47
|
export function storageFrom(declared) {
|
|
97
48
|
const { db: base, sources } = declared;
|
|
98
49
|
// Where each named entity lives. An entity claimed by two sources is refused naming
|
|
@@ -127,16 +78,19 @@ export function storageFrom(declared) {
|
|
|
127
78
|
sourceOf,
|
|
128
79
|
dbOf: (source) => engineOf(source)?.db,
|
|
129
80
|
sources: () => [DEFAULT, ...engines.keys()],
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
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) => {
|
|
133
87
|
const engine = engineOf(source);
|
|
134
88
|
if (!engine)
|
|
135
89
|
throw new Error(`No source named '${source}' — declared sources are ${[DEFAULT, ...engines.keys()].join(', ')}.`);
|
|
136
90
|
if (!engine.transacted)
|
|
137
91
|
throw new Error(`Source '${source}' hands out no transaction.`);
|
|
138
92
|
return engine.transacted(fn);
|
|
139
|
-
}
|
|
93
|
+
},
|
|
140
94
|
// One pass per source, each seeing only its own entities and the NAMES of the others —
|
|
141
95
|
// which is what makes a cross-source `ref()` a miss rather than a constraint against a
|
|
142
96
|
// stranger. What a pass DOES is the source's own: it knows its engine, this does not.
|
package/dist/storage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"
|
|
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.
|
|
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.
|
|
33
|
-
"@fougere/
|
|
34
|
-
"@fougere/
|
|
35
|
-
"@fougere/transport-http": "0.
|
|
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.
|
|
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,13 +1,4 @@
|
|
|
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';
|
|
@@ -30,13 +21,8 @@ export interface ResolvedStorage {
|
|
|
30
21
|
db?: unknown;
|
|
31
22
|
storageFactory: ((entity: any, name: string) => any) | undefined;
|
|
32
23
|
/**
|
|
33
|
-
* The source an entity's rows live in — what decides whether a frame gets a real
|
|
34
|
-
*
|
|
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.
|
|
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.
|
|
40
26
|
*/
|
|
41
27
|
sourceOf?: (entityName: string) => string;
|
|
42
28
|
/**
|
|
@@ -46,22 +32,16 @@ export interface ResolvedStorage {
|
|
|
46
32
|
dbOf?: (source: string) => unknown;
|
|
47
33
|
/** Every source that has an engine, the default one first. */
|
|
48
34
|
sources?: () => string[];
|
|
35
|
+
/** Whether that source hands out a transaction — the dual of running one in it. */
|
|
36
|
+
transacts?: (source: string) => boolean;
|
|
49
37
|
/** Run `fn` inside one transaction of that source, with a storage factory bound to it. */
|
|
50
38
|
transacted?: <R>(source: string, fn: (storageFactory: (entity: any, name: string) => any) => Promise<R>) => Promise<R>;
|
|
51
39
|
/**
|
|
52
40
|
* Brings the schema up to date once the app is scanned — the storage's `up`, handed to
|
|
53
|
-
* `migrating()`.
|
|
54
|
-
* post-boot and was read in four places under the two senses.
|
|
41
|
+
* `migrating()`.
|
|
55
42
|
*/
|
|
56
43
|
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
|
-
*/
|
|
44
|
+
/** Close every engine this opened — the dual of opening them, declared by whoever did. */
|
|
65
45
|
close?: () => Promise<void>;
|
|
66
46
|
/** Raw synchronous handle, when the engine exposes one. */
|
|
67
47
|
}
|
|
@@ -73,18 +53,7 @@ export function declaresStorage(dbConf: DbConfig): boolean {
|
|
|
73
53
|
}
|
|
74
54
|
|
|
75
55
|
|
|
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
|
-
*/
|
|
56
|
+
/** The app as ONE source sees it. */
|
|
88
57
|
function viewOf(
|
|
89
58
|
app: App,
|
|
90
59
|
holds: (name: string) => boolean,
|
|
@@ -106,14 +75,7 @@ function viewOf(
|
|
|
106
75
|
};
|
|
107
76
|
}
|
|
108
77
|
|
|
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
|
-
*/
|
|
78
|
+
/** Resolve the data layer. */
|
|
117
79
|
export function resolveStorage(dbConf: DbConfig, sources?: SourcesConfig): ResolvedStorage {
|
|
118
80
|
if (!declaresStorage(dbConf)) return { storageFactory: undefined };
|
|
119
81
|
|
|
@@ -127,24 +89,11 @@ export function resolveStorage(dbConf: DbConfig, sources?: SourcesConfig): Resol
|
|
|
127
89
|
});
|
|
128
90
|
}
|
|
129
91
|
|
|
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
|
-
*/
|
|
92
|
+
/** One config entry, resolved to the adapter it names. */
|
|
138
93
|
function built(conf: Record<string, unknown>, field: string): Source {
|
|
139
94
|
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
95
|
|
|
147
|
-
return Sources.
|
|
96
|
+
return Sources.open(name, conf as never, `${field}.source`);
|
|
148
97
|
}
|
|
149
98
|
|
|
150
99
|
/** What a config naming no adapter means. */
|
|
@@ -163,27 +112,7 @@ export interface DeclaredStorage {
|
|
|
163
112
|
sources?: Record<string, Placement>;
|
|
164
113
|
}
|
|
165
114
|
|
|
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
|
-
*/
|
|
115
|
+
/** The same routing `resolveStorage` performs, over engines the CALLER built. */
|
|
187
116
|
export function storageFrom(declared: DeclaredStorage): ResolvedStorage {
|
|
188
117
|
const { db: base, sources } = declared;
|
|
189
118
|
|
|
@@ -223,14 +152,17 @@ export function storageFrom(declared: DeclaredStorage): ResolvedStorage {
|
|
|
223
152
|
sourceOf,
|
|
224
153
|
dbOf: (source) => (engineOf(source) as { db?: unknown } | undefined)?.db,
|
|
225
154
|
sources: () => [DEFAULT, ...engines.keys()],
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
|
|
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) => {
|
|
229
161
|
const engine = engineOf(source);
|
|
230
162
|
if (!engine) throw new Error(`No source named '${source}' — declared sources are ${[DEFAULT, ...engines.keys()].join(', ')}.`);
|
|
231
163
|
if (!engine.transacted) throw new Error(`Source '${source}' hands out no transaction.`);
|
|
232
164
|
return engine.transacted(fn);
|
|
233
|
-
}
|
|
165
|
+
},
|
|
234
166
|
// One pass per source, each seeing only its own entities and the NAMES of the others —
|
|
235
167
|
// which is what makes a cross-source `ref()` a miss rather than a constraint against a
|
|
236
168
|
// stranger. What a pass DOES is the source's own: it knows its engine, this does not.
|