@fougere/core 0.16.8-alpha.0 → 0.17.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/README.md +1 -1
- package/dist/CallIdentity.js +2 -2
- package/dist/CallIdentity.js.map +1 -1
- package/dist/Declared.d.ts +2 -0
- package/dist/Declared.d.ts.map +1 -1
- package/dist/FrondDeclaration.d.ts.map +1 -1
- package/dist/FrondDeclaration.js +1 -0
- package/dist/FrondDeclaration.js.map +1 -1
- package/dist/boot/App.d.ts +4 -4
- package/dist/boot/App.d.ts.map +1 -1
- package/dist/boot/AppLifecycle.d.ts +2 -2
- package/dist/boot/AppLifecycle.js +3 -3
- package/dist/boot/AppLifecycle.js.map +1 -1
- package/dist/boot/bootstrap.d.ts.map +1 -1
- package/dist/boot/bootstrap.js +27 -28
- package/dist/boot/bootstrap.js.map +1 -1
- package/dist/boot/card.d.ts.map +1 -1
- package/dist/boot/card.js +30 -29
- package/dist/boot/card.js.map +1 -1
- package/dist/boot/install.d.ts +1 -1
- package/dist/boot/install.d.ts.map +1 -1
- package/dist/boot/install.js +15 -7
- package/dist/boot/install.js.map +1 -1
- package/dist/boot/peerOver.js +2 -2
- package/dist/boot/peerOver.js.map +1 -1
- package/dist/boot/ports.d.ts +2 -0
- package/dist/boot/ports.d.ts.map +1 -1
- package/dist/boot/ports.js +2 -0
- package/dist/boot/ports.js.map +1 -1
- package/dist/boot/remote.d.ts +7 -5
- package/dist/boot/remote.d.ts.map +1 -1
- package/dist/boot/remote.js +42 -29
- package/dist/boot/remote.js.map +1 -1
- package/dist/contract.d.ts +2 -2
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +3 -3
- package/dist/contract.js.map +1 -1
- package/dist/descriptor/ProviderEntry.d.ts +5 -0
- package/dist/descriptor/ProviderEntry.d.ts.map +1 -1
- package/dist/descriptor/bases.d.ts +7 -1
- package/dist/descriptor/bases.d.ts.map +1 -1
- package/dist/descriptor/bases.js +21 -5
- package/dist/descriptor/bases.js.map +1 -1
- package/dist/dispatch/ArgumentResolver.d.ts +7 -0
- package/dist/dispatch/ArgumentResolver.d.ts.map +1 -1
- package/dist/dispatch/ArgumentResolver.js +45 -12
- package/dist/dispatch/ArgumentResolver.js.map +1 -1
- package/dist/dispatch/Dispatcher.js +1 -1
- package/dist/dispatch/Dispatcher.js.map +1 -1
- package/dist/dispatch/HandlerFacade.d.ts.map +1 -1
- package/dist/dispatch/HandlerFacade.js +9 -8
- package/dist/dispatch/HandlerFacade.js.map +1 -1
- package/dist/dispatch/InFlight.d.ts +1 -1
- package/dist/dispatch/InFlight.d.ts.map +1 -1
- package/dist/dispatch/InFlight.js +2 -2
- package/dist/dispatch/InFlight.js.map +1 -1
- package/dist/dispatch/LocalRoutePolicy.d.ts.map +1 -1
- package/dist/dispatch/LocalRoutePolicy.js +7 -7
- package/dist/dispatch/LocalRoutePolicy.js.map +1 -1
- package/dist/dispatch/PresenterExecutor.d.ts +1 -3
- package/dist/dispatch/PresenterExecutor.d.ts.map +1 -1
- package/dist/dispatch/PresenterExecutor.js +1 -7
- package/dist/dispatch/PresenterExecutor.js.map +1 -1
- package/dist/dispatch/Release.js +0 -2
- package/dist/dispatch/Release.js.map +1 -1
- package/dist/dispatch/RouteRegistry.d.ts +1 -1
- package/dist/dispatch/RouteRegistry.d.ts.map +1 -1
- package/dist/dispatch/RouteRegistry.js +3 -3
- package/dist/dispatch/RouteRegistry.js.map +1 -1
- package/dist/dispatch/StorageGuard.d.ts.map +1 -1
- package/dist/dispatch/StorageGuard.js +3 -9
- package/dist/dispatch/StorageGuard.js.map +1 -1
- package/dist/dispatch/remoteRoutes.d.ts +1 -1
- package/dist/dispatch/remoteRoutes.d.ts.map +1 -1
- package/dist/dispatch/remoteRoutes.js +1 -1
- package/dist/dispatch/remoteRoutes.js.map +1 -1
- package/dist/dispatch/routeNotFound.d.ts.map +1 -1
- package/dist/dispatch/routeNotFound.js +6 -5
- package/dist/dispatch/routeNotFound.js.map +1 -1
- package/dist/dispatch/validateInput.d.ts +1 -1
- package/dist/dispatch/validateInput.d.ts.map +1 -1
- package/dist/dispatch/validateInput.js +1 -3
- package/dist/dispatch/validateInput.js.map +1 -1
- package/dist/entry/facade.d.ts +1 -1
- package/dist/entry/facade.d.ts.map +1 -1
- package/dist/entry/facade.js +2 -2
- package/dist/entry/facade.js.map +1 -1
- package/dist/entry/transport.js +1 -1
- package/dist/entry/transport.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/wire/CallRecord.d.ts +1 -1
- package/dist/wire/CallRecord.d.ts.map +1 -1
- package/dist/wire/FougereError.d.ts +8 -3
- package/dist/wire/FougereError.d.ts.map +1 -1
- package/dist/wire/FougereError.js +13 -4
- package/dist/wire/FougereError.js.map +1 -1
- package/dist/wire/FougereErrorOptions.d.ts +1 -1
- package/dist/wire/FougereErrorOptions.d.ts.map +1 -1
- package/dist/wire/FrondCall.d.ts +2 -2
- package/dist/wire/FrondCall.d.ts.map +1 -1
- package/dist/wire/OperationContext.d.ts +2 -2
- package/dist/wire/OperationContext.d.ts.map +1 -1
- package/dist/wire/RouteAddress.d.ts +1 -1
- package/dist/wire/RouteAddress.d.ts.map +1 -1
- package/dist/wire/RouteAddress.js +5 -5
- package/dist/wire/RouteAddress.js.map +1 -1
- package/dist/wire/RouteAddressInput.d.ts +1 -1
- package/dist/wire/RouteAddressInput.d.ts.map +1 -1
- package/dist/wire/RpcAnswer.d.ts +1 -1
- package/dist/wire/RpcAnswer.d.ts.map +1 -1
- package/dist/wire/RpcAnswer.js +1 -1
- package/dist/wire/RpcAnswer.js.map +1 -1
- package/dist/wire/SignedCall.d.ts +1 -1
- package/dist/wire/SignedCall.d.ts.map +1 -1
- package/dist/wire/StateShape.d.ts +1 -1
- package/dist/wire/StateShape.d.ts.map +1 -1
- package/dist/wire/StateShape.js +2 -2
- package/dist/wire/StateShape.js.map +1 -1
- package/dist/wire/card/IdentityCard.d.ts +10 -3
- package/dist/wire/card/IdentityCard.d.ts.map +1 -1
- package/dist/wire/card/IdentityCard.js +3 -1
- package/dist/wire/card/IdentityCard.js.map +1 -1
- package/dist/wire/drift.d.ts +2 -1
- package/dist/wire/drift.d.ts.map +1 -1
- package/dist/wire/drift.js +19 -16
- package/dist/wire/drift.js.map +1 -1
- package/dist/wire/http-error.js +2 -2
- package/dist/wire/http-error.js.map +1 -1
- package/package.json +3 -3
- package/src/CallIdentity.ts +2 -2
- package/src/Declared.ts +2 -0
- package/src/FrondDeclaration.ts +1 -0
- package/src/boot/App.ts +4 -4
- package/src/boot/AppLifecycle.ts +3 -3
- package/src/boot/bootstrap.ts +29 -30
- package/src/boot/card.ts +38 -29
- package/src/boot/install.ts +14 -8
- package/src/boot/peerOver.ts +2 -2
- package/src/boot/ports.ts +3 -0
- package/src/boot/remote.ts +52 -36
- package/src/contract.ts +3 -3
- package/src/descriptor/ProviderEntry.ts +5 -0
- package/src/descriptor/bases.ts +21 -5
- package/src/dispatch/ArgumentResolver.ts +49 -10
- package/src/dispatch/Dispatcher.ts +1 -1
- package/src/dispatch/HandlerFacade.ts +9 -10
- package/src/dispatch/InFlight.ts +2 -2
- package/src/dispatch/LocalRoutePolicy.ts +7 -7
- package/src/dispatch/PresenterExecutor.ts +0 -4
- package/src/dispatch/Release.ts +0 -2
- package/src/dispatch/RouteRegistry.ts +3 -3
- package/src/dispatch/StorageGuard.ts +3 -9
- package/src/dispatch/remoteRoutes.ts +2 -2
- package/src/dispatch/routeNotFound.ts +6 -5
- package/src/dispatch/validateInput.ts +0 -4
- package/src/entry/facade.ts +2 -2
- package/src/entry/transport.ts +1 -1
- package/src/index.ts +1 -1
- package/src/wire/CallRecord.ts +1 -1
- package/src/wire/FougereError.ts +16 -5
- package/src/wire/FougereErrorOptions.ts +1 -1
- package/src/wire/FrondCall.ts +2 -2
- package/src/wire/OperationContext.ts +2 -2
- package/src/wire/RouteAddress.ts +5 -5
- package/src/wire/RouteAddressInput.ts +1 -1
- package/src/wire/RpcAnswer.ts +1 -1
- package/src/wire/SignedCall.ts +1 -1
- package/src/wire/StateShape.ts +2 -2
- package/src/wire/card/IdentityCard.ts +9 -7
- package/src/wire/drift.ts +22 -27
- package/src/wire/http-error.ts +2 -2
package/src/entry/transport.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { Transport } from '../wire/Transport.js';
|
|
|
7
7
|
export function createTransportEntry(dispatcher: DispatchPort, surface?: string): Transport {
|
|
8
8
|
return (legacy, invocation) => dispatcher.dispatch(new Call(
|
|
9
9
|
new RouteAddress({
|
|
10
|
-
|
|
10
|
+
address: legacy.address,
|
|
11
11
|
operation: legacy.op,
|
|
12
12
|
...(surface !== undefined ? { surface } : {}),
|
|
13
13
|
}),
|
package/src/index.ts
CHANGED
|
@@ -62,7 +62,7 @@ export { FougereError, ErrorCode, validationErrorsOf } from './contract.js';
|
|
|
62
62
|
export type { AppMiddleware } from './wire/AppMiddleware.js';
|
|
63
63
|
export type { AppNext } from './wire/AppNext.js';
|
|
64
64
|
export type { OperationContext } from './wire/OperationContext.js';
|
|
65
|
-
export { assertIdentityCard,
|
|
65
|
+
export { assertIdentityCard, RPC_ADDRESS, ENVELOPE_BYTES, maxBodyBytes, maxFrameBytes, setMaxBodyBytes } from './contract.js';
|
|
66
66
|
export { createLocalRunner, createAppRunner } from './boot/runner.js';
|
|
67
67
|
|
|
68
68
|
// A frond an app STATES, for a boot that will not scan. No disk, no `typescript` — which
|
package/src/wire/CallRecord.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface CallRecord {
|
|
|
5
5
|
/** Monotonic, and the whole cursor protocol: a reader asks for what is above its own. */
|
|
6
6
|
seq: number;
|
|
7
7
|
frond?: string;
|
|
8
|
-
|
|
8
|
+
address: string;
|
|
9
9
|
operation: string;
|
|
10
10
|
surface?: string;
|
|
11
11
|
/** Known at `resolved`, so absent on a call that never found a route. */
|
package/src/wire/FougereError.ts
CHANGED
|
@@ -14,24 +14,35 @@ import type { FougereErrorOptions } from './FougereErrorOptions.js';
|
|
|
14
14
|
*/
|
|
15
15
|
export class FougereError<Code extends ErrorCode = ErrorCode> extends Error {
|
|
16
16
|
readonly code: Code;
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
address?: string;
|
|
18
|
+
operation?: string;
|
|
19
19
|
readonly details?: unknown;
|
|
20
20
|
|
|
21
21
|
constructor(options: FougereErrorOptions<Code>) {
|
|
22
22
|
super(options.message, { cause: options.cause });
|
|
23
23
|
this.name = 'FougereError';
|
|
24
24
|
this.code = options.code;
|
|
25
|
-
this.
|
|
25
|
+
this.address = options.address;
|
|
26
26
|
this.operation = options.operation;
|
|
27
27
|
this.details = options.details;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Where the call was refused. The facade a refusal crosses first is where it happened, so a
|
|
32
|
+
* facade further out leaves what a deeper one wrote.
|
|
33
|
+
*/
|
|
34
|
+
at(address: string, operation: string): this {
|
|
35
|
+
this.address ??= address;
|
|
36
|
+
this.operation ??= operation;
|
|
37
|
+
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
toJSON() {
|
|
31
42
|
return {
|
|
32
43
|
code: this.code,
|
|
33
44
|
message: this.message,
|
|
34
|
-
...(this.
|
|
45
|
+
...(this.address && { address: this.address }),
|
|
35
46
|
...(this.operation && { operation: this.operation }),
|
|
36
47
|
...(this.details !== undefined && { details: this.details }),
|
|
37
48
|
};
|
|
@@ -45,7 +56,7 @@ export class FougereError<Code extends ErrorCode = ErrorCode> extends Error {
|
|
|
45
56
|
return new FougereError({
|
|
46
57
|
code: known ? (raw.code as ErrorCode) : ErrorCode.INTERNAL_ERROR,
|
|
47
58
|
message: typeof raw.message === 'string' ? raw.message : 'Unknown error',
|
|
48
|
-
|
|
59
|
+
address: typeof raw.address === 'string' ? raw.address : undefined,
|
|
49
60
|
operation: typeof raw.operation === 'string' ? raw.operation : undefined,
|
|
50
61
|
details: known ? raw.details : { originalCode: raw.code, details: raw.details },
|
|
51
62
|
});
|
package/src/wire/FrondCall.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
export interface FrondCall {
|
|
3
3
|
/** Frond name, when the caller knows it. Routing hint only. */
|
|
4
4
|
frond?: string;
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/** The address a handler answers at (e.g. 'product'). */
|
|
6
|
+
address: string;
|
|
7
7
|
/** Façade operation name (e.g. 'findById', 'search'). */
|
|
8
8
|
op: string;
|
|
9
9
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ── Types ───────────────────────────────────────
|
|
2
2
|
export interface OperationContext {
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/** The address the operation answers at — `checkout` in `checkout.pay`. */
|
|
4
|
+
address: string;
|
|
5
5
|
/**
|
|
6
6
|
* Which frond owns this operation — the unit that gets deployed, and therefore the
|
|
7
7
|
* one a reader groups by first. Absent only where no frond claims the call.
|
package/src/wire/RouteAddress.ts
CHANGED
|
@@ -14,13 +14,13 @@ function optional(value: string | undefined, name: string): string | undefined {
|
|
|
14
14
|
|
|
15
15
|
/** Canonical identity of one callable operation. */
|
|
16
16
|
export class RouteAddress {
|
|
17
|
-
readonly
|
|
17
|
+
readonly address: string;
|
|
18
18
|
readonly operation: string;
|
|
19
19
|
readonly surface?: string;
|
|
20
20
|
|
|
21
21
|
/** Names one callable operation, and freezes it so a registry key cannot drift. */
|
|
22
22
|
constructor(input: RouteAddressInput) {
|
|
23
|
-
this.
|
|
23
|
+
this.address = required(input.address, 'address');
|
|
24
24
|
this.operation = required(input.operation, 'operation');
|
|
25
25
|
this.surface = optional(input.surface, 'surface');
|
|
26
26
|
Object.freeze(this);
|
|
@@ -28,7 +28,7 @@ export class RouteAddress {
|
|
|
28
28
|
|
|
29
29
|
/** Serializes the three segments so a route can be looked up in a `Map`. */
|
|
30
30
|
key(): string {
|
|
31
|
-
return JSON.stringify([this.surface ?? null, this.
|
|
31
|
+
return JSON.stringify([this.surface ?? null, this.address, this.operation]);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/** Compares two addresses by their key, so `surface` counts as much as the operation. */
|
|
@@ -40,13 +40,13 @@ export class RouteAddress {
|
|
|
40
40
|
toString(): string {
|
|
41
41
|
const audience = this.surface ? `${this.surface}/` : '';
|
|
42
42
|
|
|
43
|
-
return `${audience}${this.
|
|
43
|
+
return `${audience}${this.address}.${this.operation}`;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** Sends the address over the wire without `surface: undefined` riding along. */
|
|
47
47
|
toJSON(): RouteAddressInput {
|
|
48
48
|
return {
|
|
49
|
-
|
|
49
|
+
address: this.address,
|
|
50
50
|
operation: this.operation,
|
|
51
51
|
...(this.surface !== undefined ? { surface: this.surface } : {}),
|
|
52
52
|
};
|
package/src/wire/RpcAnswer.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { InvocationContext } from './InvocationContext.js';
|
|
2
2
|
|
|
3
3
|
/** Reserved namespace — calls the runner answers itself, never a façade. */
|
|
4
|
-
export const
|
|
4
|
+
export const RPC_ADDRESS = 'rpc';
|
|
5
5
|
|
|
6
6
|
/** What an `rpc` op answers — the facade for what the app says about ITSELF, never about a row. */
|
|
7
7
|
export type RpcAnswer = (invocation: InvocationContext, surface?: string) => unknown;
|
package/src/wire/SignedCall.ts
CHANGED
package/src/wire/StateShape.ts
CHANGED
|
@@ -51,7 +51,7 @@ export class StateShape {
|
|
|
51
51
|
*/
|
|
52
52
|
judge(
|
|
53
53
|
state: Record<string, unknown>,
|
|
54
|
-
|
|
54
|
+
address: string,
|
|
55
55
|
operation: string,
|
|
56
56
|
entered: Record<string, unknown> = {},
|
|
57
57
|
): Record<string, unknown> {
|
|
@@ -69,7 +69,7 @@ export class StateShape {
|
|
|
69
69
|
message: errors.map((error) => `${dotted(error.path)}: ${error.message}`).join(', ')
|
|
70
70
|
+ (declared.length ? ` — this process declares ${declared.join(', ')}` : ' — this process declares no state member'),
|
|
71
71
|
details: errors,
|
|
72
|
-
|
|
72
|
+
address,
|
|
73
73
|
operation,
|
|
74
74
|
});
|
|
75
75
|
}
|
|
@@ -7,18 +7,19 @@ import type { CardOp } from './CardOp.js';
|
|
|
7
7
|
export interface IdentityCard {
|
|
8
8
|
fronds: {
|
|
9
9
|
name: string;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
/** What the frond STORES — one schema per entity, under the entity's name. */
|
|
11
|
+
entities: { name: string; schema: SchemaDescriptor }[];
|
|
12
|
+
/**
|
|
13
|
+
* What the frond ANSWERS — one entry per address, each op carrying its own input and output.
|
|
14
|
+
* An output that IS an entity is titled with the entity's name.
|
|
15
|
+
*/
|
|
16
|
+
facades: { name: string; ops: CardOp[] }[];
|
|
16
17
|
/** The facts this frond ANNOUNCES — one entry per `Emit<T>` its handlers inject. */
|
|
17
18
|
facts: { name: string; schema?: SchemaDescriptor }[];
|
|
18
19
|
}[];
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
/** The shape a card must have to be walked — `fronds`, and each frond's `facades`. */
|
|
22
|
+
/** The shape a card must have to be walked — `fronds`, and each frond's `entities` and `facades`. */
|
|
22
23
|
export function assertIdentityCard(value: unknown, source: string): IdentityCard {
|
|
23
24
|
const card = value as IdentityCard | undefined;
|
|
24
25
|
const fronds = Array.isArray(card?.fronds) ? card.fronds : undefined;
|
|
@@ -26,6 +27,7 @@ export function assertIdentityCard(value: unknown, source: string): IdentityCard
|
|
|
26
27
|
for (const frond of fronds) {
|
|
27
28
|
if (!frond || typeof frond.name !== 'string') throw cardRefusal(source, 'a frond with no name');
|
|
28
29
|
if (!Array.isArray(frond.facades)) throw cardRefusal(source, `frond '${frond.name}' has no valid facades array`);
|
|
30
|
+
if (!Array.isArray(frond.entities)) throw cardRefusal(source, `frond '${frond.name}' has no valid entities array`);
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
return card as IdentityCard;
|
package/src/wire/drift.ts
CHANGED
|
@@ -8,26 +8,18 @@ export interface CardDrift {
|
|
|
8
8
|
missingDoors: string[];
|
|
9
9
|
/** An operation the consumer calls that the facade no longer has. */
|
|
10
10
|
missingOps: { facade: string; ops: string[] }[];
|
|
11
|
-
/**
|
|
12
|
-
shapes: { facade: string; changes: Change[] }[];
|
|
11
|
+
/** What an operation the consumer still calls answers, and how it moved. */
|
|
12
|
+
shapes: { facade: string; op: string; changes: Change[] }[];
|
|
13
13
|
/** A fact the consumer subscribes to whose shape moved, or that is gone. */
|
|
14
14
|
facts: { fact: string; changes: Change[] | 'gone' }[];
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
/** Every facade of a card, by name. */
|
|
18
|
-
|
|
19
|
-
|
|
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>();
|
|
17
|
+
/** Every facade of a card, by name, and what each of its operations answers. */
|
|
18
|
+
function facadesOf(card: IdentityCard, frond: string): Map<string, Map<string, SchemaDescriptor | undefined>> {
|
|
19
|
+
const found = new Map<string, Map<string, SchemaDescriptor | undefined>>();
|
|
26
20
|
for (const one of card.fronds) {
|
|
27
21
|
if (one.name !== frond) continue;
|
|
28
|
-
for (const facade of one.facades)
|
|
29
|
-
found.set(facade.name, { ops: new Set(facade.ops.map((op) => op.name)), schema: facade.schema });
|
|
30
|
-
}
|
|
22
|
+
for (const facade of one.facades) found.set(facade.name, new Map(facade.ops.map((op) => [op.name, op.output])));
|
|
31
23
|
}
|
|
32
24
|
|
|
33
25
|
return found;
|
|
@@ -53,29 +45,32 @@ export function driftOf(mine: IdentityCard, theirs: IdentityCard, frond: string)
|
|
|
53
45
|
return drift;
|
|
54
46
|
}
|
|
55
47
|
|
|
56
|
-
/** A door that is gone, an operation it no longer answers, or
|
|
48
|
+
/** A door that is gone, an operation it no longer answers, or an answer that changed shape. */
|
|
57
49
|
function driftInDoors(
|
|
58
|
-
mine: Map<string,
|
|
59
|
-
theirs: Map<string,
|
|
50
|
+
mine: Map<string, Map<string, SchemaDescriptor | undefined>>,
|
|
51
|
+
theirs: Map<string, Map<string, SchemaDescriptor | undefined>>,
|
|
60
52
|
drift: CardDrift,
|
|
61
53
|
): void {
|
|
62
|
-
for (const [name,
|
|
54
|
+
for (const [name, ops] of mine) {
|
|
63
55
|
const there = theirs.get(name);
|
|
64
56
|
if (!there) {
|
|
65
57
|
drift.missingDoors.push(name);
|
|
66
58
|
continue;
|
|
67
59
|
}
|
|
68
60
|
|
|
69
|
-
const missing = [...
|
|
61
|
+
const missing = [...ops.keys()].filter((op) => !there.has(op));
|
|
70
62
|
if (missing.length > 0) drift.missingOps.push({ facade: name, ops: missing.sort() });
|
|
71
63
|
|
|
72
|
-
|
|
64
|
+
for (const [op, output] of ops) {
|
|
65
|
+
const answered = there.get(op);
|
|
66
|
+
if (!output || !answered) continue;
|
|
73
67
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
// `Card.diff` never guesses a rename — a field gone plus a field appeared lands in
|
|
69
|
+
// `ambiguous`, and only a declaration settles it. Here nobody can declare one, so the
|
|
70
|
+
// pair is reported as it is and a human reads it.
|
|
71
|
+
const moved = Card.fromDescriptor(output).diff(Card.fromDescriptor(answered));
|
|
72
|
+
if (moved.changes.length > 0) drift.shapes.push({ facade: name, op, changes: moved.changes });
|
|
73
|
+
}
|
|
79
74
|
}
|
|
80
75
|
}
|
|
81
76
|
|
|
@@ -112,8 +107,8 @@ export function explain(drift: CardDrift): string[] {
|
|
|
112
107
|
const lines: string[] = [];
|
|
113
108
|
for (const facade of drift.missingDoors) lines.push(`${drift.frond}.${facade} — you call it, it is not served`);
|
|
114
109
|
for (const { facade, ops } of drift.missingOps) lines.push(`${drift.frond}.${facade} — gone: ${ops.join(', ')}`);
|
|
115
|
-
for (const { facade, changes } of drift.shapes) {
|
|
116
|
-
for (const change of changes) lines.push(`${drift.frond}.${facade} — ${describe(change)}`);
|
|
110
|
+
for (const { facade, op, changes } of drift.shapes) {
|
|
111
|
+
for (const change of changes) lines.push(`${drift.frond}.${facade}.${op} — ${describe(change)}`);
|
|
117
112
|
}
|
|
118
113
|
for (const { fact, changes } of drift.facts) {
|
|
119
114
|
if (changes === 'gone') { lines.push(`${fact} — you subscribe to it, it is no longer announced`); continue; }
|
package/src/wire/http-error.ts
CHANGED
|
@@ -38,13 +38,13 @@ function httpStatusFor(code: ErrorCode): number {
|
|
|
38
38
|
/** Serialize an application error for an untrusted caller. */
|
|
39
39
|
export function toPublicError(err: FougereError): ReturnType<FougereError['toJSON']> {
|
|
40
40
|
if (err.code !== ErrorCode.INTERNAL_ERROR) return err.toJSON();
|
|
41
|
-
const where = [err.
|
|
41
|
+
const where = [err.address, err.operation].filter(Boolean).join('.');
|
|
42
42
|
log.error(`${where || 'internal'}: ${err.message}`, err.cause ?? err);
|
|
43
43
|
|
|
44
44
|
return {
|
|
45
45
|
code: ErrorCode.INTERNAL_ERROR,
|
|
46
46
|
message: 'Internal error',
|
|
47
|
-
...(err.
|
|
47
|
+
...(err.address && { address: err.address }),
|
|
48
48
|
...(err.operation && { operation: err.operation }),
|
|
49
49
|
};
|
|
50
50
|
}
|