@fougere/core 0.5.0-alpha.0 → 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/boot/Emissions.d.ts +2 -2
- package/dist/boot/Emissions.d.ts.map +1 -1
- package/dist/boot/Emissions.js +1 -1
- package/dist/boot/HandlerFacade.d.ts +1 -1
- package/dist/boot/HandlerFacade.d.ts.map +1 -1
- package/dist/boot/HandlerFacade.js +1 -1
- package/dist/boot/HandlerFacade.js.map +1 -1
- package/dist/boot/auth.d.ts +6 -6
- package/dist/boot/auth.d.ts.map +1 -1
- package/dist/boot/boot.d.ts +2 -2
- package/dist/boot/boot.d.ts.map +1 -1
- package/dist/boot/boot.js +1 -1
- package/dist/boot/boot.js.map +1 -1
- package/dist/boot/bootstrap.d.ts.map +1 -1
- package/dist/boot/bootstrap.js +47 -38
- package/dist/boot/bootstrap.js.map +1 -1
- package/dist/boot/card.d.ts +21 -0
- package/dist/boot/card.d.ts.map +1 -0
- package/dist/boot/card.js +100 -0
- package/dist/boot/card.js.map +1 -0
- package/dist/boot/frame.d.ts +3 -3
- package/dist/boot/frame.d.ts.map +1 -1
- package/dist/boot/frame.js +7 -7
- package/dist/boot/frame.js.map +1 -1
- package/dist/boot/hosted.d.ts +8 -2
- package/dist/boot/hosted.d.ts.map +1 -1
- package/dist/boot/hosted.js +10 -10
- package/dist/boot/hosted.js.map +1 -1
- package/dist/boot/ownership.d.ts +7 -5
- package/dist/boot/ownership.d.ts.map +1 -1
- package/dist/boot/ownership.js +9 -14
- package/dist/boot/ownership.js.map +1 -1
- package/dist/boot/ports.d.ts +2 -2
- package/dist/boot/ports.d.ts.map +1 -1
- package/dist/boot/ports.js +1 -1
- package/dist/boot/runner.d.ts +28 -0
- package/dist/boot/runner.d.ts.map +1 -0
- package/dist/boot/runner.js +26 -0
- package/dist/boot/runner.js.map +1 -0
- package/dist/boot/seed.d.ts +2 -2
- package/dist/boot/seed.d.ts.map +1 -1
- package/dist/boot/seed.js +5 -5
- package/dist/boot/seed.js.map +1 -1
- package/dist/boot/statement-drift.d.ts +2 -1
- package/dist/boot/statement-drift.d.ts.map +1 -1
- package/dist/boot/statement-drift.js.map +1 -1
- package/dist/boot/together.d.ts +4 -4
- package/dist/boot/together.d.ts.map +1 -1
- package/dist/boot/together.js +17 -19
- package/dist/boot/together.js.map +1 -1
- package/dist/boot/types.d.ts +12 -11
- package/dist/boot/types.d.ts.map +1 -1
- package/dist/contract/CallLog.d.ts +2 -2
- package/dist/contract/CallLog.d.ts.map +1 -1
- package/dist/contract/RouteAddress.d.ts +2 -0
- package/dist/contract/RouteAddress.d.ts.map +1 -1
- package/dist/contract/RouteAddress.js.map +1 -1
- package/dist/{scan/declare.d.ts → declare.d.ts} +3 -3
- package/dist/declare.d.ts.map +1 -0
- package/dist/{scan/declare.js → declare.js} +3 -3
- package/dist/declare.js.map +1 -0
- package/dist/{scan → descriptor}/Fronds.d.ts +6 -7
- package/dist/descriptor/Fronds.d.ts.map +1 -0
- package/dist/{scan → descriptor}/Fronds.js +6 -7
- package/dist/descriptor/Fronds.js.map +1 -0
- package/dist/{scan → descriptor}/frond.d.ts +7 -47
- package/dist/descriptor/frond.d.ts.map +1 -0
- package/dist/{scan → descriptor}/frond.js.map +1 -1
- package/dist/dispatch/ArgumentResolver.d.ts +5 -2
- package/dist/dispatch/ArgumentResolver.d.ts.map +1 -1
- package/dist/dispatch/ArgumentResolver.js +48 -3
- package/dist/dispatch/ArgumentResolver.js.map +1 -1
- package/dist/dispatch/DispatchEvent.d.ts +1 -1
- package/dist/dispatch/DispatchEvent.d.ts.map +1 -1
- package/dist/dispatch/PresenterArgumentResolver.d.ts +1 -1
- package/dist/dispatch/PresenterArgumentResolver.d.ts.map +1 -1
- package/dist/dispatch/PresenterArgumentResolver.js +1 -1
- package/dist/dispatch/Route.d.ts +1 -2
- package/dist/dispatch/Route.d.ts.map +1 -1
- package/dist/dispatch/StorageGuard.d.ts +2 -2
- package/dist/dispatch/StorageGuard.d.ts.map +1 -1
- package/dist/dispatch/StorageGuard.js +7 -7
- package/dist/dispatch/StorageGuard.js.map +1 -1
- package/dist/effective-operation.d.ts +29 -3
- package/dist/effective-operation.d.ts.map +1 -1
- package/dist/effective-operation.js +52 -1
- package/dist/effective-operation.js.map +1 -1
- package/dist/emit.d.ts +1 -1
- package/dist/frond-config.d.ts +2 -2
- package/dist/graph.d.ts +1 -1
- package/dist/graph.d.ts.map +1 -1
- package/dist/imports.d.ts +1 -1
- package/dist/imports.d.ts.map +1 -1
- package/dist/index.d.ts +15 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -6
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +2 -2
- package/dist/node.js.map +1 -1
- package/dist/prefab/collector.d.ts +2 -2
- package/dist/prefab/collector.js +2 -2
- package/dist/prefab/crud.d.ts +5 -5
- package/dist/prefab/crud.d.ts.map +1 -1
- package/dist/prefab/crud.js +12 -12
- package/dist/prefab/crud.js.map +1 -1
- package/dist/prefab/mirror.d.ts +5 -5
- package/dist/prefab/mirror.d.ts.map +1 -1
- package/dist/prefab/mirror.js +5 -5
- package/dist/prefab/mirror.js.map +1 -1
- package/dist/prefab/presenter.d.ts +2 -2
- package/dist/prefab/presenter.js +2 -2
- package/dist/prefab/repository.d.ts +14 -14
- package/dist/prefab/repository.d.ts.map +1 -1
- package/dist/prefab/repository.js +19 -19
- package/dist/prefab/repository.js.map +1 -1
- package/dist/rows.d.ts +40 -0
- package/dist/rows.d.ts.map +1 -0
- package/dist/rows.js +146 -0
- package/dist/rows.js.map +1 -0
- package/dist/scan/conventions.d.ts +8 -12
- package/dist/scan/conventions.d.ts.map +1 -1
- package/dist/scan/conventions.js +0 -13
- package/dist/scan/conventions.js.map +1 -1
- package/dist/scan/emit.d.ts +1 -1
- package/dist/scan/emit.d.ts.map +1 -1
- package/dist/scan/emit.js +1 -1
- package/dist/scan/emit.js.map +1 -1
- package/dist/scan/handler-parser.d.ts +11 -56
- package/dist/scan/handler-parser.d.ts.map +1 -1
- package/dist/scan/handler-parser.js +2 -2
- package/dist/scan/handler-parser.js.map +1 -1
- package/dist/scan/result.d.ts +41 -0
- package/dist/scan/result.d.ts.map +1 -0
- package/dist/scan/result.js +2 -0
- package/dist/scan/result.js.map +1 -0
- package/dist/scan/scanner.d.ts +18 -1
- package/dist/scan/scanner.d.ts.map +1 -1
- package/dist/scan/scanner.js +34 -17
- package/dist/scan/scanner.js.map +1 -1
- package/dist/scan/statement.d.ts +1 -1
- package/dist/scan/statement.d.ts.map +1 -1
- package/dist/scan/statement.js.map +1 -1
- package/dist/{orm.d.ts → storage.d.ts} +98 -16
- package/dist/storage.d.ts.map +1 -0
- package/dist/{orm.js → storage.js} +53 -5
- package/dist/storage.js.map +1 -0
- package/dist/verify.d.ts +1 -1
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +2 -2
- package/dist/verify.js.map +1 -1
- package/dist/{boot → wire}/binding.d.ts +2 -10
- package/dist/wire/binding.d.ts.map +1 -0
- package/dist/{boot → wire}/binding.js +0 -52
- package/dist/wire/binding.js.map +1 -0
- package/dist/wire/call.d.ts +2 -40
- package/dist/wire/call.d.ts.map +1 -1
- package/dist/wire/call.js +0 -125
- package/dist/wire/call.js.map +1 -1
- package/dist/wire/operation.d.ts +5 -31
- package/dist/wire/operation.d.ts.map +1 -1
- package/dist/wire/operation.js +0 -51
- package/dist/wire/operation.js.map +1 -1
- package/dist/wire/signature.d.ts +58 -0
- package/dist/wire/signature.d.ts.map +1 -0
- package/dist/wire/signature.js +2 -0
- package/dist/wire/signature.js.map +1 -0
- package/package.json +3 -3
- package/src/boot/Emissions.ts +2 -2
- package/src/boot/HandlerFacade.ts +3 -3
- package/src/boot/auth.ts +6 -6
- package/src/boot/boot.ts +3 -3
- package/src/boot/bootstrap.ts +50 -39
- package/src/boot/card.ts +113 -0
- package/src/boot/frame.ts +7 -7
- package/src/boot/hosted.ts +11 -14
- package/src/boot/ownership.ts +14 -15
- package/src/boot/ports.ts +2 -2
- package/src/boot/runner.ts +34 -0
- package/src/boot/seed.ts +6 -6
- package/src/boot/statement-drift.ts +5 -4
- package/src/boot/together.ts +24 -25
- package/src/boot/types.ts +12 -11
- package/src/contract/CallLog.ts +2 -2
- package/src/contract/RouteAddress.ts +3 -0
- package/src/{scan/declare.ts → declare.ts} +5 -5
- package/src/{scan → descriptor}/Fronds.ts +6 -7
- package/src/{scan → descriptor}/frond.ts +7 -48
- package/src/dispatch/ArgumentResolver.ts +53 -7
- package/src/dispatch/DispatchEvent.ts +1 -1
- package/src/dispatch/PresenterArgumentResolver.ts +2 -2
- package/src/dispatch/Route.ts +1 -3
- package/src/dispatch/StorageGuard.ts +7 -7
- package/src/effective-operation.ts +63 -11
- package/src/emit.ts +1 -1
- package/src/frond-config.ts +2 -2
- package/src/graph.ts +1 -1
- package/src/imports.ts +1 -1
- package/src/index.ts +15 -13
- package/src/node.ts +2 -2
- package/src/prefab/collector.ts +2 -2
- package/src/prefab/crud.ts +18 -18
- package/src/prefab/mirror.ts +8 -8
- package/src/prefab/presenter.ts +2 -2
- package/src/prefab/repository.ts +32 -32
- package/src/rows.ts +155 -0
- package/src/scan/conventions.ts +0 -18
- package/src/scan/emit.ts +3 -5
- package/src/scan/handler-parser.ts +14 -67
- package/src/scan/result.ts +42 -0
- package/src/scan/scanner.ts +44 -21
- package/src/scan/statement.ts +2 -1
- package/src/{orm.ts → storage.ts} +126 -17
- package/src/verify.ts +3 -3
- package/src/{boot → wire}/binding.ts +2 -66
- package/src/wire/call.ts +1 -129
- package/src/wire/operation.ts +5 -62
- package/src/wire/signature.ts +59 -0
- package/dist/boot/binding.d.ts.map +0 -1
- package/dist/boot/binding.js.map +0 -1
- package/dist/orm.d.ts.map +0 -1
- package/dist/orm.js.map +0 -1
- package/dist/scan/Fronds.d.ts.map +0 -1
- package/dist/scan/Fronds.js.map +0 -1
- package/dist/scan/declare.d.ts.map +0 -1
- package/dist/scan/declare.js.map +0 -1
- package/dist/scan/frond.d.ts.map +0 -1
- /package/dist/{scan → descriptor}/frond.js +0 -0
package/src/boot/together.ts
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
import { ambient } from '#ambient';
|
|
11
11
|
import { upperFirst, lowerFirst, type SchemaView } from '@fougere/schema';
|
|
12
12
|
import type { Container } from '@fougere/container';
|
|
13
|
-
import { membersOfTogetherKey,
|
|
13
|
+
import { membersOfTogetherKey, storageKeyOf, entityOfStorageKey, type Storage, type StorageFactory } from '../storage.js';
|
|
14
14
|
import type { Logger } from '../builtins/logger.js';
|
|
15
|
-
import type { ProviderEntry } from '../
|
|
15
|
+
import type { ProviderEntry } from '../descriptor/frond.js';
|
|
16
16
|
import { StorageGuard } from '../dispatch/StorageGuard.js';
|
|
17
17
|
import { recording, unwind, type Undo } from './frame.js';
|
|
18
18
|
|
|
@@ -23,9 +23,9 @@ export interface FrameWorld {
|
|
|
23
23
|
/** Which frond holds an entity — the question `remotes:` turns into a refusal. */
|
|
24
24
|
frondOf: Map<string, string>;
|
|
25
25
|
hostedHere: (frond: string) => boolean;
|
|
26
|
-
|
|
26
|
+
storageFactory?: StorageFactory;
|
|
27
27
|
sourceOf?: (entityName: string) => string;
|
|
28
|
-
transacted?: <R>(source: string, fn: (
|
|
28
|
+
transacted?: <R>(source: string, fn: (storageFactory: StorageFactory) => Promise<R>) => Promise<R>;
|
|
29
29
|
log: Logger;
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -74,7 +74,7 @@ function resolve(names: { entities: string[]; providers: string[] }, declared: r
|
|
|
74
74
|
/**
|
|
75
75
|
* A member that cannot write here at all — its frond is hosted elsewhere.
|
|
76
76
|
*
|
|
77
|
-
* Refused rather than compensated: a compensated frame still writes through a local
|
|
77
|
+
* Refused rather than compensated: a compensated frame still writes through a local storage,
|
|
78
78
|
* and a remote frond registers none. There is nothing to record and nothing to undo.
|
|
79
79
|
*/
|
|
80
80
|
function refuseRemote(members: Members, world: FrameWorld, key: string): void {
|
|
@@ -96,13 +96,12 @@ function refuseRemote(members: Members, world: FrameWorld, key: string): void {
|
|
|
96
96
|
* The frame does not widen itself to cover it: doing so would pull an entity from another
|
|
97
97
|
* source into the group without anyone writing it down. Named instead, with the one-word fix.
|
|
98
98
|
*/
|
|
99
|
-
function refuseUncoveredWrites(members: Members, key: string): void {
|
|
99
|
+
function refuseUncoveredWrites(members: Members, key: string, world: FrameWorld): void {
|
|
100
100
|
const covered = new Set(members.entities.map((member) => member.name));
|
|
101
101
|
for (const provider of members.providers) {
|
|
102
102
|
for (const dep of provider.deps) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (covered.has(entity)) continue;
|
|
103
|
+
const entity = entityOfStorageKey(dep, (name) => world.entityByName.has(name));
|
|
104
|
+
if (!entity || covered.has(entity)) continue;
|
|
106
105
|
throw new Error(
|
|
107
106
|
`Together<[…]> (${key}): ${provider.ctor.name} writes ${entity}, which is not in the ` +
|
|
108
107
|
`frame's entity list — its writes would escape the unwind. Add it: ` +
|
|
@@ -115,30 +114,30 @@ function refuseUncoveredWrites(members: Members, key: string): void {
|
|
|
115
114
|
/**
|
|
116
115
|
* Open the block: build every member over `factory`, in a scope of their own.
|
|
117
116
|
*
|
|
118
|
-
* The scope is what makes a provider member work without a locator — its
|
|
117
|
+
* The scope is what makes a provider member work without a locator — its storage keys are
|
|
119
118
|
* registered here, so the container hands it the framed ones through the ordinary
|
|
120
119
|
* constructor. It is disposed when the block ends, whichever way it ended.
|
|
121
120
|
*/
|
|
122
121
|
async function inScope<R>(
|
|
123
122
|
parent: Container,
|
|
124
123
|
members: Members,
|
|
125
|
-
factory:
|
|
126
|
-
wrap: (
|
|
124
|
+
factory: StorageFactory,
|
|
125
|
+
wrap: (storage: Storage, name: string, schema: SchemaView) => Storage,
|
|
127
126
|
fn: (entities: unknown[], providers: unknown[]) => Promise<R>,
|
|
128
127
|
): Promise<R> {
|
|
129
128
|
const scope = parent.createScope();
|
|
130
129
|
try {
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
scope.registerValue(
|
|
134
|
-
return
|
|
130
|
+
const storages = members.entities.map((member) => {
|
|
131
|
+
const storage = wrap(factory(member.schema, member.name), member.name, member.schema);
|
|
132
|
+
scope.registerValue(storageKeyOf(member.name), storage);
|
|
133
|
+
return storage;
|
|
135
134
|
});
|
|
136
|
-
// Providers after every
|
|
135
|
+
// Providers after every storage is in place: one may depend on another member's.
|
|
137
136
|
const built = members.providers.map((provider) => {
|
|
138
137
|
scope.register(provider.ctor.name, provider.ctor, { deps: provider.deps });
|
|
139
138
|
return scope.resolve(provider.ctor.name);
|
|
140
139
|
});
|
|
141
|
-
return await fn(
|
|
140
|
+
return await fn(storages, built);
|
|
142
141
|
} finally {
|
|
143
142
|
await scope.dispose();
|
|
144
143
|
}
|
|
@@ -162,17 +161,17 @@ export function registerFrames(
|
|
|
162
161
|
const names = membersOfTogetherKey(key);
|
|
163
162
|
if (!names) continue;
|
|
164
163
|
registered += 1;
|
|
165
|
-
if (!world.
|
|
164
|
+
if (!world.storageFactory) {
|
|
166
165
|
throw new Error(`Together<[${names.entities.join(', ')}]> needs storage, and this boot declares none.`);
|
|
167
166
|
}
|
|
168
167
|
|
|
169
168
|
const members = resolve(names, providers, world);
|
|
170
169
|
refuseRemote(members, world, key);
|
|
171
|
-
refuseUncoveredWrites(members, key);
|
|
170
|
+
refuseUncoveredWrites(members, key, world);
|
|
172
171
|
|
|
173
172
|
const sources = new Set(members.entities.map((member) => world.sourceOf?.(member.name) ?? 'db'));
|
|
174
|
-
const judge = (
|
|
175
|
-
new StorageGuard(schema.getFields(), name).guard(
|
|
173
|
+
const judge = (storage: Storage, name: string, schema: SchemaView) =>
|
|
174
|
+
new StorageGuard(schema.getFields(), name).guard(storage);
|
|
176
175
|
|
|
177
176
|
// One engine and a way into it: the engine gives the unwind AND the isolation.
|
|
178
177
|
if (world.transacted && sources.size === 1) {
|
|
@@ -196,10 +195,10 @@ export function registerFrames(
|
|
|
196
195
|
scope.registerValue(key, {
|
|
197
196
|
run: <R>(fn: (entities: never, providers: never) => Promise<R>): Promise<R> => ambient.enterFrame(key, async () => {
|
|
198
197
|
const journal: Undo[] = [];
|
|
199
|
-
const record = (
|
|
200
|
-
judge(recording(
|
|
198
|
+
const record = (storage: Storage, name: string, schema: SchemaView) =>
|
|
199
|
+
judge(recording(storage, name, schema.getFields(), journal), name, schema);
|
|
201
200
|
try {
|
|
202
|
-
return await inScope(scope, members, world.
|
|
201
|
+
return await inScope(scope, members, world.storageFactory!, record, fn as never);
|
|
203
202
|
} catch (cause) {
|
|
204
203
|
return unwind(journal, cause, world.log);
|
|
205
204
|
}
|
package/src/boot/types.ts
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* landed in it, seventeen notions deep.
|
|
7
7
|
*/
|
|
8
8
|
import type { Container } from '@fougere/container';
|
|
9
|
-
import type { Fronds } from '../
|
|
10
|
-
import type { FrondDescriptor
|
|
9
|
+
import type { Fronds } from '../descriptor/Fronds.js';
|
|
10
|
+
import type { FrondDescriptor } from '../descriptor/frond.js';
|
|
11
|
+
import type { ScanResult } from '../scan/result.js';
|
|
11
12
|
import type { SchemaView } from '@fougere/schema';
|
|
12
|
-
import type {
|
|
13
|
+
import type { StorageFactory } from '../storage.js';
|
|
13
14
|
import type { AppMiddleware } from '../wire/middleware.js';
|
|
14
15
|
import type { RpcAnswer, Transport } from '../wire/call.js';
|
|
15
16
|
import type { Extension } from './AppLifecycle.js';
|
|
@@ -22,23 +23,23 @@ import type { DispatchPort } from '../dispatch/DispatchPort.js';
|
|
|
22
23
|
export interface CreateAppOptions {
|
|
23
24
|
/** Factory function to create the container. Required. */
|
|
24
25
|
createContainer: () => Container;
|
|
25
|
-
/** Factory to auto-generate
|
|
26
|
-
|
|
26
|
+
/** Factory to auto-generate Storage for each scanned entity. */
|
|
27
|
+
storageFactory?: StorageFactory;
|
|
27
28
|
/**
|
|
28
29
|
* Which source an entity's rows live in, and how to open a transaction on one — the two
|
|
29
30
|
* questions that decide whether `Together<[…]>` gets the engine's own unwind or replays
|
|
30
31
|
* inverses itself.
|
|
31
32
|
*
|
|
32
|
-
* Optional together, because a host may hand in a bare `
|
|
33
|
+
* Optional together, because a host may hand in a bare `storageFactory` and know neither. A
|
|
33
34
|
* frame then compensates: not knowing where the rows are and promising atomicity over
|
|
34
35
|
* them are two different things, and only one of them is honest.
|
|
35
36
|
*/
|
|
36
37
|
sourceOf?: (entityName: string) => string;
|
|
37
|
-
transacted?: <R>(source: string, fn: (
|
|
38
|
+
transacted?: <R>(source: string, fn: (storageFactory: StorageFactory) => Promise<R>) => Promise<R>;
|
|
38
39
|
/**
|
|
39
40
|
* Builds the cross-source reader a frond gets when it declares `reads:`.
|
|
40
41
|
*
|
|
41
|
-
* A factory rather than a value, for the same reason `
|
|
42
|
+
* A factory rather than a value, for the same reason `storageFactory` is one: core must
|
|
42
43
|
* not name a storage package, and this one costs 71 MB of downloaded extensions and a
|
|
43
44
|
* native module — nothing a first run that only wanted sqlite should carry. The host
|
|
44
45
|
* decides what backs it; `@fougere/adapter-duckdb` is one answer, not the contract.
|
|
@@ -219,14 +220,14 @@ export interface App extends DispatchPort {
|
|
|
219
220
|
* the dual of {@link facadeFor}. `undefined` when no loaded frond hosts the entity,
|
|
220
221
|
* or when the app booted with no storage at all.
|
|
221
222
|
*
|
|
222
|
-
* `unknown` because the port belongs to whoever wired it: narrowing it to `
|
|
223
|
+
* `unknown` because the port belongs to whoever wired it: narrowing it to `Storage`
|
|
223
224
|
* is the caller saying which implementation they are standing on.
|
|
224
225
|
*/
|
|
225
|
-
|
|
226
|
+
storageFor(entity: string): unknown | undefined;
|
|
226
227
|
/**
|
|
227
228
|
* The presenter of an entity, resolved through its owning frond's scope.
|
|
228
229
|
*
|
|
229
|
-
* Same shape as {@link
|
|
230
|
+
* Same shape as {@link storageFor}, and it exists so an adapter never spells the container
|
|
230
231
|
* key itself: `schema-graphql` wrote out `${Name}Presenter` by hand, and a key respelled
|
|
231
232
|
* in a second place finds nothing and reports nothing the day the convention moves.
|
|
232
233
|
*/
|
package/src/contract/CallLog.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RouteKind } from './RouteAddress.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* What crosses the door: the address, the route it took, and the verdict.
|
|
@@ -18,7 +18,7 @@ export interface CallRecord {
|
|
|
18
18
|
operation: string;
|
|
19
19
|
surface?: string;
|
|
20
20
|
/** Known at `resolved`, so absent on a call that never found a route. */
|
|
21
|
-
route?:
|
|
21
|
+
route?: RouteKind;
|
|
22
22
|
/**
|
|
23
23
|
* The traceparent the invocation carried, when one did.
|
|
24
24
|
*
|
|
@@ -19,9 +19,9 @@ import { lowerFirst, type SchemaView } from '@fougere/schema';
|
|
|
19
19
|
import type {
|
|
20
20
|
CollectorEntry, EntityEntry, FrondDescriptor, HandlerEntry,
|
|
21
21
|
PresenterEntry, ProviderEntry, SeedEntry,
|
|
22
|
-
} from './frond.js';
|
|
23
|
-
import { DEFAULT_CONVENTIONS } from './conventions.js';
|
|
24
|
-
import { getPresenterFields } from '
|
|
22
|
+
} from './descriptor/frond.js';
|
|
23
|
+
import { DEFAULT_CONVENTIONS } from './scan/conventions.js';
|
|
24
|
+
import { getPresenterFields } from './prefab/presenter.js';
|
|
25
25
|
|
|
26
26
|
/** A class, as a declaration hands it over: the constructor itself. */
|
|
27
27
|
type Ctor = new (...args: never[]) => unknown;
|
|
@@ -31,7 +31,7 @@ type Ctor = new (...args: never[]) => unknown;
|
|
|
31
31
|
*
|
|
32
32
|
* `deps` is the one thing no runtime can recover: TypeScript erases the parameter types,
|
|
33
33
|
* and `registerFrames` reads them to know which frames to build — asking for one IS
|
|
34
|
-
* declaring it. A handler that takes only its own entity's
|
|
34
|
+
* declaring it. A handler that takes only its own entity's storage needs nothing here.
|
|
35
35
|
*/
|
|
36
36
|
export interface DeclaredSubject {
|
|
37
37
|
ctor: Ctor;
|
|
@@ -98,7 +98,7 @@ export interface FrondDeclaration {
|
|
|
98
98
|
*
|
|
99
99
|
* ```ts
|
|
100
100
|
* createApp({
|
|
101
|
-
* scan: { fronds: Fronds.
|
|
101
|
+
* scan: { fronds: Fronds.hosting([frond('blog', { entities: [Post], handlers: [PostHandler] })]), diagnostics: [] },
|
|
102
102
|
* })
|
|
103
103
|
* ```
|
|
104
104
|
*/
|
|
@@ -2,8 +2,7 @@ import type { SchemaView } from '@fougere/schema';
|
|
|
2
2
|
import type { EntityEntry, FrondDescriptor } from './frond.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* What this app hosts
|
|
6
|
-
* by hand.
|
|
5
|
+
* What this app hosts, with the questions everyone was asking it by hand.
|
|
7
6
|
*
|
|
8
7
|
* The list was a bare array, so every reader re-walked it: "which frond owns this
|
|
9
8
|
* entity" was spelled four times, "every entity by name" three times as three literal
|
|
@@ -20,8 +19,8 @@ export class Fronds extends Array<FrondDescriptor> {
|
|
|
20
19
|
return Array;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
/**
|
|
24
|
-
static
|
|
22
|
+
/** The fronds an app hosts, scanned or stated. The only gate in — `Array.of` is taken. */
|
|
23
|
+
static hosting(fronds: readonly FrondDescriptor[]): Fronds {
|
|
25
24
|
const held = new Fronds();
|
|
26
25
|
held.push(...fronds);
|
|
27
26
|
return held;
|
|
@@ -32,7 +31,7 @@ export class Fronds extends Array<FrondDescriptor> {
|
|
|
32
31
|
return this.find((frond) => frond.entities.some((e) => e.name === entity));
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
/** The entity of that name, wherever it was
|
|
34
|
+
/** The entity of that name, wherever it was declared. */
|
|
36
35
|
entity(name: string): EntityEntry | undefined {
|
|
37
36
|
for (const frond of this) {
|
|
38
37
|
const found = frond.entities.find((e) => e.name === name);
|
|
@@ -42,7 +41,7 @@ export class Fronds extends Array<FrondDescriptor> {
|
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
/**
|
|
45
|
-
* Every
|
|
44
|
+
* Every entity class, by name — across all fronds and never per-frond.
|
|
46
45
|
*
|
|
47
46
|
* A fact is announced in one frond and heard in another, so the subscriber's own
|
|
48
47
|
* frond does not hold the shape it must judge; a `reads:` clause names entities
|
|
@@ -52,7 +51,7 @@ export class Fronds extends Array<FrondDescriptor> {
|
|
|
52
51
|
return new Map(this.flatMap((frond) => frond.entities.map((e) => [e.name, e.entityClass] as const)));
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
/** Every
|
|
54
|
+
/** Every declared entity, sorted. What is DECLARED, not what answers. */
|
|
56
55
|
entityNames(): string[] {
|
|
57
56
|
return this.flatMap((frond) => frond.entities.map((e) => e.name)).sort();
|
|
58
57
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* run could not read.
|
|
2
|
+
* What a frond is made of — one interface per convention directory.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
4
|
+
* Produced two ways and indistinguishable once here: the scan DERIVES it from a
|
|
5
|
+
* directory tree, `frond()` STATES it outright. `frond-config.ts` is the declared
|
|
6
|
+
* side; they meet at `FrondDescriptor.operationsOverrides`.
|
|
7
7
|
*/
|
|
8
8
|
import type { SchemaView } from '@fougere/schema';
|
|
9
|
-
import type {
|
|
9
|
+
import type { Param, OperationContract, OperationsMap } from '../wire/operation.js';
|
|
10
10
|
import type { PresenterViews } from '../prefab/presenter.js';
|
|
11
11
|
import type { Fronds } from './Fronds.js';
|
|
12
12
|
|
|
@@ -74,7 +74,7 @@ export interface HandlerEntry {
|
|
|
74
74
|
filePath: string;
|
|
75
75
|
/** Whether this handler is part of the frond's public contract. */
|
|
76
76
|
exposed?: boolean;
|
|
77
|
-
/** Output schema override — when Crud(Entity, Output), restricts
|
|
77
|
+
/** Output schema override — when Crud(Entity, Output), restricts storage output. */
|
|
78
78
|
outputOverride?: SchemaView;
|
|
79
79
|
/** Surface name — subdirectory in handlers/ (e.g. 'admin', 'public'). */
|
|
80
80
|
surface?: string;
|
|
@@ -104,7 +104,7 @@ export interface PresenterFieldMeta {
|
|
|
104
104
|
* A computed field that declares `user?: User` is then fed by the collector
|
|
105
105
|
* that resolves one. A presenter is not a second mechanism.
|
|
106
106
|
*/
|
|
107
|
-
params?:
|
|
107
|
+
params?: Param[];
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
/** A discovered presenter (computed fields for an entity's output). */
|
|
@@ -192,44 +192,3 @@ export interface FrondDescriptor {
|
|
|
192
192
|
method?: string;
|
|
193
193
|
}>;
|
|
194
194
|
}
|
|
195
|
-
|
|
196
|
-
/** Result of scanning a project directory. */
|
|
197
|
-
/**
|
|
198
|
-
* Something the scan could NOT do — recorded instead of swallowed.
|
|
199
|
-
*
|
|
200
|
-
* The scan answers with what it found. Until now it answered the same way whether
|
|
201
|
-
* a directory held nothing or could not be read, and whether a handler declared no
|
|
202
|
-
* operation or failed to parse: `catch → empty`. So every downstream reader — the
|
|
203
|
-
* façade, the identity card, anything asking "what does this app serve?" — could
|
|
204
|
-
* not tell **"there is nothing"** from **"I could not look"**.
|
|
205
|
-
*
|
|
206
|
-
* That distinction is what makes a rule about an ABSENCE sound. Without it, a check
|
|
207
|
-
* derived from the scan reports "nothing wrong" precisely when it read nothing.
|
|
208
|
-
*/
|
|
209
|
-
export interface ScanDiagnostic {
|
|
210
|
-
/**
|
|
211
|
-
* `blocking` — the app now serves less than its source declares, and no caller
|
|
212
|
-
* can know it: a handler that failed to parse contributes zero operations.
|
|
213
|
-
* `warning` — something may be missing and the scan cannot decide, e.g. a base
|
|
214
|
-
* class it is not allowed to resolve. Statable in `frond.config.ts`.
|
|
215
|
-
*/
|
|
216
|
-
severity: 'blocking' | 'warning';
|
|
217
|
-
/** Stable rule name — `handler-parse-failed`, `directory-unreadable`. */
|
|
218
|
-
code: string;
|
|
219
|
-
/** Absolute path of what could not be read. */
|
|
220
|
-
filePath: string;
|
|
221
|
-
/** The frond it belongs to, when the scan got far enough to know. */
|
|
222
|
-
frond?: string;
|
|
223
|
-
/** The declaration the diagnostic is about — e.g. `PostHandler.publish`. */
|
|
224
|
-
subject?: string;
|
|
225
|
-
/** What could not be done, and what it costs. One sentence, for a human. */
|
|
226
|
-
message: string;
|
|
227
|
-
/** The underlying failure, kept whole. */
|
|
228
|
-
cause?: unknown;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export interface ScanResult {
|
|
232
|
-
fronds: Fronds;
|
|
233
|
-
/** What the scan could not do. Empty is a claim, not a default — see {@link ScanDiagnostic}. */
|
|
234
|
-
diagnostics: ScanDiagnostic[];
|
|
235
|
-
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
resolveArgs,
|
|
3
|
-
type BindingPlan,
|
|
4
|
-
type CollectorResolver,
|
|
5
|
-
} from '../boot/binding.js';
|
|
6
1
|
import type { InvocationContext } from '../contract/Invocation.js';
|
|
2
|
+
import type { BindingPlan } from '../wire/binding.js';
|
|
3
|
+
|
|
4
|
+
export interface CollectorResolver {
|
|
5
|
+
collect(ctx: InvocationContext): Promise<unknown>;
|
|
6
|
+
}
|
|
7
7
|
|
|
8
8
|
export type CollectorLookup = (typeName: string) => CollectorResolver | undefined;
|
|
9
9
|
|
|
@@ -11,7 +11,53 @@ export type CollectorLookup = (typeName: string) => CollectorResolver | undefine
|
|
|
11
11
|
export class ArgumentResolver {
|
|
12
12
|
constructor(private readonly collectors?: CollectorLookup) {}
|
|
13
13
|
|
|
14
|
-
resolve(plan: BindingPlan,
|
|
15
|
-
|
|
14
|
+
async resolve(plan: BindingPlan, ctx: InvocationContext): Promise<unknown[]> {
|
|
15
|
+
const args: unknown[] = [];
|
|
16
|
+
|
|
17
|
+
for (const binding of plan) {
|
|
18
|
+
switch (binding.source.kind) {
|
|
19
|
+
case 'collector': {
|
|
20
|
+
const collector = this.collectors?.(binding.source.typeName);
|
|
21
|
+
args.push(collector ? await collector.collect(ctx) : undefined);
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
case 'context': {
|
|
25
|
+
args.push(ctx);
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
case 'param': {
|
|
29
|
+
// `null` is a value, not a miss. Nullish coalescing used to make an explicit
|
|
30
|
+
// nullable path/GraphQL argument fall through to query (or become undefined),
|
|
31
|
+
// collapsing `T | null` into `T | undefined`. Only undefined means absent.
|
|
32
|
+
const fromParams = ctx.params[binding.source.name];
|
|
33
|
+
let val: unknown = fromParams === undefined
|
|
34
|
+
? ctx.query[binding.source.name]
|
|
35
|
+
: fromParams;
|
|
36
|
+
if (val != null && binding.source.coerce === 'number') val = Number(val);
|
|
37
|
+
if (val != null && binding.source.coerce === 'boolean') val = val === 'true' || val === '1' || val === true;
|
|
38
|
+
args.push(val);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
case 'fact': {
|
|
42
|
+
// A fact IS the payload — the whole of what happened, never a piece of it.
|
|
43
|
+
// Identical to `body` today, and deliberately not sharing its branch: the two
|
|
44
|
+
// agree by coincidence, not by rule, and the day `body` learns to look up a
|
|
45
|
+
// value by parameter name a subscriber would receive ONE FIELD of the fact it
|
|
46
|
+
// subscribed to. Splitting it costs nothing and removes that trap.
|
|
47
|
+
args.push(ctx.body);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case 'body': {
|
|
51
|
+
args.push(ctx.body);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case 'query': {
|
|
55
|
+
args.push(ctx.query);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return args;
|
|
16
62
|
}
|
|
17
63
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { computeBindingPlan } from '../
|
|
1
|
+
import { computeBindingPlan } from '../wire/binding.js';
|
|
2
2
|
import type { InvocationContext } from '../contract/Invocation.js';
|
|
3
|
-
import type { PresenterEntry } from '../
|
|
3
|
+
import type { PresenterEntry } from '../descriptor/frond.js';
|
|
4
4
|
import type { ArgumentResolver } from './ArgumentResolver.js';
|
|
5
5
|
import type { PresenterArgs } from './PresenterExecutor.js';
|
|
6
6
|
|
package/src/dispatch/Route.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { Call } from '../contract/Call.js';
|
|
2
|
-
import type { RouteAddress } from '../contract/RouteAddress.js';
|
|
3
|
-
|
|
4
|
-
export type RouteKind = 'local' | 'remote' | 'system';
|
|
2
|
+
import type { RouteAddress, RouteKind } from '../contract/RouteAddress.js';
|
|
5
3
|
|
|
6
4
|
/** Executable destination known by the dispatcher. */
|
|
7
5
|
export interface Route {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ValueJudge, type Fields } from '@fougere/schema';
|
|
2
|
-
import { assertListOptions } from '../
|
|
2
|
+
import { assertListOptions } from '../storage.js';
|
|
3
3
|
import { ErrorCode, FougereError } from '../wire/errors.js';
|
|
4
4
|
|
|
5
5
|
interface Writer {
|
|
@@ -8,7 +8,7 @@ interface Writer {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Judges storage writes and list options without narrowing the
|
|
11
|
+
* Judges storage writes and list options without narrowing the storage interface.
|
|
12
12
|
*
|
|
13
13
|
* Storage is a way out like the client surface, so what goes to it is judged too — the
|
|
14
14
|
* same rule `OutputProjector` applies on the other exit.
|
|
@@ -19,12 +19,12 @@ export class StorageGuard {
|
|
|
19
19
|
private readonly entity: string,
|
|
20
20
|
) {}
|
|
21
21
|
|
|
22
|
-
guard<T extends object>(
|
|
23
|
-
const writer =
|
|
24
|
-
if (typeof writer.create !== 'function' || typeof writer.update !== 'function') return
|
|
22
|
+
guard<T extends object>(storage: T): T {
|
|
23
|
+
const writer = storage as unknown as Writer;
|
|
24
|
+
if (typeof writer.create !== 'function' || typeof writer.update !== 'function') return storage;
|
|
25
25
|
|
|
26
26
|
const validation = this;
|
|
27
|
-
const guarded = Object.create(
|
|
27
|
+
const guarded = Object.create(storage) as T & Writer;
|
|
28
28
|
|
|
29
29
|
guarded.create = async function (...args) {
|
|
30
30
|
validation.judge(args[0], 'create');
|
|
@@ -36,7 +36,7 @@ export class StorageGuard {
|
|
|
36
36
|
return writer.update.apply(this, args);
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
const reader =
|
|
39
|
+
const reader = storage as unknown as { list?: (...args: unknown[]) => unknown };
|
|
40
40
|
if (typeof reader.list === 'function') {
|
|
41
41
|
(guarded as unknown as typeof reader).list = async function (...args: unknown[]) {
|
|
42
42
|
assertListOptions(args[0] as object | undefined, validation.entity);
|
|
@@ -7,21 +7,17 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { lowerFirst, type SchemaView } from '@fougere/schema';
|
|
9
9
|
import { statementDrift } from './boot/statement-drift.js';
|
|
10
|
-
import type
|
|
10
|
+
import { computeBindingPlan, type BindingPlan } from './wire/binding.js';
|
|
11
11
|
import { targetOf } from './prefab/prefab.js';
|
|
12
|
-
import type {
|
|
13
|
-
|
|
14
|
-
FrondDescriptor,
|
|
15
|
-
HandlerEntry,
|
|
16
|
-
ScanDiagnostic,
|
|
17
|
-
} from './scan/frond.js';
|
|
12
|
+
import type { CollectorEntry, FrondDescriptor, HandlerEntry } from './descriptor/frond.js';
|
|
13
|
+
import type { ScanDiagnostic } from './scan/result.js';
|
|
18
14
|
import { verify } from './verify.js';
|
|
19
15
|
import {
|
|
20
16
|
inferOperationKind,
|
|
21
|
-
resolveContracts,
|
|
22
17
|
type OperationContract,
|
|
23
18
|
type OperationKind,
|
|
24
|
-
type
|
|
19
|
+
type OperationsMap,
|
|
20
|
+
type TypeRef,
|
|
25
21
|
knownVerbs,
|
|
26
22
|
} from './wire/operation.js';
|
|
27
23
|
|
|
@@ -491,11 +487,11 @@ function validateProvenance(
|
|
|
491
487
|
return valid;
|
|
492
488
|
}
|
|
493
489
|
|
|
494
|
-
function structural(type:
|
|
490
|
+
function structural(type: TypeRef | undefined): boolean {
|
|
495
491
|
return type !== undefined && type.raw.includes('{');
|
|
496
492
|
}
|
|
497
493
|
|
|
498
|
-
function schemaMatches(schema: SchemaView, type:
|
|
494
|
+
function schemaMatches(schema: SchemaView, type: TypeRef | undefined): boolean {
|
|
499
495
|
const names = new Set([schema.name, schema.derivation?.sourceName].filter(Boolean));
|
|
500
496
|
if (type?.name && names.has(type.name)) return true;
|
|
501
497
|
return type?.generics?.some((generic) => generic.name && names.has(generic.name)) ?? false;
|
|
@@ -599,3 +595,59 @@ function uniqueDiagnostics(diagnostics: readonly ScanDiagnostic[]): ScanDiagnost
|
|
|
599
595
|
return true;
|
|
600
596
|
});
|
|
601
597
|
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* The contract of every operation a handler serves — the three producers, merged once.
|
|
601
|
+
*
|
|
602
|
+
* This lived inline in `buildFacade`, which made the façade the only thing that could
|
|
603
|
+
* answer "what is this op's contract, really?". Anything else asking — a checker, a
|
|
604
|
+
* client generator, a descriptor — had to redo the merge, and a second opinion that
|
|
605
|
+
* drifts reports nothing wrong while looking at the wrong thing.
|
|
606
|
+
*
|
|
607
|
+
* The order is a claim about authority, not a convenience:
|
|
608
|
+
*
|
|
609
|
+
* 1. a prefab DECLARES what it built (`Crud(E).__ops`) — runtime, so it survives a
|
|
610
|
+
* scan that resolved nothing;
|
|
611
|
+
* 2. the scan DERIVES from source — a method written in this very file is the
|
|
612
|
+
* author's own word and beats everything; a method it merely READ on a base
|
|
613
|
+
* class is a guess about someone else's code, and yields to that code's own
|
|
614
|
+
* declaration;
|
|
615
|
+
* 3. `frond.config.ts` STATES — the most explicit statement, made by whoever
|
|
616
|
+
* assembles the app, and the only answer for an op inherited from an installed
|
|
617
|
+
* base class the workspace-only scan cannot see. Merged per key, so stating a
|
|
618
|
+
* `binding` alone does not erase an `input` the scan found.
|
|
619
|
+
*
|
|
620
|
+
* Pure: no container, no instance, no disk. The binding plan is resolved here, so
|
|
621
|
+
* nothing downstream ever meets an AST.
|
|
622
|
+
*/
|
|
623
|
+
export function resolveContracts(
|
|
624
|
+
handler: Pick<HandlerEntry, 'ctor' | 'operations'>,
|
|
625
|
+
overrides: FrondDescriptor['operationsOverrides'],
|
|
626
|
+
collectorTypeNames: Set<string>,
|
|
627
|
+
): OperationsMap {
|
|
628
|
+
const declared = (handler.ctor as { __ops?: Record<string, OperationContract> }).__ops ?? {};
|
|
629
|
+
const contracts: OperationsMap = new Map(Object.entries(declared));
|
|
630
|
+
|
|
631
|
+
for (const [opName, scanned] of handler.operations) {
|
|
632
|
+
if (scanned.signature?.inherited && opName in declared) continue;
|
|
633
|
+
contracts.set(opName, {
|
|
634
|
+
...scanned,
|
|
635
|
+
binding: scanned.binding
|
|
636
|
+
?? (scanned.signature ? computeBindingPlan(scanned.signature.params, collectorTypeNames) : undefined),
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
for (const [opName, override] of Object.entries(overrides ?? {})) {
|
|
641
|
+
const { input, output, binding, description } = override;
|
|
642
|
+
if (input === undefined && output === undefined && binding === undefined && description === undefined) continue;
|
|
643
|
+
contracts.set(opName, {
|
|
644
|
+
...contracts.get(opName),
|
|
645
|
+
...(input !== undefined && { input }),
|
|
646
|
+
...(output !== undefined && { output }),
|
|
647
|
+
...(binding !== undefined && { binding }),
|
|
648
|
+
...(description !== undefined && { description }),
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return contracts;
|
|
653
|
+
}
|
package/src/emit.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
import { lowerFirst } from '@fougere/schema';
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* What an emitter injects. Resolved by type, like `
|
|
25
|
+
* What an emitter injects. Resolved by type, like `Storage<Post>` and `Facade<T>`.
|
|
26
26
|
*
|
|
27
27
|
* The returned promise settles when the fact has been **dispatched**, never when it has
|
|
28
28
|
* been handled: a subscriber's failure is its own. The `EventBus` this replaces did
|
package/src/frond-config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import type { EntityConstructor, SchemaView } from '@fougere/schema';
|
|
4
|
-
import type { BindingPlan } from './
|
|
4
|
+
import type { BindingPlan } from './wire/binding.js';
|
|
5
5
|
|
|
6
6
|
// ── Types ────────────────────────────────────────
|
|
7
7
|
|
|
@@ -91,7 +91,7 @@ export interface FrondConfig {
|
|
|
91
91
|
* door on purpose ("it would publish the auth tables to anyone who asks"), and a SQL
|
|
92
92
|
* door at app scope would hand them over; this is what keeps it shut.
|
|
93
93
|
*
|
|
94
|
-
* Declaring it is what makes `
|
|
94
|
+
* Declaring it is what makes `Reads` injectable here. A frond that declares none
|
|
95
95
|
* asks for none, and nothing is attached on its behalf.
|
|
96
96
|
*/
|
|
97
97
|
reads?: string[];
|
package/src/graph.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Role } from '@fougere/schema';
|
|
|
3
3
|
* Entity dependency graph — analyzes ref() links between entities
|
|
4
4
|
* to suggest domain boundaries for splitting fronds.
|
|
5
5
|
*/
|
|
6
|
-
import type { FrondDescriptor } from './
|
|
6
|
+
import type { FrondDescriptor } from './descriptor/frond.js';
|
|
7
7
|
|
|
8
8
|
export interface EntityNode {
|
|
9
9
|
name: string;
|