@procedures/client 0.3.0 → 0.5.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/CHANGELOG.md +68 -0
- package/README.md +62 -7
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/http/client/transport.d.ts +2 -1
- package/dist/adapters/http/client/transport.d.ts.map +1 -1
- package/dist/adapters/http/client/transport.js +9 -2
- package/dist/adapters/http/client/transport.js.map +1 -1
- package/dist/adapters/http/server/transport.d.ts +3 -1
- package/dist/adapters/http/server/transport.d.ts.map +1 -1
- package/dist/adapters/http/server/transport.js +8 -6
- package/dist/adapters/http/server/transport.js.map +1 -1
- package/dist/adapters/local/client/transport.d.ts +2 -1
- package/dist/adapters/local/client/transport.d.ts.map +1 -1
- package/dist/adapters/local/client/transport.js +12 -4
- package/dist/adapters/local/client/transport.js.map +1 -1
- package/dist/adapters/local/client/types.d.ts +6 -0
- package/dist/adapters/local/client/types.d.ts.map +1 -1
- package/dist/adapters/local/client/types.js.map +1 -1
- package/dist/adapters/sequence.d.ts +36 -0
- package/dist/adapters/sequence.d.ts.map +1 -0
- package/dist/adapters/sequence.js +55 -0
- package/dist/adapters/sequence.js.map +1 -0
- package/dist/adapters/websocket/client/node.d.ts +30 -0
- package/dist/adapters/websocket/client/node.d.ts.map +1 -0
- package/dist/adapters/websocket/client/node.js +32 -0
- package/dist/adapters/websocket/client/node.js.map +1 -0
- package/dist/adapters/websocket/client/transport.d.ts +39 -1
- package/dist/adapters/websocket/client/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/client/transport.js +119 -16
- package/dist/adapters/websocket/client/transport.js.map +1 -1
- package/dist/adapters/websocket/client/types.d.ts +15 -5
- package/dist/adapters/websocket/client/types.d.ts.map +1 -1
- package/dist/adapters/websocket/client/types.js.map +1 -1
- package/dist/adapters/websocket/server/transport.d.ts +22 -1
- package/dist/adapters/websocket/server/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/server/transport.js +29 -1
- package/dist/adapters/websocket/server/transport.js.map +1 -1
- package/dist/adapters/websocket/server/types.d.ts +6 -0
- package/dist/adapters/websocket/server/types.d.ts.map +1 -1
- package/dist/adapters/websocket/shared/rpc.d.ts +4 -3
- package/dist/adapters/websocket/shared/rpc.d.ts.map +1 -1
- package/dist/adapters/websocket/shared/rpc.js +18 -9
- package/dist/adapters/websocket/shared/rpc.js.map +1 -1
- package/dist/browser.d.ts +3 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +4 -0
- package/dist/browser.js.map +1 -1
- package/dist/client/errors/http-errors.d.ts.map +1 -1
- package/dist/client/errors/http-errors.js +6 -0
- package/dist/client/errors/http-errors.js.map +1 -1
- package/dist/client/errors/index.d.ts +1 -1
- package/dist/client/errors/index.js +1 -1
- package/dist/client/errors/registry.d.ts +2 -1
- package/dist/client/errors/registry.d.ts.map +1 -1
- package/dist/client/errors/registry.js +73 -22
- package/dist/client/errors/registry.js.map +1 -1
- package/dist/client/middleware/rate-limit.d.ts.map +1 -1
- package/dist/client/middleware/rate-limit.js +0 -1
- package/dist/client/middleware/rate-limit.js.map +1 -1
- package/dist/client/middleware/timeout.d.ts.map +1 -1
- package/dist/client/middleware/timeout.js +2 -9
- package/dist/client/middleware/timeout.js.map +1 -1
- package/dist/events/index.d.ts +4 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +3 -0
- package/dist/events/index.js.map +1 -1
- package/dist/events/publisher.d.ts +95 -0
- package/dist/events/publisher.d.ts.map +1 -0
- package/dist/events/publisher.js +268 -0
- package/dist/events/publisher.js.map +1 -0
- package/dist/events/remote.d.ts +164 -0
- package/dist/events/remote.d.ts.map +1 -0
- package/dist/events/remote.js +250 -0
- package/dist/events/remote.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +11 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +10 -0
- package/dist/node.js.map +1 -0
- package/dist/procedures/collection/index.d.ts +1 -1
- package/dist/procedures/collection/index.d.ts.map +1 -1
- package/dist/procedures/collection/index.js.map +1 -1
- package/dist/procedures/collection/procedures.d.ts.map +1 -1
- package/dist/procedures/collection/procedures.js +6 -1
- package/dist/procedures/collection/procedures.js.map +1 -1
- package/dist/procedures/collection/schemas.d.ts +16 -1
- package/dist/procedures/collection/schemas.d.ts.map +1 -1
- package/dist/procedures/collection/schemas.js +13 -2
- package/dist/procedures/collection/schemas.js.map +1 -1
- package/dist/procedures/collection/storage.d.ts +14 -2
- package/dist/procedures/collection/storage.d.ts.map +1 -1
- package/dist/procedures/core/array.d.ts +1 -1
- package/dist/procedures/core/array.d.ts.map +1 -1
- package/dist/procedures/core/array.js +56 -15
- package/dist/procedures/core/array.js.map +1 -1
- package/dist/procedures/core/index.d.ts.map +1 -1
- package/dist/procedures/core/index.js +9 -2
- package/dist/procedures/core/index.js.map +1 -1
- package/dist/procedures/core/logic.js +1 -1
- package/dist/procedures/core/logic.js.map +1 -1
- package/dist/procedures/core/math.d.ts.map +1 -1
- package/dist/procedures/core/math.js +5 -1
- package/dist/procedures/core/math.js.map +1 -1
- package/dist/procedures/core/meta.d.ts.map +1 -1
- package/dist/procedures/core/meta.js +41 -7
- package/dist/procedures/core/meta.js.map +1 -1
- package/dist/procedures/core/type.d.ts.map +1 -1
- package/dist/procedures/core/type.js +4 -2
- package/dist/procedures/core/type.js.map +1 -1
- package/dist/procedures/discovery.d.ts +4 -6
- package/dist/procedures/discovery.d.ts.map +1 -1
- package/dist/procedures/discovery.js +4 -6
- package/dist/procedures/discovery.js.map +1 -1
- package/dist/procedures/invoke.d.ts +4 -2
- package/dist/procedures/invoke.d.ts.map +1 -1
- package/dist/procedures/invoke.js +1 -1
- package/dist/procedures/invoke.js.map +1 -1
- package/dist/procedures/ref.d.ts +4 -2
- package/dist/procedures/ref.d.ts.map +1 -1
- package/dist/procedures/ref.js +2 -1
- package/dist/procedures/ref.js.map +1 -1
- package/dist/protocol.d.ts +10 -3
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +13 -2
- package/dist/protocol.js.map +1 -1
- package/dist/server/host-policy.d.ts +11 -0
- package/dist/server/host-policy.d.ts.map +1 -1
- package/dist/server/host-policy.js +5 -0
- package/dist/server/host-policy.js.map +1 -1
- package/dist/server/procedure-server.d.ts +26 -3
- package/dist/server/procedure-server.d.ts.map +1 -1
- package/dist/server/procedure-server.js +38 -6
- package/dist/server/procedure-server.js.map +1 -1
- package/package.json +7 -6
- package/src/adapters/http/client/transport.ts +9 -2
- package/src/adapters/http/server/transport.ts +8 -6
- package/src/adapters/local/client/transport.ts +12 -4
- package/src/adapters/local/client/types.ts +7 -0
- package/src/adapters/sequence.ts +55 -0
- package/src/adapters/websocket/client/node.ts +34 -0
- package/src/adapters/websocket/client/transport.ts +128 -16
- package/src/adapters/websocket/client/types.ts +16 -5
- package/src/adapters/websocket/server/transport.ts +43 -1
- package/src/adapters/websocket/server/types.ts +7 -0
- package/src/adapters/websocket/shared/rpc.ts +19 -9
- package/src/browser.ts +15 -0
- package/src/client/errors/README.md +2 -0
- package/src/client/errors/http-errors.ts +5 -0
- package/src/client/errors/index.ts +1 -1
- package/src/client/errors/registry.ts +80 -22
- package/src/client/middleware/rate-limit.ts +0 -2
- package/src/client/middleware/timeout.ts +2 -9
- package/src/events/index.ts +16 -0
- package/src/events/publisher.ts +314 -0
- package/src/events/remote.ts +369 -0
- package/src/index.ts +4 -0
- package/src/node.ts +11 -0
- package/src/procedures/collection/index.ts +1 -0
- package/src/procedures/collection/procedures.ts +6 -1
- package/src/procedures/collection/schemas.ts +28 -2
- package/src/procedures/collection/storage.ts +14 -2
- package/src/procedures/core/array.ts +53 -16
- package/src/procedures/core/index.ts +9 -2
- package/src/procedures/core/logic.ts +1 -1
- package/src/procedures/core/math.ts +5 -1
- package/src/procedures/core/meta.ts +43 -7
- package/src/procedures/core/type.ts +4 -2
- package/src/procedures/discovery.ts +4 -6
- package/src/procedures/invoke.ts +4 -2
- package/src/procedures/ref.ts +4 -2
- package/src/protocol.ts +16 -3
- package/src/server/host-policy.ts +14 -0
- package/src/server/procedure-server.ts +45 -8
- package/dist/procedures/cli/announce.d.ts +0 -67
- package/dist/procedures/cli/announce.d.ts.map +0 -1
- package/dist/procedures/cli/announce.js +0 -392
- package/dist/procedures/cli/announce.js.map +0 -1
- package/dist/procedures/cli/discover.d.ts +0 -66
- package/dist/procedures/cli/discover.d.ts.map +0 -1
- package/dist/procedures/cli/discover.js +0 -310
- package/dist/procedures/cli/discover.js.map +0 -1
- package/dist/procedures/cli/index.d.ts +0 -13
- package/dist/procedures/cli/index.d.ts.map +0 -1
- package/dist/procedures/cli/index.js +0 -203
- package/dist/procedures/cli/index.js.map +0 -1
- package/dist/procedures/cli/init.d.ts +0 -17
- package/dist/procedures/cli/init.d.ts.map +0 -1
- package/dist/procedures/cli/init.js +0 -336
- package/dist/procedures/cli/init.js.map +0 -1
- package/src/procedures/cli/announce.ts +0 -470
- package/src/procedures/cli/discover.ts +0 -390
- package/src/procedures/cli/index.ts +0 -231
- package/src/procedures/cli/init.ts +0 -370
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
parseProcedureJson,
|
|
15
15
|
stringifyProcedureJson,
|
|
16
16
|
isAnyProcedureRef,
|
|
17
|
+
isProcedureRef,
|
|
17
18
|
isOutputRef,
|
|
18
19
|
isPlainObject,
|
|
19
20
|
resolveOutputRef,
|
|
@@ -58,18 +59,37 @@ const ImportOutputSchema = z.looseObject({
|
|
|
58
59
|
|
|
59
60
|
type ImportProcedure = Procedure<ImportInput, ImportOutput, { description: string; tags: string[] }>;
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* This function gives true for the runtime form of a procedure reference whose path is a list of
|
|
64
|
+
* texts. `client.import` accepts only such a value at the top level.
|
|
65
|
+
*/
|
|
66
|
+
function isImportableRef(value: unknown): boolean {
|
|
67
|
+
if (!isProcedureRef(value)) return false;
|
|
68
|
+
const path = (value as { path?: unknown }).path;
|
|
69
|
+
return Array.isArray(path) && path.length > 0 && path.every((segment) => typeof segment === "string");
|
|
70
|
+
}
|
|
71
|
+
|
|
61
72
|
const importProcedure: ImportProcedure = defineProcedure({
|
|
62
73
|
path: ["import"],
|
|
63
74
|
input: ImportInputSchema,
|
|
64
75
|
output: ImportOutputSchema,
|
|
65
76
|
metadata: {
|
|
66
|
-
description:
|
|
77
|
+
description:
|
|
78
|
+
"Parse a procedure reference ({ $proc, input }) from JSON text. Other JSON, for example a number or a list, gives success false.",
|
|
67
79
|
tags: ["core", "meta"],
|
|
68
80
|
},
|
|
69
81
|
handler: async (input: ImportInput): Promise<ImportOutput> => {
|
|
70
82
|
try {
|
|
71
83
|
if (input.json) {
|
|
72
84
|
const procedure = parseProcedureJson(input.json);
|
|
85
|
+
// Before, each JSON value gave success true, for example 42 (catalog test, roadmap R29)
|
|
86
|
+
if (!isImportableRef(procedure)) {
|
|
87
|
+
return {
|
|
88
|
+
procedure: null,
|
|
89
|
+
success: false,
|
|
90
|
+
error: "The JSON is not a procedure reference. A reference is an object whose $proc is a list of texts.",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
73
93
|
return { procedure, success: true };
|
|
74
94
|
}
|
|
75
95
|
if (input.path) {
|
|
@@ -107,8 +127,19 @@ interface ExportInput {
|
|
|
107
127
|
pretty?: boolean | undefined;
|
|
108
128
|
}
|
|
109
129
|
|
|
130
|
+
/**
|
|
131
|
+
* This function gives a schema of a required value: undefined has no JSON text, so the input
|
|
132
|
+
* schema refuses it with `VALIDATION_ERROR`. Before, the call failed with
|
|
133
|
+
* `OUTPUT_VALIDATION_ERROR` (catalog test, roadmap R29).
|
|
134
|
+
*/
|
|
135
|
+
const definedValue = (description: string) =>
|
|
136
|
+
z
|
|
137
|
+
.unknown()
|
|
138
|
+
.refine((value) => value !== undefined, { message: "The value is missing: undefined has no JSON text" })
|
|
139
|
+
.describe(description);
|
|
140
|
+
|
|
110
141
|
const ExportInputSchema = z.object({
|
|
111
|
-
procedure:
|
|
142
|
+
procedure: definedValue("The procedure reference to serialize. The procedure refuses a missing value (undefined) with VALIDATION_ERROR, because undefined has no JSON text."),
|
|
112
143
|
pretty: z.boolean().optional().describe("Pretty print JSON output"),
|
|
113
144
|
});
|
|
114
145
|
|
|
@@ -134,13 +165,15 @@ const exportProcedure: ExportProcedure = defineProcedure({
|
|
|
134
165
|
input: ExportInputSchema,
|
|
135
166
|
output: ExportOutputSchema,
|
|
136
167
|
metadata: {
|
|
137
|
-
description: "Serialize procedure
|
|
168
|
+
description: "Serialize a procedure reference to JSON text. It refuses a missing procedure (undefined) with VALIDATION_ERROR.",
|
|
138
169
|
// The refs of the input are data: exec() must not run them (deep dive CORE-5)
|
|
139
170
|
tags: ["core", "meta", RAW_INPUT_TAG],
|
|
140
171
|
},
|
|
141
172
|
handler: async (input: ExportInput): Promise<ExportOutput> => {
|
|
142
173
|
try {
|
|
143
|
-
const json = stringifyProcedureJson(input.procedure, input.pretty ? 2 : undefined);
|
|
174
|
+
const json = stringifyProcedureJson(input.procedure, input.pretty ? 2 : undefined) as string | undefined;
|
|
175
|
+
// A function or a symbol has no JSON text either: an error result, not an invalid output
|
|
176
|
+
if (json === undefined) return { json: "", success: false, error: "The procedure has no JSON text" };
|
|
144
177
|
return { json, success: true };
|
|
145
178
|
} catch (error) {
|
|
146
179
|
return {
|
|
@@ -268,7 +301,7 @@ interface StringifyJsonInput {
|
|
|
268
301
|
}
|
|
269
302
|
|
|
270
303
|
const StringifyJsonInputSchema = z.object({
|
|
271
|
-
value:
|
|
304
|
+
value: definedValue("The value to write as JSON. The procedure refuses a missing value (undefined) with VALIDATION_ERROR, because undefined has no JSON text."),
|
|
272
305
|
pretty: z.boolean().optional().describe("Pretty print with indentation"),
|
|
273
306
|
indent: z.union([z.number(), z.string()]).optional().describe("Custom indentation (number of spaces or string)"),
|
|
274
307
|
});
|
|
@@ -280,13 +313,16 @@ const stringifyJsonProcedure: StringifyJsonProcedure = defineProcedure({
|
|
|
280
313
|
input: StringifyJsonInputSchema,
|
|
281
314
|
output: stringOutput("The JSON text"),
|
|
282
315
|
metadata: {
|
|
283
|
-
description: "Stringify value to JSON (with procedure ref support)",
|
|
316
|
+
description: "Stringify value to JSON (with procedure ref support). It refuses a missing value (undefined) with VALIDATION_ERROR.",
|
|
284
317
|
// The refs of the input are data: exec() must not run them (deep dive CORE-5)
|
|
285
318
|
tags: ["core", "meta", RAW_INPUT_TAG],
|
|
286
319
|
},
|
|
287
320
|
handler: async (input: StringifyJsonInput): Promise<string> => {
|
|
288
321
|
const indent = input.pretty ? (input.indent ?? 2) : input.indent;
|
|
289
|
-
|
|
322
|
+
const json = stringifyProcedureJson(input.value, indent as number) as string | undefined;
|
|
323
|
+
// A function or a symbol has no JSON text either: a clear error, not an invalid output
|
|
324
|
+
if (json === undefined) throw new Error("The value has no JSON text");
|
|
325
|
+
return json;
|
|
290
326
|
},
|
|
291
327
|
});
|
|
292
328
|
|
|
@@ -10,6 +10,7 @@ import { anyOutput, booleanOutput, stringOutput } from "./outputs.js";
|
|
|
10
10
|
import { defineProcedure, namespace } from "../define.js";
|
|
11
11
|
import type { AnyProcedure, Procedure } from "../types.js";
|
|
12
12
|
import { PURE_EFFECTS, withEffects } from "../effects.js";
|
|
13
|
+
import { isPlainObject } from "../ref.js";
|
|
13
14
|
|
|
14
15
|
// =============================================================================
|
|
15
16
|
// Typeof Procedure
|
|
@@ -168,11 +169,12 @@ const isObjectProcedure: IsObjectProcedure = defineProcedure({
|
|
|
168
169
|
input: IsObjectInputSchema,
|
|
169
170
|
output: booleanOutput("True for a plain object"),
|
|
170
171
|
metadata: {
|
|
171
|
-
description: "Check if value is a plain object (not array, not null)",
|
|
172
|
+
description: "Check if value is a plain object (not array, not null, not a Date or another class instance)",
|
|
172
173
|
tags: ["core", "type"],
|
|
173
174
|
},
|
|
174
175
|
handler: async (input: IsObjectInput): Promise<boolean> => {
|
|
175
|
-
|
|
176
|
+
// Before, a Date or a Map gave true (catalog test, roadmap R29)
|
|
177
|
+
return isPlainObject(input.value);
|
|
176
178
|
},
|
|
177
179
|
});
|
|
178
180
|
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Procedure Registration Helpers
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* use the CLI: `npx client-registry discover`
|
|
4
|
+
* These helpers add procedures to the global registry.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* 3. Consumers run `npx client-registry discover` to generate imports
|
|
6
|
+
* A package with procedures declares `"client": { "procedures": "./dist/register.js" }` in its
|
|
7
|
+
* `package.json`. Its `src/register.ts` registers its module. `mark` finds the packages by the
|
|
8
|
+
* `client.procedures` field, and an app imports the `/register` entry of each package that it uses.
|
|
11
9
|
*/
|
|
12
10
|
|
|
13
11
|
import { PROCEDURE_REGISTRY } from "./registry.js";
|
package/src/procedures/invoke.ts
CHANGED
|
@@ -68,8 +68,10 @@ export type InvocationErrorCode =
|
|
|
68
68
|
* of `@procedures/fs`).
|
|
69
69
|
* - `host-not-allowed`: the input has a URL whose host the host does not allow, or a redirect goes
|
|
70
70
|
* to such a host (`createHttpModule({ allow })` of `@procedures/http`).
|
|
71
|
+
* - `topic-not-allowed`: the `allow` function of an `EventPublisher` refuses a topic of a
|
|
72
|
+
* subscription (`documentation/EVENTS.md`).
|
|
71
73
|
*/
|
|
72
|
-
export type ForbiddenReason = CapabilityRefusalReason | "outside-root" | "host-not-allowed";
|
|
74
|
+
export type ForbiddenReason = CapabilityRefusalReason | "outside-root" | "host-not-allowed" | "topic-not-allowed";
|
|
73
75
|
|
|
74
76
|
/** An error of the invocation itself. An error that the handler throws passes through unchanged. */
|
|
75
77
|
export class InvocationError extends Error {
|
|
@@ -79,7 +81,7 @@ export class InvocationError extends Error {
|
|
|
79
81
|
readonly retryable: boolean = false;
|
|
80
82
|
/**
|
|
81
83
|
* The reason of a `FORBIDDEN` error: `effects-undeclared`, `effects-exceeded`, `clearance`,
|
|
82
|
-
* `deadline`, `outside-root` or `
|
|
84
|
+
* `deadline`, `outside-root`, `host-not-allowed` or `topic-not-allowed`.
|
|
83
85
|
*/
|
|
84
86
|
readonly reason: ForbiddenReason | undefined;
|
|
85
87
|
|
package/src/procedures/ref.ts
CHANGED
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
* - `"$never"`: Never execute during hydration, pass as pure data (with its whole subtree)
|
|
23
23
|
* - `"$parent"`: Defer to parent procedure (pass as data)
|
|
24
24
|
* - `"someName"`: Defer to the runner that gives this name in its `contextStack`
|
|
25
|
-
* (`dag.traverse` gives "dag.traverse"
|
|
25
|
+
* (`dag.traverse` gives "dag.traverse". `client.tryCatch` gives no name: it is control flow,
|
|
26
|
+
* and it runs its `try` and `catch` operands itself.)
|
|
26
27
|
*
|
|
27
28
|
* A deferred ref is data at every depth: hydration of the input that holds it does not run it.
|
|
28
29
|
* The procedure that receives it runs it later (deep dive CORE-11).
|
|
@@ -681,7 +682,8 @@ export interface HydrateOptions {
|
|
|
681
682
|
|
|
682
683
|
/**
|
|
683
684
|
* The context names of the runner. A ref with `$when: "<name>"` runs only when this list has
|
|
684
|
-
* the name. A runner such as `
|
|
685
|
+
* the name. A runner such as `dag.traverse` gives its own name here when it hydrates its operand.
|
|
686
|
+
* (Control flow such as `client.tryCatch` runs its operands itself and gives no name.)
|
|
685
687
|
*/
|
|
686
688
|
contextStack?: string[] | undefined;
|
|
687
689
|
|
package/src/protocol.ts
CHANGED
|
@@ -46,12 +46,14 @@ export type ProtocolErrorCode =
|
|
|
46
46
|
| "TIMEOUT"
|
|
47
47
|
| "UNAVAILABLE"
|
|
48
48
|
| "CONNECTION_CLOSED"
|
|
49
|
-
| "CONNECTION_NOT_FOUND"
|
|
49
|
+
| "CONNECTION_NOT_FOUND"
|
|
50
|
+
| "TOPIC_NOT_FOUND";
|
|
50
51
|
|
|
51
52
|
/**
|
|
52
53
|
* The error codes of the protocol, on each face (HTTP, WebSocket, MCP, local). An error that a
|
|
53
|
-
* handler throws with its own `code` keeps it
|
|
54
|
-
* the status
|
|
54
|
+
* handler throws with its own `code` keeps it, for a value and for a stream (`errorCodeOf`). The
|
|
55
|
+
* HTTP status follows the code: the status of this list, the status of `ERROR_REGISTRY` for
|
|
56
|
+
* another known code, or 500.
|
|
55
57
|
*/
|
|
56
58
|
export const ERROR_CODES: Readonly<Record<ProtocolErrorCode, ErrorCodeInfo>> = {
|
|
57
59
|
NOT_FOUND: { http: 404, retryable: false, meaning: "No procedure has the path, or the host does not expose it" },
|
|
@@ -75,6 +77,7 @@ export const ERROR_CODES: Readonly<Record<ProtocolErrorCode, ErrorCodeInfo>> = {
|
|
|
75
77
|
UNAVAILABLE: { http: 503, retryable: true, meaning: "The server cannot take the call now: it stops, or it is busy" },
|
|
76
78
|
CONNECTION_CLOSED: { http: 503, retryable: true, meaning: "The connection closed before the call left" },
|
|
77
79
|
CONNECTION_NOT_FOUND: { http: 404, retryable: false, meaning: "The server has no connection with this id" },
|
|
80
|
+
TOPIC_NOT_FOUND: { http: 404, retryable: false, meaning: "The topic rule of the host has no topic with this name" },
|
|
78
81
|
};
|
|
79
82
|
|
|
80
83
|
/** True when a code is an error code of the protocol. */
|
|
@@ -82,6 +85,16 @@ export function isProtocolErrorCode(code: unknown): code is ProtocolErrorCode {
|
|
|
82
85
|
return typeof code === "string" && Object.prototype.hasOwnProperty.call(ERROR_CODES, code);
|
|
83
86
|
}
|
|
84
87
|
|
|
88
|
+
/**
|
|
89
|
+
* The code of an error that a handler or an invocation throws, on each face: its own code when
|
|
90
|
+
* it is a string, or `HANDLER_ERROR`. An invocation error has a code of this list. Before, a
|
|
91
|
+
* value lost the own code of a handler error, and a stream kept it (roadmap R30).
|
|
92
|
+
*/
|
|
93
|
+
export function errorCodeOf(error: unknown): string {
|
|
94
|
+
const code = (error as { code?: unknown } | null | undefined)?.code;
|
|
95
|
+
return typeof code === "string" ? code : "HANDLER_ERROR";
|
|
96
|
+
}
|
|
97
|
+
|
|
85
98
|
/** The HTTP status of an error code, or undefined for a code that is not in the list. */
|
|
86
99
|
export function httpStatusOf(code: unknown): number | undefined {
|
|
87
100
|
return isProtocolErrorCode(code) ? ERROR_CODES[code].http : undefined;
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* - `token`, `browser`: the checks of the network transports (`client-server` applies them).
|
|
15
15
|
* - `capability`, `readOnly`, `clearanceOrder`, `resources`, `admitItem`: the capability of each
|
|
16
16
|
* call and what the host gives for it (CAPABILITIES-2026-10).
|
|
17
|
+
* - `events`: the remote events of the host (`documentation/EVENTS.md`). The server pins
|
|
18
|
+
* `events.subscribe` with this topic rule, and its procedures get the bus as `ctx.bus`.
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
21
|
import type { Capability, ClearanceOrder, ProcedurePath, ResourcesOf } from "../procedures/types.js";
|
|
@@ -24,6 +26,7 @@ import { timeoutInterceptor } from "../procedures/interceptors.js";
|
|
|
24
26
|
import type { BrowserGuardOptions } from "./browser-guard.js";
|
|
25
27
|
import type { ProcedureRegistry } from "../procedures/registry.js";
|
|
26
28
|
import type { ProcedureServerOptions } from "./procedure-server.js";
|
|
29
|
+
import type { EventPublisherOptions } from "../events/publisher.js";
|
|
27
30
|
import { toMs, type Duration } from "../duration.js";
|
|
28
31
|
|
|
29
32
|
/** The procedures of a host that are tools: namespaces (first path segments), less some keys. */
|
|
@@ -86,6 +89,12 @@ export interface HostPolicyOptions {
|
|
|
86
89
|
resources?: ((invocation: Invocation) => ResourcesOf) | undefined;
|
|
87
90
|
/** The filter of the output that leaves the host. */
|
|
88
91
|
admitItem?: AdmitItem | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* The remote events of the host (`documentation/EVENTS.md`): the topic rule, with an optional
|
|
94
|
+
* bus, `allow` function and buffer. The default bus is the global bus, which is also the default
|
|
95
|
+
* `ctx.bus`. Without this option, the host does not serve `events.subscribe`.
|
|
96
|
+
*/
|
|
97
|
+
events?: EventPublisherOptions | undefined;
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
/** What a host lets its callers do. */
|
|
@@ -100,6 +109,8 @@ export interface HostPolicy {
|
|
|
100
109
|
readonly clearanceOrder: ClearanceOrder | undefined;
|
|
101
110
|
readonly resources: ((invocation: Invocation) => ResourcesOf) | undefined;
|
|
102
111
|
readonly admitItem: AdmitItem | undefined;
|
|
112
|
+
/** The remote events: each server of the policy makes its own publisher from these options. */
|
|
113
|
+
readonly events: EventPublisherOptions | undefined;
|
|
103
114
|
}
|
|
104
115
|
|
|
105
116
|
/** The capability function of a policy: the capability of the options, narrowed by `readOnly`. */
|
|
@@ -145,6 +156,7 @@ export function hostPolicy(options: HostPolicyOptions = {}): HostPolicy {
|
|
|
145
156
|
clearanceOrder: options.clearanceOrder,
|
|
146
157
|
resources: options.resources,
|
|
147
158
|
admitItem: options.admitItem,
|
|
159
|
+
events: options.events,
|
|
148
160
|
};
|
|
149
161
|
}
|
|
150
162
|
|
|
@@ -162,5 +174,7 @@ export function procedureServerOptions(policy: HostPolicy, registry?: ProcedureR
|
|
|
162
174
|
...(policy.clearanceOrder ? { clearanceOrder: policy.clearanceOrder } : {}),
|
|
163
175
|
...(policy.resources ? { resources: policy.resources } : {}),
|
|
164
176
|
...(policy.admitItem ? { admitItem: policy.admitItem } : {}),
|
|
177
|
+
// The remote events: the server makes its publisher and pins events.subscribe (EVENTS.md)
|
|
178
|
+
...(policy.events ? { events: policy.events } : {}),
|
|
165
179
|
};
|
|
166
180
|
}
|
|
@@ -24,6 +24,9 @@ import { IdempotencyCache, idempotencyKeyOf, isRepeatable, type IdempotencyOptio
|
|
|
24
24
|
import { optionalMs, type Duration } from "../duration.js";
|
|
25
25
|
import type { CollectionStorage } from "../procedures/collection/storage.js";
|
|
26
26
|
import { TIMER_DEFAULTS } from "../timers.js";
|
|
27
|
+
import { errorCodeOf } from "../protocol.js";
|
|
28
|
+
import { EventPublisher, type EventPublisherOptions } from "../events/publisher.js";
|
|
29
|
+
import { getGlobalEventBus } from "../events/bus.js";
|
|
27
30
|
|
|
28
31
|
// =============================================================================
|
|
29
32
|
// Types
|
|
@@ -118,6 +121,14 @@ export interface ProcedureServerOptions extends ServerOptions {
|
|
|
118
121
|
* `request.principal`.
|
|
119
122
|
*/
|
|
120
123
|
authenticate?: ((request: ServerRequest<unknown>) => Principal | undefined | Promise<Principal | undefined>) | undefined;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The remote events of the server (`documentation/EVENTS.md`): a bus and its topic rule, or an
|
|
127
|
+
* `EventPublisher`. The server pins the procedure `events.subscribe`, outside its registry, so a
|
|
128
|
+
* peer can subscribe to the topics. The procedures of the server get this bus as `ctx.bus`.
|
|
129
|
+
* `stop()` ends each subscription first, with `UNAVAILABLE`.
|
|
130
|
+
*/
|
|
131
|
+
events?: EventPublisherOptions | EventPublisher | undefined;
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
/**
|
|
@@ -198,7 +209,8 @@ function withRequestTimeout(
|
|
|
198
209
|
|
|
199
210
|
/**
|
|
200
211
|
* The error response of a failed invocation. An invocation error keeps its code
|
|
201
|
-
* (VALIDATION_ERROR, NOT_EXPOSED, ABORTED and so on). An error of the handler
|
|
212
|
+
* (VALIDATION_ERROR, NOT_EXPOSED, ABORTED and so on). An error of the handler keeps its own code
|
|
213
|
+
* when it is a string, as for a stream (`WIRE-PROTOCOL.md`). Without a code, it is HANDLER_ERROR.
|
|
202
214
|
*/
|
|
203
215
|
export function errorResponse(id: string, error: unknown): ServerResponse<unknown> {
|
|
204
216
|
// An error that says it is retryable stays retryable: before, every error lost the flag
|
|
@@ -214,7 +226,7 @@ export function errorResponse(id: string, error: unknown): ServerResponse<unknow
|
|
|
214
226
|
id,
|
|
215
227
|
status: {
|
|
216
228
|
type: "error",
|
|
217
|
-
code: error
|
|
229
|
+
code: errorCodeOf(error),
|
|
218
230
|
message: error instanceof Error ? error.message : String(error),
|
|
219
231
|
retryable: typeof retryable === "boolean" ? retryable : false,
|
|
220
232
|
},
|
|
@@ -286,6 +298,8 @@ export class ProcedureServer extends Server implements RepositoryProvider {
|
|
|
286
298
|
private readonly authenticate: ProcedureServerOptions["authenticate"];
|
|
287
299
|
/** The functions that `stop()` waits on: each one runs when no call is in flight */
|
|
288
300
|
private idleWaiters: Array<() => void> = [];
|
|
301
|
+
/** The publisher of the remote events, from the option `events`, or undefined. */
|
|
302
|
+
readonly publisher: EventPublisher | undefined;
|
|
289
303
|
|
|
290
304
|
constructor(options: ProcedureServerOptions = {}) {
|
|
291
305
|
super(options);
|
|
@@ -303,6 +317,9 @@ export class ProcedureServer extends Server implements RepositoryProvider {
|
|
|
303
317
|
this.idempotency = options.idempotency === false ? undefined : new IdempotencyCache(options.idempotency ?? {});
|
|
304
318
|
this.token = options.token;
|
|
305
319
|
this.authenticate = options.authenticate;
|
|
320
|
+
// The remote events: the server pins events.subscribe, as it pins host.health (EVENTS.md)
|
|
321
|
+
this.publisher = options.events instanceof EventPublisher ? options.events : options.events ? new EventPublisher(options.events) : undefined;
|
|
322
|
+
if (this.publisher) this.registerProcedure(this.publisher.procedure);
|
|
306
323
|
|
|
307
324
|
// Configure collections
|
|
308
325
|
if (options.collections) {
|
|
@@ -643,6 +660,8 @@ export class ProcedureServer extends Server implements RepositoryProvider {
|
|
|
643
660
|
clearanceOrder,
|
|
644
661
|
resources,
|
|
645
662
|
admitItem,
|
|
663
|
+
// The bus of the remote events is the bus of the procedures (EVENTS.md)
|
|
664
|
+
...(this.publisher ? { bus: this.publisher.bus } : {}),
|
|
646
665
|
});
|
|
647
666
|
if (output.kind === "stream") {
|
|
648
667
|
// The faces that collect a stream read this mark (roadmap R15)
|
|
@@ -721,23 +740,41 @@ export class ProcedureServer extends Server implements RepositoryProvider {
|
|
|
721
740
|
}
|
|
722
741
|
|
|
723
742
|
/**
|
|
724
|
-
*
|
|
743
|
+
* This function publishes an event on the bus of the server: the bus of the option `events`, or
|
|
744
|
+
* the global bus. Each subscription of the topic gets it (`documentation/EVENTS.md`).
|
|
745
|
+
*
|
|
746
|
+
* @param topic - The topic: a channel of the bus
|
|
747
|
+
* @param data - The data of the event. It crosses the wire as JSON.
|
|
748
|
+
*/
|
|
749
|
+
publish<T>(topic: string, data: T): void {
|
|
750
|
+
(this.publisher?.bus ?? getGlobalEventBus()).emit(topic, data);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* This method closes each storage one time, also a storage of two collections, and removes the
|
|
755
|
+
* storages from the server. A closed storage rejects each operation, so the server does not keep
|
|
756
|
+
* it. Before a new `start()`, give the storages again with `useStorage()`.
|
|
725
757
|
*/
|
|
726
758
|
async closeStorages(): Promise<void> {
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
);
|
|
730
|
-
|
|
759
|
+
const storages = new Set(this.storages.values());
|
|
760
|
+
this.storages.clear();
|
|
761
|
+
const results = await Promise.allSettled(Array.from(storages, (storage) => storage.close()));
|
|
762
|
+
const failed = results.find((result): result is PromiseRejectedResult => result.status === "rejected");
|
|
763
|
+
if (failed) throw failed.reason;
|
|
731
764
|
}
|
|
732
765
|
|
|
733
766
|
/**
|
|
734
|
-
*
|
|
767
|
+
* This method stops the server, then closes the storages and removes them (`closeStorages()`).
|
|
768
|
+
* The server can start again, with new storages from `useStorage()`. Before, a server that
|
|
769
|
+
* started again used the closed storages.
|
|
735
770
|
*/
|
|
736
771
|
override async stop(options: StopOptions = {}): Promise<void> {
|
|
737
772
|
const graceMs = optionalMs(options.grace, "grace") ?? TIMER_DEFAULTS.server.stopGraceMs;
|
|
738
773
|
// A graceful stop: new calls get UNAVAILABLE, and the calls in flight have the grace time
|
|
739
774
|
// to end (roadmap R14). Before, the transports aborted every call at once.
|
|
740
775
|
this.draining = true;
|
|
776
|
+
// A subscription has no end: it ends now, with a retryable UNAVAILABLE (EVENTS.md)
|
|
777
|
+
this.publisher?.end();
|
|
741
778
|
if (graceMs > 0 && this.inFlight > 0) {
|
|
742
779
|
await new Promise<void>((resolve) => {
|
|
743
780
|
const timer = setTimeout(resolve, graceMs);
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Procedure Announcement (Self-Registration)
|
|
3
|
-
*
|
|
4
|
-
* When a package with procedures is installed, it "announces" itself
|
|
5
|
-
* by writing to a registry file at the root of the project.
|
|
6
|
-
*
|
|
7
|
-
* This enables true bottom-up propagation:
|
|
8
|
-
* - Each package with procedures has `"postinstall": "client-registry announce"`
|
|
9
|
-
* - On install, it walks up to find the root and registers itself
|
|
10
|
-
* - `client-registry discover` reads this registry instead of scanning
|
|
11
|
-
*
|
|
12
|
-
* The registry file (.client-registry.json) accumulates entries from
|
|
13
|
-
* all packages as they're installed, making discovery instant.
|
|
14
|
-
*/
|
|
15
|
-
export interface AnnounceOptions {
|
|
16
|
-
/** Be verbose about what's happening */
|
|
17
|
-
verbose: boolean;
|
|
18
|
-
/** The folder to start from (default: the current folder) */
|
|
19
|
-
cwd?: string | undefined;
|
|
20
|
-
/**
|
|
21
|
-
* Add `client-registry announce` to the postinstall script of the package that depends on this one.
|
|
22
|
-
* This edits a package.json that belongs to the consumer, so it happens only when asked
|
|
23
|
-
* (`client-registry announce --propagate`). Default: false.
|
|
24
|
-
*/
|
|
25
|
-
propagate?: boolean | undefined;
|
|
26
|
-
}
|
|
27
|
-
interface RegistryEntry {
|
|
28
|
-
/** Path to procedures file relative to package */
|
|
29
|
-
proceduresPath: string;
|
|
30
|
-
/** When this entry was added */
|
|
31
|
-
registeredAt: string;
|
|
32
|
-
/** Package version */
|
|
33
|
-
version?: string;
|
|
34
|
-
}
|
|
35
|
-
interface Registry {
|
|
36
|
-
/** Schema version for future compatibility */
|
|
37
|
-
schemaVersion: 1;
|
|
38
|
-
/** Map of package name to registry entry */
|
|
39
|
-
packages: Record<string, RegistryEntry>;
|
|
40
|
-
}
|
|
41
|
-
declare const REGISTRY_FILENAME = ".client-registry.json";
|
|
42
|
-
/**
|
|
43
|
-
* Announce this package's procedures to the project registry.
|
|
44
|
-
*
|
|
45
|
-
* Called from a package's postinstall script to register its procedures.
|
|
46
|
-
*
|
|
47
|
-
* With `propagate: true` (`client-registry announce --propagate`), it also adds the postinstall script
|
|
48
|
-
* to the package that depends on this one, for transitive discovery:
|
|
49
|
-
* - When client is installed in package D, client's postinstall adds postinstall to D
|
|
50
|
-
* - When D is installed in C, D's postinstall adds postinstall to C
|
|
51
|
-
* - When C is installed in B, C's postinstall adds postinstall to B
|
|
52
|
-
*
|
|
53
|
-
* The propagation edits the package.json of the consumer, so it happens only when asked.
|
|
54
|
-
* Without it, each package that has its own postinstall script still registers itself.
|
|
55
|
-
*/
|
|
56
|
-
export declare function announce(options: AnnounceOptions): Promise<void>;
|
|
57
|
-
/**
|
|
58
|
-
* Remove this package from the registry (for uninstall).
|
|
59
|
-
*/
|
|
60
|
-
export declare function unannounce(options: AnnounceOptions): Promise<void>;
|
|
61
|
-
/**
|
|
62
|
-
* Get the registry for use by discover command.
|
|
63
|
-
*/
|
|
64
|
-
export declare function getRegistry(rootDir: string): Promise<Registry>;
|
|
65
|
-
export { REGISTRY_FILENAME };
|
|
66
|
-
export type { Registry, RegistryEntry };
|
|
67
|
-
//# sourceMappingURL=announce.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"announce.d.ts","sourceRoot":"","sources":["../../../src/procedures/cli/announce.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,MAAM,WAAW,eAAe;IAC9B,wCAAwC;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,6DAA6D;IAC7D,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACjC;AAED,UAAU,aAAa;IACrB,kDAAkD;IAClD,cAAc,EAAE,MAAM,CAAC;IACvB,gCAAgC;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,QAAQ;IAChB,8CAA8C;IAC9C,aAAa,EAAE,CAAC,CAAC;IACjB,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACzC;AAkFD,QAAA,MAAM,iBAAiB,0BAA0B,CAAC;AA0GlD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BtE;AAiKD;;GAEG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBxE;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAEpE;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAC7B,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC"}
|