@fougere/defaults 0.2.0-alpha.2
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/LICENSE +21 -0
- package/README.md +15 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/storage.d.ts +80 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +140 -0
- package/dist/storage.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fougere contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @fougere/defaults
|
|
2
|
+
> The conventional boot
|
|
3
|
+
`bootAppFromConfig()` and `resolveStorage()` — **the single** place that names a storage
|
|
4
|
+
package. Changing engine is a change here, and nowhere else.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @fougere/defaults
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Part of [Fougere](https://github.com/chok/fougere) — one schema, a gradient from
|
|
14
|
+
monolith to distributed, the same user code.
|
|
15
|
+
Reference documentation: [the site](https://chok.github.io/fougere/) (en/fr).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
*/
|
|
11
|
+
import { type App } from '@fougere/core';
|
|
12
|
+
export { resolveStorage, declaresStorage, storageFrom } from './storage.js';
|
|
13
|
+
export type { DbConfig, SourcesConfig, DeclaredStorage, Placement, ResolvedStorage } from './storage.js';
|
|
14
|
+
export interface BootAppOptions {
|
|
15
|
+
/** Boot only these fronds (by name). Absent = every discovered frond. */
|
|
16
|
+
fronds?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* Follow `remotes:` from config (default true). A host process (`serve`)
|
|
19
|
+
* passes false — it *is* the frond, it doesn't route back out.
|
|
20
|
+
*/
|
|
21
|
+
topology?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Boot a Fougere app from the `fougere.config.ts` at `root`, wired the
|
|
25
|
+
* conventional way (container + sqlite + http remotes).
|
|
26
|
+
*/
|
|
27
|
+
export declare function bootAppFromConfig(root: string, opts?: BootAppOptions): Promise<App>;
|
|
28
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAoB,KAAK,GAAG,EAAE,MAAM,eAAe,CAAC;AAK3D,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;CACpB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,GAAG,CAAC,CAc7F"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
*/
|
|
11
|
+
import { boot, loadConfig } from '@fougere/core';
|
|
12
|
+
import { createContainer } from '@fougere/container';
|
|
13
|
+
import { createHttpTransport } from '@fougere/transport-http';
|
|
14
|
+
import { resolveStorage } from './storage.js';
|
|
15
|
+
export { resolveStorage, declaresStorage, storageFrom } from './storage.js';
|
|
16
|
+
/**
|
|
17
|
+
* Boot a Fougere app from the `fougere.config.ts` at `root`, wired the
|
|
18
|
+
* conventional way (container + sqlite + http remotes).
|
|
19
|
+
*/
|
|
20
|
+
export async function bootAppFromConfig(root, opts = {}) {
|
|
21
|
+
const config = await loadConfig(root);
|
|
22
|
+
const remotes = config.remotes ?? {};
|
|
23
|
+
const useRemotes = (opts.topology ?? true) && Object.keys(remotes).length > 0;
|
|
24
|
+
return boot({
|
|
25
|
+
root,
|
|
26
|
+
createContainer,
|
|
27
|
+
fronds: opts.fronds,
|
|
28
|
+
remotes: useRemotes ? remotes : undefined,
|
|
29
|
+
remoteTransport: useRemotes ? (url) => createHttpTransport(url) : undefined,
|
|
30
|
+
// One resolver, one place that knows a storage package.
|
|
31
|
+
db: (cfg) => resolveStorage(cfg.db, cfg.sources),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,IAAI,EAAE,UAAU,EAAY,MAAM,eAAe,CAAC;AAC3D,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;AAa5E;;;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;KACjG,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
*/
|
|
11
|
+
import type { App } from '@fougere/core';
|
|
12
|
+
import { type DialectName, type Setup } from '@fougere/adapter-sql';
|
|
13
|
+
/** The `db` field of fougere.config.ts, read structurally. */
|
|
14
|
+
export type DbConfig = false | 'sqlite' | {
|
|
15
|
+
dialect?: string;
|
|
16
|
+
path?: string;
|
|
17
|
+
} | undefined;
|
|
18
|
+
/** A named source and the entities it holds — the `sources` field, read structurally. */
|
|
19
|
+
export type SourcesConfig = Record<string, {
|
|
20
|
+
dialect?: string;
|
|
21
|
+
path?: string;
|
|
22
|
+
entities: string[];
|
|
23
|
+
}> | undefined;
|
|
24
|
+
export interface ResolvedStorage {
|
|
25
|
+
/** Opaque handle handed to auth providers. */
|
|
26
|
+
db?: unknown;
|
|
27
|
+
ormFactory: ((entity: any, name: string) => any) | undefined;
|
|
28
|
+
/** Brings the schema up to date once the app is scanned. */
|
|
29
|
+
afterBoot?: (app: App) => Promise<void> | void;
|
|
30
|
+
/** Raw synchronous handle, when the engine exposes one. */
|
|
31
|
+
raw?: {
|
|
32
|
+
exec(sql: string): void;
|
|
33
|
+
};
|
|
34
|
+
dialect?: DialectName;
|
|
35
|
+
}
|
|
36
|
+
/** Does this config ask for persistence at all? */
|
|
37
|
+
export declare function declaresStorage(dbConf: DbConfig): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the data layer. `db: false` (or absent) means a frond with no
|
|
40
|
+
* persistence of its own — the caller decides what to fall back to.
|
|
41
|
+
*
|
|
42
|
+
* `sources` names the places that are NOT the default one. An entity it does not
|
|
43
|
+
* name stays in `db`, so an app with one database declares nothing and behaves
|
|
44
|
+
* exactly as before.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveStorage(dbConf: DbConfig, sources?: SourcesConfig): ResolvedStorage;
|
|
47
|
+
/** One source: the engine that holds it, and the entities that live there. */
|
|
48
|
+
export interface Placement {
|
|
49
|
+
setup: Setup;
|
|
50
|
+
entities: string[];
|
|
51
|
+
}
|
|
52
|
+
export interface DeclaredStorage {
|
|
53
|
+
/** The default source — where an entity no placement names lands. */
|
|
54
|
+
db: Setup;
|
|
55
|
+
/** The other places. Absent means one source, the way it always was. */
|
|
56
|
+
sources?: Record<string, Placement>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The same routing `resolveStorage` performs, over engines the CALLER built.
|
|
60
|
+
*
|
|
61
|
+
* `resolveStorage` reads a config file, and a config file cannot hold a live Kysely
|
|
62
|
+
* dialect — so it can only ever resolve sqlite, the one driver this package depends
|
|
63
|
+
* on. That was fine while `db:` was alone, because the escape hatch was to abandon
|
|
64
|
+
* the convention entirely and hand `configureFougere` your own factory. With several
|
|
65
|
+
* sources that stopped being an escape: a user wanting Postgres for ONE of them had
|
|
66
|
+
* to re-implement the routing and the per-source migration to keep the other.
|
|
67
|
+
*
|
|
68
|
+
* So the two jobs are separated. Resolving a NAME into an engine is sqlite-only and
|
|
69
|
+
* stays there; placing entities and migrating each source is engine-agnostic and
|
|
70
|
+
* lives here, where any `Setup` is welcome:
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* configureFougere(storageFrom({
|
|
74
|
+
* db: setupSqlite({ path: '.data/app.db' }),
|
|
75
|
+
* sources: { legacy: { setup: setupKysely(pgDialect, 'postgres'), entities: ['Book'] } },
|
|
76
|
+
* }));
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function storageFrom(declared: DeclaredStorage): ResolvedStorage;
|
|
80
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +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;AACzC,OAAO,EAAwB,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAE1F,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,4DAA4D;IAC5D,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC/C,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;AA+DD;;;;;;;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,CA6CtE"}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { setupSqlite, migrate } from '@fougere/adapter-sql';
|
|
2
|
+
/** Does this config ask for persistence at all? */
|
|
3
|
+
export function declaresStorage(dbConf) {
|
|
4
|
+
if (dbConf === false || dbConf === undefined)
|
|
5
|
+
return false;
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Only sqlite is resolvable from a NAME — its driver is a dependency here. Any other
|
|
10
|
+
* engine needs a Kysely dialect INSTANCE, which only the host can build because only
|
|
11
|
+
* the host has its driver. So the name is refused, and the way in is named.
|
|
12
|
+
*
|
|
13
|
+
* `dialect` used to be declared and never read: `db: { dialect: 'postgres' }` started
|
|
14
|
+
* SQLite and said nothing — a config whose central word was ignored.
|
|
15
|
+
*/
|
|
16
|
+
function refuseUnresolvable(declared, field) {
|
|
17
|
+
if (declared === undefined || declared === 'sqlite')
|
|
18
|
+
return;
|
|
19
|
+
throw new Error(`${field} '${declared}' cannot be resolved from its name — only 'sqlite' can, ` +
|
|
20
|
+
`because it is the one driver this package depends on. For ${declared}, build the ` +
|
|
21
|
+
`Kysely dialect yourself and call setupKysely(dialect, '${declared}') ` +
|
|
22
|
+
`from @fougere/adapter-sql.`);
|
|
23
|
+
}
|
|
24
|
+
/** The name an entity is registered under — 'Book' and 'book' name the same rows. */
|
|
25
|
+
const keyOf = (name) => name.charAt(0).toLowerCase() + name.slice(1);
|
|
26
|
+
/** Every entity the app hosts, wherever its rows are — what a partition is cut from. */
|
|
27
|
+
function entityNames(app) {
|
|
28
|
+
return app
|
|
29
|
+
.fronds.flatMap((frond) => frond.entities.map((entry) => entry.name));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The app as ONE source sees it: the entities that live there, and the names of those
|
|
33
|
+
* that do not.
|
|
34
|
+
*
|
|
35
|
+
* The second half is what lets the DDL stop lying. A batch holding every entity could
|
|
36
|
+
* never tell a cross-source target from a typo, so it derived a table name and emitted
|
|
37
|
+
* a foreign key against a table that may not exist. Cut per source, `elsewhere` says
|
|
38
|
+
* which misses are legitimate — and a target in neither list is a mistake, out loud.
|
|
39
|
+
*
|
|
40
|
+
* Auth entities ride with the default source: a provider's tables are the app's own,
|
|
41
|
+
* and nothing yet lets one declare where it lives.
|
|
42
|
+
*/
|
|
43
|
+
function partition(app, holds, withAuth, materialize) {
|
|
44
|
+
const typed = app;
|
|
45
|
+
const fronds = typed.fronds
|
|
46
|
+
.map((frond) => ({ ...frond, entities: frond.entities.filter((entry) => holds(entry.name)) }))
|
|
47
|
+
.filter((frond) => frond.entities.length > 0);
|
|
48
|
+
return {
|
|
49
|
+
fronds,
|
|
50
|
+
auth: withAuth ? typed.auth : undefined,
|
|
51
|
+
elsewhere: entityNames(app).filter((name) => !holds(name)),
|
|
52
|
+
// A derivation makes no table unless a source names it — see AppLike.materialize.
|
|
53
|
+
materialize,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the data layer. `db: false` (or absent) means a frond with no
|
|
58
|
+
* persistence of its own — the caller decides what to fall back to.
|
|
59
|
+
*
|
|
60
|
+
* `sources` names the places that are NOT the default one. An entity it does not
|
|
61
|
+
* name stays in `db`, so an app with one database declares nothing and behaves
|
|
62
|
+
* exactly as before.
|
|
63
|
+
*/
|
|
64
|
+
export function resolveStorage(dbConf, sources) {
|
|
65
|
+
if (!declaresStorage(dbConf))
|
|
66
|
+
return { ormFactory: undefined };
|
|
67
|
+
refuseUnresolvable(typeof dbConf === 'object' ? dbConf.dialect : (dbConf || undefined), 'db.dialect');
|
|
68
|
+
const named = {};
|
|
69
|
+
for (const [name, conf] of Object.entries(sources ?? {})) {
|
|
70
|
+
refuseUnresolvable(conf.dialect, `sources.${name}.dialect`);
|
|
71
|
+
named[name] = { setup: setupSqlite({ path: conf.path }), entities: conf.entities };
|
|
72
|
+
}
|
|
73
|
+
return storageFrom({
|
|
74
|
+
db: setupSqlite({ path: typeof dbConf === 'object' ? dbConf.path : undefined }),
|
|
75
|
+
sources: named,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The same routing `resolveStorage` performs, over engines the CALLER built.
|
|
80
|
+
*
|
|
81
|
+
* `resolveStorage` reads a config file, and a config file cannot hold a live Kysely
|
|
82
|
+
* dialect — so it can only ever resolve sqlite, the one driver this package depends
|
|
83
|
+
* on. That was fine while `db:` was alone, because the escape hatch was to abandon
|
|
84
|
+
* the convention entirely and hand `configureFougere` your own factory. With several
|
|
85
|
+
* sources that stopped being an escape: a user wanting Postgres for ONE of them had
|
|
86
|
+
* to re-implement the routing and the per-source migration to keep the other.
|
|
87
|
+
*
|
|
88
|
+
* So the two jobs are separated. Resolving a NAME into an engine is sqlite-only and
|
|
89
|
+
* stays there; placing entities and migrating each source is engine-agnostic and
|
|
90
|
+
* lives here, where any `Setup` is welcome:
|
|
91
|
+
*
|
|
92
|
+
* ```ts
|
|
93
|
+
* configureFougere(storageFrom({
|
|
94
|
+
* db: setupSqlite({ path: '.data/app.db' }),
|
|
95
|
+
* sources: { legacy: { setup: setupKysely(pgDialect, 'postgres'), entities: ['Book'] } },
|
|
96
|
+
* }));
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function storageFrom(declared) {
|
|
100
|
+
const { db: base, sources } = declared;
|
|
101
|
+
// Where each named entity lives. An entity claimed by two sources is refused naming
|
|
102
|
+
// both: the rows would be read from one and written to the other, by whichever
|
|
103
|
+
// registration ran last — the same silent duplicate `remotes:` refuses one level up.
|
|
104
|
+
const home = new Map();
|
|
105
|
+
const engines = new Map();
|
|
106
|
+
/** Everything a source names — the opt-in that turns a derivation into stored rows. */
|
|
107
|
+
const named = [];
|
|
108
|
+
for (const [name, placement] of Object.entries(sources ?? {})) {
|
|
109
|
+
engines.set(name, placement.setup);
|
|
110
|
+
for (const entity of placement.entities) {
|
|
111
|
+
const key = keyOf(entity);
|
|
112
|
+
const claimed = home.get(key);
|
|
113
|
+
if (claimed) {
|
|
114
|
+
throw new Error(`sources: '${entity}' is claimed by both '${claimed}' and '${name}' — an entity lives in one place.`);
|
|
115
|
+
}
|
|
116
|
+
home.set(key, name);
|
|
117
|
+
named.push(entity);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const engineFor = (entityName) => {
|
|
121
|
+
const source = home.get(keyOf(entityName));
|
|
122
|
+
return (source && engines.get(source)) || base;
|
|
123
|
+
};
|
|
124
|
+
return {
|
|
125
|
+
db: base.db,
|
|
126
|
+
ormFactory: (entity, name) => engineFor(name).ormFactory(entity, name),
|
|
127
|
+
raw: base.sqlite,
|
|
128
|
+
dialect: base.dialect,
|
|
129
|
+
// Additive migration: creates missing tables AND adds columns an entity gained.
|
|
130
|
+
// One pass per source, each seeing only its own tables — which is what makes a
|
|
131
|
+
// cross-source `ref()` a miss rather than a constraint against a stranger.
|
|
132
|
+
afterBoot: async (app) => {
|
|
133
|
+
await migrate(partition(app, (name) => !home.has(keyOf(name)), true, named), base.db);
|
|
134
|
+
for (const [name, engine] of engines) {
|
|
135
|
+
await migrate(partition(app, (e) => home.get(keyOf(e)) === name, false, named), engine.db);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fougere/defaults",
|
|
3
|
+
"version": "0.2.0-alpha.2",
|
|
4
|
+
"description": "The conventional boot of a Fougere app — the one place storage is wired.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"fougere",
|
|
7
|
+
"typescript",
|
|
8
|
+
"framework",
|
|
9
|
+
"bootstrap",
|
|
10
|
+
"sqlite"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/chok/fougere.git",
|
|
16
|
+
"directory": "packages/defaults"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "dist/index.js",
|
|
20
|
+
"types": "dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@fougere/core": "0.2.0-alpha.2",
|
|
32
|
+
"@fougere/transport-http": "0.2.0-alpha.2",
|
|
33
|
+
"@fougere/container": "0.2.0-alpha.2",
|
|
34
|
+
"@fougere/adapter-sql": "0.2.0-alpha.2"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"kysely": "^0.28.17",
|
|
41
|
+
"better-sqlite3": "^13.0.3",
|
|
42
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
43
|
+
"@fougere/schema": "0.2.0-alpha.2"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "rm -rf dist && tsc",
|
|
47
|
+
"typecheck": "tsc --noEmit -p tsconfig.test.json",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"test:watch": "vitest"
|
|
50
|
+
}
|
|
51
|
+
}
|