@fougere/core 0.16.7-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/builtin/Logger.d.ts.map +1 -1
- package/dist/builtin/Logger.js +9 -1
- package/dist/builtin/Logger.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/builtin/Logger.ts +11 -1
- 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
|
@@ -154,9 +154,7 @@ export class StorageGuard {
|
|
|
154
154
|
if (errors.length > 0) {
|
|
155
155
|
throw new FougereError({
|
|
156
156
|
code: ErrorCode.BAD_REQUEST,
|
|
157
|
-
message: `Refused as a filter — ${errors.join(', ')}`,
|
|
158
|
-
entity: this.entity,
|
|
159
|
-
operation: 'list',
|
|
157
|
+
message: `Refused as a filter on ${this.entity} — ${errors.join(', ')}`,
|
|
160
158
|
details: errors,
|
|
161
159
|
});
|
|
162
160
|
}
|
|
@@ -247,9 +245,7 @@ export class StorageGuard {
|
|
|
247
245
|
if (errors.length > 0) {
|
|
248
246
|
throw new FougereError({
|
|
249
247
|
code: ErrorCode.INTERNAL_ERROR,
|
|
250
|
-
message: `Refused on the way out — ${errors.join(', ')}`,
|
|
251
|
-
entity: this.entity,
|
|
252
|
-
operation,
|
|
248
|
+
message: `Refused on the way out of ${this.entity}.${operation} — ${errors.join(', ')}`,
|
|
253
249
|
details: errors,
|
|
254
250
|
});
|
|
255
251
|
}
|
|
@@ -287,9 +283,7 @@ export class StorageGuard {
|
|
|
287
283
|
|
|
288
284
|
throw new FougereError({
|
|
289
285
|
code: ErrorCode.VALIDATION_FAILED,
|
|
290
|
-
message: `Refused on the way out — ${errors.join(', ')}`,
|
|
291
|
-
entity: this.entity,
|
|
292
|
-
operation,
|
|
286
|
+
message: `Refused on the way out of ${this.entity}.${operation} — ${errors.join(', ')}`,
|
|
293
287
|
details: errors,
|
|
294
288
|
});
|
|
295
289
|
}
|
|
@@ -2,14 +2,14 @@ import { OperationRoute } from './OperationRoute.js';
|
|
|
2
2
|
import type { RouteResolver } from './RouteResolver.js';
|
|
3
3
|
|
|
4
4
|
type RemoteFacade = Record<string, Function>;
|
|
5
|
-
export type RemoteFacadeResolver = (
|
|
5
|
+
export type RemoteFacadeResolver = (address: string) => RemoteFacade | undefined;
|
|
6
6
|
|
|
7
7
|
/** Builds a remote route on the first call, once the remote façade can be resolved. */
|
|
8
8
|
export function remoteRoutes(facades: RemoteFacadeResolver): RouteResolver {
|
|
9
9
|
return (address) => {
|
|
10
10
|
if (address.surface !== undefined) return undefined;
|
|
11
11
|
|
|
12
|
-
const facade = facades(address.
|
|
12
|
+
const facade = facades(address.address);
|
|
13
13
|
const operation = facade?.[address.operation];
|
|
14
14
|
|
|
15
15
|
if (typeof operation !== 'function') return undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Call } from '../wire/Call.js';
|
|
2
2
|
import { ErrorCode } from '../wire/ErrorCode.js';
|
|
3
3
|
import { FougereError } from '../wire/FougereError.js';
|
|
4
|
+
import { RPC_ADDRESS } from '../wire/RpcAnswer.js';
|
|
4
5
|
import type { Route } from './Route.js';
|
|
5
6
|
import type { RoutePolicy } from './RoutePolicy.js';
|
|
6
7
|
|
|
@@ -12,7 +13,7 @@ export function servedOperations(
|
|
|
12
13
|
): readonly string[] {
|
|
13
14
|
return routes
|
|
14
15
|
.filter((route) => !policy || policy.accepts(route))
|
|
15
|
-
.filter((route) => route.address.
|
|
16
|
+
.filter((route) => route.address.address === call.address.address)
|
|
16
17
|
.filter((route) => route.kind === 'system' || route.address.surface === call.address.surface)
|
|
17
18
|
.map((route) => route.address.operation);
|
|
18
19
|
}
|
|
@@ -22,13 +23,13 @@ export function routeNotFound(
|
|
|
22
23
|
call: Call,
|
|
23
24
|
served: readonly string[],
|
|
24
25
|
): FougereError {
|
|
25
|
-
const {
|
|
26
|
-
const message =
|
|
26
|
+
const { address, operation } = call.address;
|
|
27
|
+
const message = address === RPC_ADDRESS
|
|
27
28
|
? `Unknown rpc operation '${operation}'. `
|
|
28
29
|
+ (served.length ? `It serves ${served.join(', ')}.` : 'It serves nothing.')
|
|
29
30
|
: served.length
|
|
30
|
-
? `Unknown operation '${operation}' on '${
|
|
31
|
+
? `Unknown operation '${operation}' on '${address}'. It serves ${served.join(', ')}.`
|
|
31
32
|
: `No route serves '${call.address}'`;
|
|
32
33
|
|
|
33
|
-
return new FougereError({ code: ErrorCode.NOT_FOUND, message,
|
|
34
|
+
return new FougereError({ code: ErrorCode.NOT_FOUND, message, address, operation });
|
|
34
35
|
}
|
|
@@ -7,8 +7,6 @@ import { FougereError } from '../wire/FougereError.js';
|
|
|
7
7
|
export function validateInput(
|
|
8
8
|
schema: SchemaView | undefined,
|
|
9
9
|
invocation: InvocationContext,
|
|
10
|
-
entity: string,
|
|
11
|
-
operation: string,
|
|
12
10
|
): InvocationContext {
|
|
13
11
|
if (!schema || invocation.input === undefined || invocation.input === null) return invocation;
|
|
14
12
|
|
|
@@ -20,8 +18,6 @@ export function validateInput(
|
|
|
20
18
|
code: ErrorCode.VALIDATION_FAILED,
|
|
21
19
|
message: result.errors.map((error) => `${dotted(error.path)}: ${error.message}`).join(', '),
|
|
22
20
|
details: result.errors,
|
|
23
|
-
entity,
|
|
24
|
-
operation,
|
|
25
21
|
});
|
|
26
22
|
}
|
|
27
23
|
|
package/src/entry/facade.ts
CHANGED
|
@@ -32,7 +32,7 @@ export function dynamicOperations(operation: (name: string) => Operation): Recor
|
|
|
32
32
|
*/
|
|
33
33
|
export function facadeOperations(
|
|
34
34
|
dispatcher: DispatchPort,
|
|
35
|
-
|
|
35
|
+
address: string,
|
|
36
36
|
operationNames?: Iterable<string>,
|
|
37
37
|
surface?: string,
|
|
38
38
|
received?: Received,
|
|
@@ -40,7 +40,7 @@ export function facadeOperations(
|
|
|
40
40
|
const operation = (name: string): Operation => async (invocation) => {
|
|
41
41
|
const answer = await dispatcher.dispatch(new Call(
|
|
42
42
|
new RouteAddress({
|
|
43
|
-
|
|
43
|
+
address,
|
|
44
44
|
operation: name,
|
|
45
45
|
...(surface !== undefined ? { surface } : {}),
|
|
46
46
|
}),
|
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
|
}
|