@fougere/core 0.10.0-alpha.0 → 0.12.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/CallIdentity.d.ts.map +1 -1
- package/dist/CallIdentity.js +2 -0
- package/dist/CallIdentity.js.map +1 -1
- package/dist/Constraint.d.ts +1 -1
- package/dist/Constraint.d.ts.map +1 -1
- package/dist/FougereConfig.d.ts +10 -6
- package/dist/FougereConfig.d.ts.map +1 -1
- package/dist/FougereConfig.js +19 -4
- package/dist/FougereConfig.js.map +1 -1
- package/dist/FrondConfig.d.ts +0 -6
- package/dist/FrondConfig.d.ts.map +1 -1
- package/dist/FrondConfig.js.map +1 -1
- package/dist/FrondDeclaration.d.ts +2 -9
- package/dist/FrondDeclaration.d.ts.map +1 -1
- package/dist/FrondDeclaration.js +0 -1
- package/dist/FrondDeclaration.js.map +1 -1
- package/dist/FrondsStated.d.ts +54 -0
- package/dist/FrondsStated.d.ts.map +1 -0
- package/dist/FrondsStated.js +26 -0
- package/dist/FrondsStated.js.map +1 -0
- package/dist/Source.d.ts.map +1 -1
- package/dist/Source.js +4 -3
- package/dist/Source.js.map +1 -1
- package/dist/StatedFrond.d.ts +21 -0
- package/dist/StatedFrond.d.ts.map +1 -0
- package/dist/StatedFrond.js +34 -0
- package/dist/StatedFrond.js.map +1 -0
- package/dist/StatedModules.d.ts +19 -0
- package/dist/StatedModules.d.ts.map +1 -0
- package/dist/StatedModules.js +37 -0
- package/dist/StatedModules.js.map +1 -0
- package/dist/boot/CreateAppOptions.d.ts +19 -2
- package/dist/boot/CreateAppOptions.d.ts.map +1 -1
- package/dist/boot/Emissions.d.ts +9 -0
- package/dist/boot/Emissions.d.ts.map +1 -1
- package/dist/boot/Emissions.js +11 -5
- package/dist/boot/Emissions.js.map +1 -1
- package/dist/boot/Hosting.d.ts +38 -0
- package/dist/boot/Hosting.d.ts.map +1 -0
- package/dist/boot/Hosting.js +2 -0
- package/dist/boot/Hosting.js.map +1 -0
- package/dist/boot/Peer.d.ts +16 -0
- package/dist/boot/Peer.d.ts.map +1 -0
- package/dist/boot/Peer.js +2 -0
- package/dist/boot/Peer.js.map +1 -0
- package/dist/boot/bootstrap.d.ts.map +1 -1
- package/dist/boot/bootstrap.js +372 -235
- package/dist/boot/bootstrap.js.map +1 -1
- package/dist/boot/declared.d.ts.map +1 -1
- package/dist/boot/declared.js +38 -3
- package/dist/boot/declared.js.map +1 -1
- package/dist/boot/frame.d.ts +0 -4
- package/dist/boot/frame.d.ts.map +1 -1
- package/dist/boot/frame.js +31 -14
- package/dist/boot/frame.js.map +1 -1
- package/dist/boot/hosted.d.ts.map +1 -1
- package/dist/boot/hosted.js +4 -3
- package/dist/boot/hosted.js.map +1 -1
- package/dist/boot/install.d.ts +19 -0
- package/dist/boot/install.d.ts.map +1 -1
- package/dist/boot/install.js +273 -178
- package/dist/boot/install.js.map +1 -1
- package/dist/boot/nesting.d.ts +33 -0
- package/dist/boot/nesting.d.ts.map +1 -0
- package/dist/boot/nesting.js +164 -0
- package/dist/boot/nesting.js.map +1 -0
- package/dist/boot/ownership.d.ts +0 -1
- package/dist/boot/ownership.d.ts.map +1 -1
- package/dist/boot/ownership.js +66 -51
- package/dist/boot/ownership.js.map +1 -1
- package/dist/boot/peerOver.d.ts +4 -0
- package/dist/boot/peerOver.d.ts.map +1 -0
- package/dist/boot/peerOver.js +12 -0
- package/dist/boot/peerOver.js.map +1 -0
- package/dist/boot/ports.js +30 -25
- package/dist/boot/ports.js.map +1 -1
- package/dist/boot/relations.d.ts +54 -0
- package/dist/boot/relations.d.ts.map +1 -0
- package/dist/boot/relations.js +150 -0
- package/dist/boot/relations.js.map +1 -0
- package/dist/boot/remote.d.ts.map +1 -1
- package/dist/boot/remote.js +31 -28
- package/dist/boot/remote.js.map +1 -1
- package/dist/boot/seed.d.ts.map +1 -1
- package/dist/boot/seed.js +27 -20
- package/dist/boot/seed.js.map +1 -1
- package/dist/boot/together.d.ts +3 -0
- package/dist/boot/together.d.ts.map +1 -1
- package/dist/boot/together.js +72 -56
- package/dist/boot/together.js.map +1 -1
- package/dist/contract.d.ts +1 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js.map +1 -1
- package/dist/descriptor/FrondDescriptor.d.ts +7 -0
- package/dist/descriptor/FrondDescriptor.d.ts.map +1 -1
- package/dist/descriptor/MiddlewareEntry.d.ts +6 -5
- package/dist/descriptor/MiddlewareEntry.d.ts.map +1 -1
- package/dist/descriptor/ProviderEntry.d.ts +7 -0
- package/dist/descriptor/ProviderEntry.d.ts.map +1 -1
- package/dist/dispatch/Dependent.d.ts +19 -0
- package/dist/dispatch/Dependent.d.ts.map +1 -0
- package/dist/dispatch/Dependent.js +2 -0
- package/dist/dispatch/Dependent.js.map +1 -0
- package/dist/dispatch/Dispatcher.d.ts +14 -1
- package/dist/dispatch/Dispatcher.d.ts.map +1 -1
- package/dist/dispatch/Dispatcher.js +36 -8
- package/dist/dispatch/Dispatcher.js.map +1 -1
- package/dist/dispatch/HandlerFacade.d.ts +6 -1
- package/dist/dispatch/HandlerFacade.d.ts.map +1 -1
- package/dist/dispatch/HandlerFacade.js +23 -20
- package/dist/dispatch/HandlerFacade.js.map +1 -1
- package/dist/dispatch/Journal.d.ts +25 -0
- package/dist/dispatch/Journal.d.ts.map +1 -0
- package/dist/dispatch/Journal.js +3 -0
- package/dist/dispatch/Journal.js.map +1 -0
- package/dist/dispatch/PresenterExecutor.d.ts +6 -0
- package/dist/dispatch/PresenterExecutor.d.ts.map +1 -1
- package/dist/dispatch/PresenterExecutor.js +30 -22
- package/dist/dispatch/PresenterExecutor.js.map +1 -1
- package/dist/dispatch/RelationCheck.d.ts +22 -0
- package/dist/dispatch/RelationCheck.d.ts.map +1 -0
- package/dist/dispatch/RelationCheck.js +2 -0
- package/dist/dispatch/RelationCheck.js.map +1 -0
- package/dist/dispatch/Release.d.ts +36 -0
- package/dist/dispatch/Release.d.ts.map +1 -0
- package/dist/dispatch/Release.js +130 -0
- package/dist/dispatch/Release.js.map +1 -0
- package/dist/dispatch/StorageGuard.d.ts +39 -1
- package/dist/dispatch/StorageGuard.d.ts.map +1 -1
- package/dist/dispatch/StorageGuard.js +128 -54
- package/dist/dispatch/StorageGuard.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +2 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +2 -1
- package/dist/node.js.map +1 -1
- package/dist/storage/Storage.js +1 -1
- package/dist/storage/Storage.js.map +1 -1
- package/dist/storage/Store.d.ts.map +1 -1
- package/dist/storage/Store.js +29 -21
- package/dist/storage/Store.js.map +1 -1
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +14 -0
- package/dist/verify.js.map +1 -1
- package/dist/wire/Emit.d.ts +9 -0
- package/dist/wire/Emit.d.ts.map +1 -1
- package/dist/wire/Emit.js +16 -0
- package/dist/wire/Emit.js.map +1 -1
- package/dist/wire/Invocation.d.ts +3 -2
- package/dist/wire/Invocation.d.ts.map +1 -1
- package/dist/wire/Invocation.js +5 -2
- package/dist/wire/Invocation.js.map +1 -1
- package/dist/wire/InvocationContext.d.ts +2 -0
- package/dist/wire/InvocationContext.d.ts.map +1 -1
- package/dist/wire/SignedCall.d.ts +2 -0
- package/dist/wire/SignedCall.d.ts.map +1 -1
- package/dist/wire/binding.d.ts.map +1 -1
- package/dist/wire/binding.js +29 -57
- package/dist/wire/binding.js.map +1 -1
- package/dist/wire/drift.d.ts.map +1 -1
- package/dist/wire/drift.js +22 -19
- package/dist/wire/drift.js.map +1 -1
- package/package.json +4 -4
- package/src/CallIdentity.ts +2 -0
- package/src/Constraint.ts +1 -1
- package/src/FougereConfig.ts +24 -10
- package/src/FrondConfig.ts +0 -6
- package/src/FrondDeclaration.ts +2 -7
- package/src/FrondsStated.ts +72 -0
- package/src/Source.ts +3 -2
- package/src/StatedFrond.ts +51 -0
- package/src/StatedModules.ts +58 -0
- package/src/boot/CreateAppOptions.ts +19 -2
- package/src/boot/Emissions.ts +16 -5
- package/src/boot/Hosting.ts +38 -0
- package/src/boot/Peer.ts +15 -0
- package/src/boot/bootstrap.ts +512 -271
- package/src/boot/declared.ts +38 -3
- package/src/boot/frame.ts +42 -11
- package/src/boot/hosted.ts +4 -3
- package/src/boot/install.ts +400 -203
- package/src/boot/nesting.ts +196 -0
- package/src/boot/ownership.ts +79 -51
- package/src/boot/peerOver.ts +17 -0
- package/src/boot/ports.ts +45 -31
- package/src/boot/relations.ts +172 -0
- package/src/boot/remote.ts +47 -29
- package/src/boot/seed.ts +36 -27
- package/src/boot/together.ts +102 -63
- package/src/contract.ts +1 -0
- package/src/descriptor/FrondDescriptor.ts +7 -0
- package/src/descriptor/MiddlewareEntry.ts +6 -5
- package/src/descriptor/ProviderEntry.ts +7 -0
- package/src/dispatch/Dependent.ts +19 -0
- package/src/dispatch/Dispatcher.ts +43 -7
- package/src/dispatch/HandlerFacade.ts +27 -17
- package/src/dispatch/Journal.ts +26 -0
- package/src/dispatch/PresenterExecutor.ts +33 -22
- package/src/dispatch/RelationCheck.ts +21 -0
- package/src/dispatch/Release.ts +180 -0
- package/src/dispatch/StorageGuard.ts +138 -55
- package/src/index.ts +8 -0
- package/src/node.ts +2 -1
- package/src/storage/Storage.ts +2 -2
- package/src/storage/Store.ts +39 -18
- package/src/verify.ts +15 -0
- package/src/wire/Emit.ts +23 -0
- package/src/wire/Invocation.ts +5 -2
- package/src/wire/InvocationContext.ts +2 -0
- package/src/wire/SignedCall.ts +2 -0
- package/src/wire/binding.ts +26 -58
- package/src/wire/drift.ts +48 -21
package/src/storage/Store.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { comparisonOf, comparisonsIn, type Comparison } from './Comparison.js';
|
|
|
3
3
|
import type { Storage } from './Storage.js';
|
|
4
4
|
import type { StorageFactory } from './StorageFactory.js';
|
|
5
5
|
import type { Values } from './Values.js';
|
|
6
|
+
import type { ListResult } from './ListResult.js';
|
|
6
7
|
|
|
7
8
|
/** Instances addressed by key — what an adapter supplies, and all of it. */
|
|
8
9
|
export interface Store {
|
|
@@ -68,24 +69,8 @@ export function storageOver(open: (entity: SchemaView, name: string) => Store):
|
|
|
68
69
|
let items = await store.all();
|
|
69
70
|
if (options?.where) items = items.filter((values) => matches(values, options.where));
|
|
70
71
|
if (options?.orderBy) items = sorted(items, options.orderBy, options.order);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// answered a different question — everything the store holds, including the ones
|
|
74
|
-
// the filter exists to keep out of this caller's sight.
|
|
75
|
-
const matching = items.length;
|
|
76
|
-
const limit = options?.limit;
|
|
77
|
-
const offset = options?.page && limit ? (options.page - 1) * limit : options?.offset ?? 0;
|
|
78
|
-
if (offset > 0) items = items.slice(offset);
|
|
79
|
-
const hasMore = limit ? items.length > limit : false;
|
|
80
|
-
if (limit) items = items.slice(0, limit);
|
|
81
|
-
// The cursor is read before the scope cuts: a view that drops the key still
|
|
82
|
-
// pages, the way it does over SQL.
|
|
83
|
-
const endCursor = items.length > 0 ? String((items[items.length - 1] as any)[pk] ?? '') : undefined;
|
|
84
|
-
const result = items.map(pick) as any;
|
|
85
|
-
result.hasMore = hasMore;
|
|
86
|
-
result.endCursor = endCursor;
|
|
87
|
-
if (options?.count) result.total = matching;
|
|
88
|
-
return result;
|
|
72
|
+
|
|
73
|
+
return paged(items, options, pk, pick);
|
|
89
74
|
},
|
|
90
75
|
async findById(id: string) {
|
|
91
76
|
const values = await store.get(keyOf(id));
|
|
@@ -206,3 +191,39 @@ const rank = (left: unknown, right: unknown): number => {
|
|
|
206
191
|
|
|
207
192
|
return held === against ? 0 : held < against ? -1 : 1;
|
|
208
193
|
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The page cut out of what matched.
|
|
197
|
+
*
|
|
198
|
+
* `total` is held BEFORE the cut and after the filter — it answers "how many match", which is
|
|
199
|
+
* what a paginator divides. Reading the store's size at the end answered a different question:
|
|
200
|
+
* everything it holds, including the rows the filter exists to keep out of this caller's sight.
|
|
201
|
+
*/
|
|
202
|
+
function paged(
|
|
203
|
+
matched: Values[],
|
|
204
|
+
options: { limit?: number; page?: number; offset?: number; count?: boolean } | undefined,
|
|
205
|
+
pk: string,
|
|
206
|
+
pick: (values: Values) => Values,
|
|
207
|
+
): ListResult<Values> {
|
|
208
|
+
const matching = matched.length;
|
|
209
|
+
const limit = options?.limit;
|
|
210
|
+
const offset = options?.page && limit ? (options.page - 1) * limit : options?.offset ?? 0;
|
|
211
|
+
|
|
212
|
+
let items = offset > 0 ? matched.slice(offset) : matched;
|
|
213
|
+
const hasMore = limit ? items.length > limit : false;
|
|
214
|
+
if (limit) items = items.slice(0, limit);
|
|
215
|
+
|
|
216
|
+
// The cursor is read before the scope cuts: a view that drops the key still pages, the way it
|
|
217
|
+
// does over SQL.
|
|
218
|
+
const endCursor = items.length > 0
|
|
219
|
+
? String((items[items.length - 1] as Record<string, unknown>)[pk] ?? '')
|
|
220
|
+
: undefined;
|
|
221
|
+
|
|
222
|
+
// An array that carries the page's terms on itself — what `ListResult` is.
|
|
223
|
+
const result = items.map(pick) as ListResult<Values>;
|
|
224
|
+
result.hasMore = hasMore;
|
|
225
|
+
result.endCursor = endCursor;
|
|
226
|
+
if (options?.count) result.total = matching;
|
|
227
|
+
|
|
228
|
+
return result;
|
|
229
|
+
}
|
package/src/verify.ts
CHANGED
|
@@ -17,6 +17,16 @@ export type Misplaced = Diagnostic & {
|
|
|
17
17
|
/** A dependency declared in a frond's scope, and what kind of thing it is. */
|
|
18
18
|
type Registration = { frond: string; kind: string };
|
|
19
19
|
|
|
20
|
+
/** Everything above a frond in the tree — what its scope reaches by walking up. */
|
|
21
|
+
function ancestors(frond: FrondDescriptor, byName: Map<string, FrondDescriptor>): Set<string> {
|
|
22
|
+
const above = new Set<string>();
|
|
23
|
+
for (let at = frond.extends; at !== undefined && !above.has(at); at = byName.get(at)?.extends) {
|
|
24
|
+
above.add(at);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return above;
|
|
28
|
+
}
|
|
29
|
+
|
|
20
30
|
/**
|
|
21
31
|
* The container keys a frond registers in its own scope, keyed as a handler's `deps` spell them —
|
|
22
32
|
* DI resolves by type name, so both sides are PascalCase.
|
|
@@ -50,6 +60,7 @@ function injectablesOf(frond: FrondDescriptor) {
|
|
|
50
60
|
*/
|
|
51
61
|
export function verify(app: { fronds: readonly FrondDescriptor[] }): Misplaced[] {
|
|
52
62
|
const index = new Map<string, Registration>();
|
|
63
|
+
const byName = new Map(app.fronds.map((frond) => [frond.name, frond]));
|
|
53
64
|
for (const frond of app.fronds) {
|
|
54
65
|
for (const [key, reg] of registrationsOf(frond)) index.set(key, reg);
|
|
55
66
|
}
|
|
@@ -76,6 +87,10 @@ export function verify(app: { fronds: readonly FrondDescriptor[] }): Misplaced[]
|
|
|
76
87
|
// façade key, or an unresolved name. None is a boundary crossing, and
|
|
77
88
|
// an unresolved dependency is the container's complaint, not this rule's.
|
|
78
89
|
if (!declared || declared.frond === frond.name) continue;
|
|
90
|
+
// An ANCESTOR is not across: the frond tree says this one resolves what that one
|
|
91
|
+
// declared, and its scope hangs off it. A sibling, a descendant or a stranger stays
|
|
92
|
+
// refused — inheriting code is not calling a frond.
|
|
93
|
+
if (ancestors(frond, byName).has(declared.frond)) continue;
|
|
79
94
|
violations.push({
|
|
80
95
|
code: 'cross-frond-dependency',
|
|
81
96
|
severity: 'warning',
|
package/src/wire/Emit.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { lowerFirst } from '@fougere/schema';
|
|
2
|
+
import { computeBindingPlan } from './binding.js';
|
|
3
|
+
import type { OperationContract } from './OperationContract.js';
|
|
4
|
+
import type { OperationsMap } from './OperationsMap.js';
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* What an emitter injects. PARTIAL, because announcing is what REALIZES the fact's
|
|
@@ -52,6 +55,26 @@ export function factsAwaitedBy(handlers: readonly { deps: readonly string[] }[])
|
|
|
52
55
|
return factsIn(handlers, factOfAwaitKey);
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
/**
|
|
59
|
+
* What a set of handlers LISTENS to — the dual of what it announces, and the half no
|
|
60
|
+
* dependency spells: accepting a `Fact<T>` IS the subscription, so it is read from the
|
|
61
|
+
* binding plan, where `Emissions` reads it too.
|
|
62
|
+
*/
|
|
63
|
+
export function factsListenedTo(handlers: readonly { operations: OperationsMap }[]): string[] {
|
|
64
|
+
const taken = (contract: OperationContract): string[] => {
|
|
65
|
+
const plan = contract.binding
|
|
66
|
+
?? (contract.signature ? computeBindingPlan(contract.signature.params, NO_COLLECTOR) : []);
|
|
67
|
+
|
|
68
|
+
return plan.flatMap((bound) =>
|
|
69
|
+
bound.source.kind === 'fact' || bound.source.kind === 'pipe' ? [bound.source.factName] : []);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return [...new Set(handlers.flatMap((handler) => [...handler.operations.values()].flatMap(taken)))];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** `Fact` and `Pipe` name themselves, and the plan reads them before it reads a collector. */
|
|
76
|
+
const NO_COLLECTOR = new Set<string>();
|
|
77
|
+
|
|
55
78
|
function factsIn(
|
|
56
79
|
handlers: readonly { deps: readonly string[] }[],
|
|
57
80
|
behindKey: (key: string) => string | undefined,
|
package/src/wire/Invocation.ts
CHANGED
|
@@ -24,6 +24,8 @@ function canonicalRecord(value: unknown): Record<string, unknown> {
|
|
|
24
24
|
|
|
25
25
|
/** Canonical invocation shared by every entry and transport. */
|
|
26
26
|
export class Invocation implements InvocationContext {
|
|
27
|
+
static readonly empty = Invocation.from();
|
|
28
|
+
|
|
27
29
|
readonly params: Record<string, unknown>;
|
|
28
30
|
readonly query: Record<string, unknown>;
|
|
29
31
|
readonly input: unknown;
|
|
@@ -31,8 +33,9 @@ export class Invocation implements InvocationContext {
|
|
|
31
33
|
readonly trace?: string;
|
|
32
34
|
readonly identity?: string;
|
|
33
35
|
readonly caller?: string;
|
|
36
|
+
readonly runAt?: number;
|
|
34
37
|
|
|
35
|
-
/** Gives every entry and every transport the same
|
|
38
|
+
/** Gives every entry and every transport the same eight-member value, frozen. */
|
|
36
39
|
private constructor(context: PartialInvocation) {
|
|
37
40
|
this.params = canonicalRecord(context.params);
|
|
38
41
|
this.query = canonicalRecord(context.query);
|
|
@@ -43,6 +46,7 @@ export class Invocation implements InvocationContext {
|
|
|
43
46
|
if (context.trace !== undefined) this.trace = context.trace;
|
|
44
47
|
if (context.identity !== undefined) this.identity = context.identity;
|
|
45
48
|
if (context.caller !== undefined) this.caller = context.caller;
|
|
49
|
+
if (context.runAt !== undefined) this.runAt = context.runAt;
|
|
46
50
|
Object.freeze(this);
|
|
47
51
|
}
|
|
48
52
|
|
|
@@ -51,7 +55,6 @@ export class Invocation implements InvocationContext {
|
|
|
51
55
|
return context instanceof Invocation ? context : new Invocation(context ?? {});
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
static readonly empty = Invocation.from();
|
|
55
58
|
|
|
56
59
|
/** Replaces the input — how the façade hands on the value it parsed. */
|
|
57
60
|
withInput(input: unknown): Invocation {
|
package/src/wire/SignedCall.ts
CHANGED
package/src/wire/binding.ts
CHANGED
|
@@ -48,66 +48,34 @@ export function computeBindingPlan(
|
|
|
48
48
|
params: Param[],
|
|
49
49
|
collectorTypeNames: Set<string>,
|
|
50
50
|
): BindingPlan {
|
|
51
|
-
return params.map((param) => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// 0. Fact, or the same fact before it is final. Both name themselves, so nothing has
|
|
59
|
-
// to be known in advance, and they come FIRST because branch 4 would otherwise hand
|
|
60
|
-
// either the caller's input under the name of something that happened.
|
|
61
|
-
const announced = param.type.name === 'Fact' || param.type.name === 'Pipe'
|
|
62
|
-
? param.type.generics?.[0]?.name
|
|
63
|
-
: undefined;
|
|
64
|
-
if (announced) {
|
|
65
|
-
return {
|
|
66
|
-
name: param.name,
|
|
67
|
-
source: {
|
|
68
|
-
kind: (param.type.name === 'Pipe' ? 'pipe' : 'fact') as 'pipe' | 'fact',
|
|
69
|
-
factName: lowerFirst(announced),
|
|
70
|
-
},
|
|
71
|
-
optional: param.optional ?? false,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
51
|
+
return params.map((param) => ({
|
|
52
|
+
name: param.name,
|
|
53
|
+
source: sourceOf(param, collectorTypeNames),
|
|
54
|
+
optional: param.optional ?? false,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
74
57
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Where ONE parameter gets its value, in the order the branches must be asked.
|
|
60
|
+
*
|
|
61
|
+
* A fact and a link name themselves, so they come FIRST: asked later, the last branch would hand
|
|
62
|
+
* the caller's input under the name of something that happened.
|
|
63
|
+
*/
|
|
64
|
+
function sourceOf(param: Param, collectorTypeNames: Set<string>): ParamSource {
|
|
65
|
+
const typeName = param.type.name;
|
|
66
|
+
// `lowerFirst`, never `toLowerCase()`: the collector set is keyed the way the scan spells it,
|
|
67
|
+
// and the two agree on one word only — `AuthorUser` looked up as `authoruser` missed
|
|
68
|
+
// `authorUser` and fell through to the request input.
|
|
69
|
+
const typeKey = lowerFirst(typeName);
|
|
83
70
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
source: { kind: 'context' as const },
|
|
89
|
-
optional: param.optional ?? false,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
71
|
+
const announced = typeName === 'Fact' || typeName === 'Pipe' ? param.type.generics?.[0]?.name : undefined;
|
|
72
|
+
if (announced) {
|
|
73
|
+
return { kind: typeName === 'Pipe' ? 'pipe' : 'fact', factName: lowerFirst(announced) };
|
|
74
|
+
}
|
|
92
75
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
name: param.name,
|
|
97
|
-
source: {
|
|
98
|
-
kind: 'param' as const,
|
|
99
|
-
name: param.name,
|
|
100
|
-
coerce: coercionFor(typeName),
|
|
101
|
-
},
|
|
102
|
-
optional: param.optional ?? false,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
76
|
+
if (collectorTypeNames.has(typeKey)) return { kind: 'collector', typeName: typeKey };
|
|
77
|
+
if (typeName === 'InvocationContext') return { kind: 'context' };
|
|
78
|
+
if (PRIMITIVES.has(typeName)) return { kind: 'param', name: param.name, coerce: coercionFor(typeName) };
|
|
105
79
|
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
name: param.name,
|
|
109
|
-
source: { kind: 'input' as const },
|
|
110
|
-
optional: param.optional ?? false,
|
|
111
|
-
};
|
|
112
|
-
});
|
|
80
|
+
return { kind: 'input' };
|
|
113
81
|
}
|
package/src/wire/drift.ts
CHANGED
|
@@ -15,8 +15,14 @@ export interface CardDrift {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/** Every facade of a card, by name. */
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
/** What a door of that card answers, and the shape of the row it hands back. */
|
|
19
|
+
interface Answering {
|
|
20
|
+
ops: Set<string>;
|
|
21
|
+
schema?: SchemaDescriptor;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function facadesOf(card: IdentityCard, frond: string): Map<string, Answering> {
|
|
25
|
+
const found = new Map<string, Answering>();
|
|
20
26
|
for (const one of card.fronds) {
|
|
21
27
|
if (one.name !== frond) continue;
|
|
22
28
|
for (const facade of one.facades) {
|
|
@@ -37,37 +43,58 @@ function factsOf(card: IdentityCard, frond: string): Map<string, SchemaDescripto
|
|
|
37
43
|
|
|
38
44
|
/** What a consumer's synced copy no longer matches in what the producer serves. */
|
|
39
45
|
export function driftOf(mine: IdentityCard, theirs: IdentityCard, frond: string): CardDrift {
|
|
40
|
-
const mineFacades = facadesOf(mine, frond);
|
|
41
|
-
const served = facadesOf(theirs, frond);
|
|
42
46
|
const drift: CardDrift = { frond, missingDoors: [], missingOps: [], shapes: [], facts: [] };
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
driftInDoors(facadesOf(mine, frond), facadesOf(theirs, frond), drift);
|
|
49
|
+
driftInFacts(factsOf(mine, frond), factsOf(theirs, frond), drift);
|
|
50
|
+
|
|
51
|
+
return drift;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A door that is gone, an operation it no longer answers, or a row that changed shape. */
|
|
55
|
+
function driftInDoors(
|
|
56
|
+
mine: Map<string, Answering>,
|
|
57
|
+
theirs: Map<string, Answering>,
|
|
58
|
+
drift: CardDrift,
|
|
59
|
+
): void {
|
|
60
|
+
for (const [name, facade] of mine) {
|
|
61
|
+
const there = theirs.get(name);
|
|
62
|
+
if (!there) {
|
|
63
|
+
drift.missingDoors.push(name);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
47
66
|
|
|
48
67
|
const missing = [...facade.ops].filter((op) => !there.ops.has(op));
|
|
49
68
|
if (missing.length > 0) drift.missingOps.push({ facade: name, ops: missing.sort() });
|
|
50
69
|
|
|
51
|
-
if (facade.schema
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
70
|
+
if (!facade.schema || !there.schema) continue;
|
|
71
|
+
|
|
72
|
+
// `Card.diff` never guesses a rename — a field gone plus a field appeared lands in
|
|
73
|
+
// `ambiguous`, and only a declaration settles it. Here nobody can declare one, so the
|
|
74
|
+
// pair is reported as it is and a human reads it.
|
|
75
|
+
const moved = Card.fromDescriptor(facade.schema).diff(Card.fromDescriptor(there.schema));
|
|
76
|
+
if (moved.changes.length > 0) drift.shapes.push({ facade: name, changes: moved.changes });
|
|
58
77
|
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A fact nobody announces any more, or one whose payload moved under its readers. */
|
|
81
|
+
function driftInFacts(
|
|
82
|
+
mine: Map<string, SchemaDescriptor | undefined>,
|
|
83
|
+
theirs: Map<string, SchemaDescriptor | undefined>,
|
|
84
|
+
drift: CardDrift,
|
|
85
|
+
): void {
|
|
86
|
+
for (const [name, shape] of mine) {
|
|
87
|
+
if (!theirs.has(name)) {
|
|
88
|
+
drift.facts.push({ fact: name, changes: 'gone' });
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
59
91
|
|
|
60
|
-
|
|
61
|
-
const servedFacts = factsOf(theirs, frond);
|
|
62
|
-
for (const [name, shape] of heldFacts) {
|
|
63
|
-
if (!servedFacts.has(name)) { drift.facts.push({ fact: name, changes: 'gone' }); continue; }
|
|
64
|
-
const there = servedFacts.get(name);
|
|
92
|
+
const there = theirs.get(name);
|
|
65
93
|
if (!shape || !there) continue;
|
|
94
|
+
|
|
66
95
|
const moved = Card.fromDescriptor(shape).diff(Card.fromDescriptor(there));
|
|
67
96
|
if (moved.changes.length > 0) drift.facts.push({ fact: name, changes: moved.changes });
|
|
68
97
|
}
|
|
69
|
-
|
|
70
|
-
return drift;
|
|
71
98
|
}
|
|
72
99
|
|
|
73
100
|
/** Whether anything at all separates the two cards. */
|