@procedures/client 0.45.0 → 0.47.1
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 +29 -0
- package/README.md +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/http/server/transport.d.ts +5 -0
- package/dist/adapters/http/server/transport.d.ts.map +1 -1
- package/dist/adapters/http/server/transport.js +7 -0
- package/dist/adapters/http/server/transport.js.map +1 -1
- package/dist/adapters/local/client/transport.d.ts.map +1 -1
- package/dist/adapters/local/client/transport.js +12 -1
- package/dist/adapters/local/client/transport.js.map +1 -1
- package/dist/adapters/websocket/client/node.d.ts.map +1 -1
- package/dist/adapters/websocket/client/node.js +9 -2
- package/dist/adapters/websocket/client/node.js.map +1 -1
- package/dist/adapters/websocket/server/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/server/transport.js +14 -5
- package/dist/adapters/websocket/server/transport.js.map +1 -1
- package/dist/browser.d.ts +2 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +7 -3
- package/dist/browser.js.map +1 -1
- package/dist/client/client.d.ts +10 -1
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +46 -5
- package/dist/client/client.js.map +1 -1
- package/dist/client/errors/known-codes.d.ts +16 -0
- package/dist/client/errors/known-codes.d.ts.map +1 -0
- package/dist/client/errors/known-codes.js +20 -0
- package/dist/client/errors/known-codes.js.map +1 -0
- package/dist/client/validation/index.d.ts +1 -1
- package/dist/client/validation/index.js +1 -1
- package/dist/events/publisher.js +1 -1
- package/dist/events/publisher.js.map +1 -1
- package/dist/procedures/core/array.js +1 -1
- package/dist/procedures/core/array.js.map +1 -1
- package/dist/procedures/core/comparison.js +1 -1
- package/dist/procedures/core/comparison.js.map +1 -1
- package/dist/procedures/core/index.d.ts.map +1 -1
- package/dist/procedures/core/index.js +4 -2
- package/dist/procedures/core/index.js.map +1 -1
- package/dist/procedures/core/math.js +1 -1
- package/dist/procedures/core/math.js.map +1 -1
- package/dist/procedures/core/meta.js +1 -1
- package/dist/procedures/core/meta.js.map +1 -1
- package/dist/procedures/core/object.js +1 -1
- package/dist/procedures/core/object.js.map +1 -1
- package/dist/procedures/core/operands.d.ts.map +1 -1
- package/dist/procedures/core/operands.js +7 -2
- package/dist/procedures/core/operands.js.map +1 -1
- package/dist/procedures/core/outputs.d.ts +1 -1
- package/dist/procedures/core/outputs.d.ts.map +1 -1
- package/dist/procedures/core/outputs.js +1 -1
- package/dist/procedures/core/outputs.js.map +1 -1
- package/dist/procedures/core/string.js +1 -1
- package/dist/procedures/core/string.js.map +1 -1
- package/dist/procedures/core/type.js +1 -1
- package/dist/procedures/core/type.js.map +1 -1
- package/dist/procedures/define-procedure.d.ts.map +1 -1
- package/dist/procedures/define-procedure.js +28 -12
- package/dist/procedures/define-procedure.js.map +1 -1
- package/dist/procedures/index.d.ts +6 -5
- package/dist/procedures/index.d.ts.map +1 -1
- package/dist/procedures/index.js +6 -4
- package/dist/procedures/index.js.map +1 -1
- package/dist/procedures/invoke.d.ts.map +1 -1
- package/dist/procedures/invoke.js +14 -1
- package/dist/procedures/invoke.js.map +1 -1
- package/dist/procedures/module.d.ts +34 -0
- package/dist/procedures/module.d.ts.map +1 -1
- package/dist/procedures/module.js +90 -0
- package/dist/procedures/module.js.map +1 -1
- package/dist/procedures/ref.d.ts +10 -1
- package/dist/procedures/ref.d.ts.map +1 -1
- package/dist/procedures/ref.js +64 -0
- package/dist/procedures/ref.js.map +1 -1
- package/dist/procedures/registry.d.ts +64 -0
- package/dist/procedures/registry.d.ts.map +1 -1
- package/dist/procedures/registry.js +60 -0
- package/dist/procedures/registry.js.map +1 -1
- package/dist/procedures/segments.d.ts.map +1 -1
- package/dist/procedures/segments.js +13 -3
- package/dist/procedures/segments.js.map +1 -1
- package/dist/procedures/types.d.ts +6 -0
- package/dist/procedures/types.d.ts.map +1 -1
- package/dist/procedures/types.js.map +1 -1
- package/dist/server/error-policy.d.ts.map +1 -1
- package/dist/server/error-policy.js +2 -1
- package/dist/server/error-policy.js.map +1 -1
- package/dist/server/host-procedures.d.ts.map +1 -1
- package/dist/server/host-procedures.js +12 -8
- package/dist/server/host-procedures.js.map +1 -1
- package/dist/server/procedure-server.d.ts +11 -0
- package/dist/server/procedure-server.d.ts.map +1 -1
- package/dist/server/procedure-server.js +18 -0
- package/dist/server/procedure-server.js.map +1 -1
- package/dist/server/server.d.ts +5 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +9 -1
- package/dist/server/server.js.map +1 -1
- package/dist/testing/transport-contract.js +1 -1
- package/dist/testing/transport-contract.js.map +1 -1
- package/dist/tracing/traceparent.d.ts +6 -0
- package/dist/tracing/traceparent.d.ts.map +1 -1
- package/dist/tracing/traceparent.js +27 -6
- package/dist/tracing/traceparent.js.map +1 -1
- package/package.json +4 -4
- package/src/adapters/http/server/transport.ts +8 -0
- package/src/adapters/local/client/transport.ts +11 -1
- package/src/adapters/websocket/client/node.ts +11 -2
- package/src/adapters/websocket/server/transport.ts +17 -5
- package/src/browser.ts +11 -0
- package/src/client/client.ts +46 -4
- package/src/client/errors/known-codes.ts +22 -0
- package/src/client/validation/index.ts +1 -1
- package/src/events/publisher.ts +1 -1
- package/src/procedures/core/array.ts +1 -1
- package/src/procedures/core/comparison.ts +1 -1
- package/src/procedures/core/index.ts +4 -1
- package/src/procedures/core/math.ts +1 -1
- package/src/procedures/core/meta.ts +1 -1
- package/src/procedures/core/object.ts +1 -1
- package/src/procedures/core/operands.ts +8 -1
- package/src/procedures/core/outputs.ts +1 -1
- package/src/procedures/core/string.ts +1 -1
- package/src/procedures/core/type.ts +1 -1
- package/src/procedures/define-procedure.ts +25 -10
- package/src/procedures/index.ts +7 -2
- package/src/procedures/invoke.ts +15 -1
- package/src/procedures/module.ts +113 -2
- package/src/procedures/ref.ts +58 -1
- package/src/procedures/registry.ts +99 -0
- package/src/procedures/segments.ts +12 -3
- package/src/procedures/types.ts +6 -0
- package/src/server/error-policy.ts +2 -1
- package/src/server/host-procedures.ts +12 -8
- package/src/server/procedure-server.ts +18 -0
- package/src/server/server.ts +10 -1
- package/src/testing/transport-contract.ts +1 -1
- package/src/tracing/traceparent.ts +28 -5
package/src/procedures/module.ts
CHANGED
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
* rule of a server all use the registry of the invocation (`ctx.registry`).
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { PROCEDURE_REGISTRY, ProcedureRegistry, RegistryError } from "./registry.js";
|
|
21
|
+
import { PROCEDURE_REGISTRY, ProcedureRegistry, RegistryError, type RegistryResolver } from "./registry.js";
|
|
22
22
|
import { coreProcedures } from "./core/index.js";
|
|
23
23
|
import { metaProcedures as definitionProcedures } from "./define-procedure.js";
|
|
24
|
-
import { pathToKey, type AnyProcedure, type ProcedureModule, type RegistrationOptions } from "./types.js";
|
|
24
|
+
import { pathToKey, type AnyProcedure, type ProcedureModule, type ProcedurePath, type RegistrationOptions } from "./types.js";
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* The procedures of `@procedures/client`: the control flow (`client.chain`, `client.map` and the
|
|
@@ -139,6 +139,117 @@ export function createRegistry(...modules: ModuleInput[]): ProcedureRegistry {
|
|
|
139
139
|
return registerModules(new ProcedureRegistry(), [clientModule, ...modules]);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/** The options of `createLazyRegistry()` (roadmap O93). */
|
|
143
|
+
export interface LazyRegistryOptions {
|
|
144
|
+
/** The modules that the registry registers at once, after `clientModule`: the core procedures. */
|
|
145
|
+
readonly modules?: readonly ModuleInput[] | undefined;
|
|
146
|
+
/**
|
|
147
|
+
* The index: the key of each path (`pathToKey()`) and the names of the packages that give it.
|
|
148
|
+
* Two packages can give one path.
|
|
149
|
+
*/
|
|
150
|
+
readonly index: Readonly<Record<string, readonly string[]>>;
|
|
151
|
+
/** This function gives the module of a package, or undefined for a package without a build. */
|
|
152
|
+
readonly load: (name: string) => Promise<ProcedureModule | undefined>;
|
|
153
|
+
/**
|
|
154
|
+
* The names of all packages, in the order of the eager load. `loadAll()` loads them in this
|
|
155
|
+
* order. The default is the names of the index.
|
|
156
|
+
*/
|
|
157
|
+
readonly packages?: readonly string[] | undefined;
|
|
158
|
+
/** This function gets each warning. The default is `console.warn`. */
|
|
159
|
+
readonly warn?: ((message: string) => void) | undefined;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** The first line of the message of an error. */
|
|
163
|
+
function firstLine(error: unknown): string {
|
|
164
|
+
return (error instanceof Error ? error.message : String(error)).split("\n")[0] ?? "";
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* This function makes a lazy registry (roadmap O93). The registry has the procedures of
|
|
169
|
+
* `clientModule` and of `options.modules` at once. A miss of `resolve()` loads only the packages
|
|
170
|
+
* that the index names for the path.
|
|
171
|
+
*
|
|
172
|
+
* - Concurrent resolves of one package start one load.
|
|
173
|
+
* - A path that is not in the index loads each package, with one warning for each path.
|
|
174
|
+
* - A package that did not load gives a warning. The resolve of a path that is still missing
|
|
175
|
+
* then rejects with a `RegistryError`, with the load error as its `cause`. A later resolve tries
|
|
176
|
+
* that package again.
|
|
177
|
+
* - The registration goes through `registerModules()`: the modules that a module requires come
|
|
178
|
+
* with it, and the same handler from two modules registers one time.
|
|
179
|
+
* - `loadAll()` loads each package in the order of `options.packages`.
|
|
180
|
+
*/
|
|
181
|
+
export function createLazyRegistry(options: LazyRegistryOptions): ProcedureRegistry {
|
|
182
|
+
const warn = options.warn ?? ((message: string): void => console.warn(message));
|
|
183
|
+
const packages = options.packages ?? [...new Set(Object.values(options.index).flat())];
|
|
184
|
+
const loaded = new Set<string>();
|
|
185
|
+
const inFlight = new Map<string, Promise<void>>();
|
|
186
|
+
const warnedPaths = new Set<string>();
|
|
187
|
+
|
|
188
|
+
function loadPackage(name: string): Promise<void> {
|
|
189
|
+
if (loaded.has(name)) return Promise.resolve();
|
|
190
|
+
const current = inFlight.get(name);
|
|
191
|
+
if (current) return current;
|
|
192
|
+
const started = (async (): Promise<void> => {
|
|
193
|
+
const module = await options.load(name);
|
|
194
|
+
if (module) registerModules(registry, [module]);
|
|
195
|
+
})();
|
|
196
|
+
inFlight.set(name, started);
|
|
197
|
+
// A failure is not recorded as a load: the next resolve tries the package again
|
|
198
|
+
started.then(
|
|
199
|
+
() => {
|
|
200
|
+
loaded.add(name);
|
|
201
|
+
inFlight.delete(name);
|
|
202
|
+
},
|
|
203
|
+
(error: unknown) => {
|
|
204
|
+
inFlight.delete(name);
|
|
205
|
+
warn(`the procedures of ${name} did not load: ${firstLine(error)}`);
|
|
206
|
+
},
|
|
207
|
+
);
|
|
208
|
+
return started;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const resolver: RegistryResolver = {
|
|
212
|
+
async resolve(path: ProcedurePath): Promise<void> {
|
|
213
|
+
const key = pathToKey(path);
|
|
214
|
+
const named = Object.hasOwn(options.index, key) ? options.index[key] : undefined;
|
|
215
|
+
let targets: readonly string[];
|
|
216
|
+
if (named !== undefined && named.length > 0) {
|
|
217
|
+
targets = named;
|
|
218
|
+
} else {
|
|
219
|
+
if (!warnedPaths.has(key)) {
|
|
220
|
+
warnedPaths.add(key);
|
|
221
|
+
warn(`the index has no package for ${key}: each package loads`);
|
|
222
|
+
}
|
|
223
|
+
targets = packages;
|
|
224
|
+
}
|
|
225
|
+
let failure: { name: string; error: unknown } | undefined;
|
|
226
|
+
// One package after the other, in the order of the index: the order of the eager load
|
|
227
|
+
for (const name of targets) {
|
|
228
|
+
try {
|
|
229
|
+
await loadPackage(name);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
failure ??= { name, error };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (failure === undefined || registry.has(path)) return;
|
|
235
|
+
const cause = failure.error instanceof Error ? failure.error : new Error(String(failure.error));
|
|
236
|
+
throw new RegistryError(`The procedure ${key} did not load: the package ${failure.name} did not load: ${firstLine(cause)}`, path, cause);
|
|
237
|
+
},
|
|
238
|
+
async loadAll(): Promise<void> {
|
|
239
|
+
for (const name of packages) {
|
|
240
|
+
try {
|
|
241
|
+
await loadPackage(name);
|
|
242
|
+
} catch {
|
|
243
|
+
// loadPackage() gave the warning. The other packages still load, as in the eager load.
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const registry = new ProcedureRegistry({ resolver });
|
|
250
|
+
return registerModules(registry, [clientModule, ...(options.modules ?? [])]);
|
|
251
|
+
}
|
|
252
|
+
|
|
142
253
|
/**
|
|
143
254
|
* This function registers the procedures of `clientModule` in a registry: the global registry by
|
|
144
255
|
* default. A procedure that the registry has already stays. The `./register` entry of the package
|
package/src/procedures/ref.ts
CHANGED
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
* ```
|
|
58
58
|
*/
|
|
59
59
|
|
|
60
|
-
import type
|
|
60
|
+
import { pathToKey, type ProcedurePath } from "./types.js";
|
|
61
|
+
import type { ResolvingRegistry } from "./registry.js";
|
|
61
62
|
|
|
62
63
|
// =============================================================================
|
|
63
64
|
// Procedure Reference Constants
|
|
@@ -854,6 +855,62 @@ export function rawInputRule(
|
|
|
854
855
|
|
|
855
856
|
const RAW_INPUT_RULES = new WeakMap<object, (path: ProcedurePath) => boolean>();
|
|
856
857
|
|
|
858
|
+
/** The `$proc` paths of a value that the registry does not have yet, by key. */
|
|
859
|
+
function missingRefPaths(registry: ResolvingRegistry, value: unknown): ProcedurePath[] {
|
|
860
|
+
const missing = new Map<string, ProcedurePath>();
|
|
861
|
+
const seen = new Set<object>();
|
|
862
|
+
const visit = (node: unknown): void => {
|
|
863
|
+
if (node === null || typeof node !== "object" || seen.has(node)) return;
|
|
864
|
+
seen.add(node);
|
|
865
|
+
if (isLiteral(node)) return;
|
|
866
|
+
if (Array.isArray(node)) {
|
|
867
|
+
for (const item of node) visit(item);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
if (isAnyProcedureRef(node)) {
|
|
871
|
+
try {
|
|
872
|
+
const ref = normalizeRef(node);
|
|
873
|
+
const path: unknown = ref.path;
|
|
874
|
+
if (Array.isArray(path) && path.length > 0 && path.every((segment) => typeof segment === "string") && registry.get(path) === undefined) {
|
|
875
|
+
missing.set(pathToKey(path), path);
|
|
876
|
+
}
|
|
877
|
+
visit(ref.input);
|
|
878
|
+
} catch {
|
|
879
|
+
// The run reports a ref that is not correct
|
|
880
|
+
}
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
if (!isPlainObject(node)) return;
|
|
884
|
+
for (const field of Object.values(node)) visit(field);
|
|
885
|
+
};
|
|
886
|
+
visit(value);
|
|
887
|
+
return [...missing.values()];
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* This function loads the procedures of the `$proc` paths of a value into a lazy registry (roadmap
|
|
892
|
+
* O93). A run starts it before it reads the raw-input rule, because the rule is sync and reads only
|
|
893
|
+
* the procedures that have loaded. The function gives undefined, with no wait, for an eager
|
|
894
|
+
* registry and for a value whose paths the registry has. A package that does not load does not
|
|
895
|
+
* stop the run: the call of its path reports the error.
|
|
896
|
+
*/
|
|
897
|
+
export function preloadRefPaths(registry: ResolvingRegistry | undefined, value: unknown): Promise<void> | undefined {
|
|
898
|
+
if (registry?.lazy !== true || registry.resolve === undefined) return undefined;
|
|
899
|
+
const missing = missingRefPaths(registry, value);
|
|
900
|
+
if (missing.length === 0) return undefined;
|
|
901
|
+
const resolve = registry.resolve.bind(registry);
|
|
902
|
+
return (async (): Promise<void> => {
|
|
903
|
+
// One path after the other: the order of the registrations does not depend on the time of each load
|
|
904
|
+
for (const path of missing) {
|
|
905
|
+
try {
|
|
906
|
+
await resolve(path);
|
|
907
|
+
} catch {
|
|
908
|
+
// The call of the path reports the load error
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
})();
|
|
912
|
+
}
|
|
913
|
+
|
|
857
914
|
// =============================================================================
|
|
858
915
|
// Template Extraction
|
|
859
916
|
// =============================================================================
|
|
@@ -76,6 +76,60 @@ function samePath(a: ProcedurePath, b: ProcedurePath): boolean {
|
|
|
76
76
|
return a.length === b.length && a.every((segment, index) => segment === b[index]);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
// =============================================================================
|
|
80
|
+
// Lazy Resolution (roadmap O93)
|
|
81
|
+
// =============================================================================
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The resolver of a lazy registry: it loads procedures into the registry on demand.
|
|
85
|
+
* `createLazyRegistry()` gives one. An eager registry has no resolver.
|
|
86
|
+
*/
|
|
87
|
+
export interface RegistryResolver {
|
|
88
|
+
/**
|
|
89
|
+
* This method loads the packages that give the path. It rejects when a package did not load.
|
|
90
|
+
* A later call tries that package again.
|
|
91
|
+
*/
|
|
92
|
+
resolve(path: ProcedurePath): Promise<void>;
|
|
93
|
+
/** This method loads each package. A package that did not load gives a warning, not a rejection. */
|
|
94
|
+
loadAll(): Promise<void>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The options of `new ProcedureRegistry()`. */
|
|
98
|
+
export interface ProcedureRegistryOptions {
|
|
99
|
+
/**
|
|
100
|
+
* The resolver of a lazy registry. Without it, the registry is eager: `resolve()` gives what
|
|
101
|
+
* `get()` gives, and `loadAll()` does nothing.
|
|
102
|
+
*/
|
|
103
|
+
resolver?: RegistryResolver | undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The reads of a registry that `resolveIn()` and `loadAllIn()` use. A lazy registry has all of them. */
|
|
107
|
+
export interface ResolvingRegistry {
|
|
108
|
+
get(path: ProcedurePath): AnyProcedure | undefined;
|
|
109
|
+
readonly lazy?: boolean;
|
|
110
|
+
resolve?(path: ProcedurePath): Promise<AnyProcedure | undefined>;
|
|
111
|
+
loadAll?(): Promise<void>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* This function gives the procedure at a path of a registry. On a miss, a lazy registry loads the
|
|
116
|
+
* packages of the path. An eager registry gives what `get()` gives.
|
|
117
|
+
*/
|
|
118
|
+
export async function resolveIn(registry: ResolvingRegistry, path: ProcedurePath): Promise<AnyProcedure | undefined> {
|
|
119
|
+
const found = registry.get(path);
|
|
120
|
+
if (found !== undefined || registry.lazy !== true || registry.resolve === undefined) return found;
|
|
121
|
+
return registry.resolve(path);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* This function loads each package of a lazy registry. A procedure that enumerates its registry
|
|
126
|
+
* (`procedure.list`, `procedure.manifest`, `mcp.listTools`) starts it first. On an eager
|
|
127
|
+
* registry, it does nothing.
|
|
128
|
+
*/
|
|
129
|
+
export async function loadAllIn(registry: Pick<ResolvingRegistry, "lazy" | "loadAll">): Promise<void> {
|
|
130
|
+
if (registry.lazy === true && registry.loadAll !== undefined) await registry.loadAll();
|
|
131
|
+
}
|
|
132
|
+
|
|
79
133
|
// =============================================================================
|
|
80
134
|
// Procedure Registry
|
|
81
135
|
// =============================================================================
|
|
@@ -113,6 +167,17 @@ export class ProcedureRegistry {
|
|
|
113
167
|
/** Registered module names for debugging */
|
|
114
168
|
private readonly registeredModules = new Set<string>();
|
|
115
169
|
|
|
170
|
+
/** The resolver of a lazy registry, or undefined for an eager registry (roadmap O93) */
|
|
171
|
+
private readonly resolver: RegistryResolver | undefined;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* This constructor makes an eager registry. With `options.resolver`, the registry is lazy: a
|
|
175
|
+
* miss of `resolve()` loads the packages of the path.
|
|
176
|
+
*/
|
|
177
|
+
constructor(options: ProcedureRegistryOptions = {}) {
|
|
178
|
+
this.resolver = options.resolver;
|
|
179
|
+
}
|
|
180
|
+
|
|
116
181
|
// ---------------------------------------------------------------------------
|
|
117
182
|
// Registration
|
|
118
183
|
// ---------------------------------------------------------------------------
|
|
@@ -278,6 +343,40 @@ export class ProcedureRegistry {
|
|
|
278
343
|
return this.procedures.size;
|
|
279
344
|
}
|
|
280
345
|
|
|
346
|
+
// ---------------------------------------------------------------------------
|
|
347
|
+
// Lazy Resolution (roadmap O93)
|
|
348
|
+
// ---------------------------------------------------------------------------
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* This property is true for a lazy registry. The sync reads (`get`, `has`, `getAll`,
|
|
352
|
+
* `getByPrefix`, `size`) of a lazy registry give only the procedures that have loaded.
|
|
353
|
+
*/
|
|
354
|
+
get lazy(): boolean {
|
|
355
|
+
return this.resolver !== undefined;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* This method gives the procedure at a path. A registered path gives its procedure at once. On
|
|
360
|
+
* a miss, a lazy registry loads the packages of the path first. An eager registry gives undefined.
|
|
361
|
+
*
|
|
362
|
+
* @throws RegistryError when the path is still missing and a package of the path did not load.
|
|
363
|
+
* The load error is the `cause`.
|
|
364
|
+
*/
|
|
365
|
+
async resolve(path: ProcedurePath): Promise<AnyProcedure | undefined> {
|
|
366
|
+
const found = this.get(path);
|
|
367
|
+
if (found || !this.resolver) return found;
|
|
368
|
+
await this.resolver.resolve(path);
|
|
369
|
+
return this.get(path);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* This method loads each package of a lazy registry. After it, the sync reads give each
|
|
374
|
+
* procedure. On an eager registry, it does nothing.
|
|
375
|
+
*/
|
|
376
|
+
async loadAll(): Promise<void> {
|
|
377
|
+
if (this.resolver) await this.resolver.loadAll();
|
|
378
|
+
}
|
|
379
|
+
|
|
281
380
|
/**
|
|
282
381
|
* Get all registered module names.
|
|
283
382
|
*/
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
|
26
26
|
export function encodeSegment(segment: string): string {
|
|
27
|
-
|
|
27
|
+
// Almost each segment has no `.` and no `%`: a test costs less than a replace with a callback (roadmap O90)
|
|
28
|
+
return /[%.]/.test(segment) ? segment.replace(/[%.]/g, (char) => (char === "%" ? "%25" : "%2E")) : segment;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
/**
|
|
@@ -39,7 +40,8 @@ export function encodeSegment(segment: string): string {
|
|
|
39
40
|
* ```
|
|
40
41
|
*/
|
|
41
42
|
export function decodeSegment(segment: string): string {
|
|
42
|
-
|
|
43
|
+
// Almost each segment has no `%`: a test costs less than a replace with a callback (roadmap O90)
|
|
44
|
+
return segment.includes("%") ? segment.replace(/%(25|2[Ee])/g, (_sequence, code: string) => (code === "25" ? "%" : ".")) : segment;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
/**
|
|
@@ -56,7 +58,14 @@ export function decodeSegment(segment: string): string {
|
|
|
56
58
|
export function segmentsToTopic(segments: readonly string[]): string {
|
|
57
59
|
// A segment that is not a string becomes text as with join(): null and undefined give "" (a
|
|
58
60
|
// caller can check a path that is not valid, for example the compiler)
|
|
59
|
-
|
|
61
|
+
// The loop makes no array for each key: a key is made for each step of a call (roadmap O90)
|
|
62
|
+
let topic = "";
|
|
63
|
+
for (let index = 0; index < segments.length; index++) {
|
|
64
|
+
const segment: unknown = segments[index];
|
|
65
|
+
if (index > 0) topic += ".";
|
|
66
|
+
topic += encodeSegment(segment === null || segment === undefined ? "" : String(segment));
|
|
67
|
+
}
|
|
68
|
+
return topic;
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
/**
|
package/src/procedures/types.ts
CHANGED
|
@@ -442,6 +442,12 @@ export interface ProcedureRegistryLike {
|
|
|
442
442
|
getAll(): AnyProcedure[];
|
|
443
443
|
register(procedure: AnyProcedure, options?: RegistrationOptions): void;
|
|
444
444
|
unregister(path: ProcedurePath): boolean;
|
|
445
|
+
/** This property is true for a lazy registry (roadmap O93). Its sync reads give only the procedures that have loaded. */
|
|
446
|
+
readonly lazy?: boolean;
|
|
447
|
+
/** This method gives the procedure at a path. A lazy registry loads the packages of the path on a miss. */
|
|
448
|
+
resolve?(path: ProcedurePath): Promise<AnyProcedure | undefined>;
|
|
449
|
+
/** This method loads each package of a lazy registry. A procedure that enumerates the registry starts it first. */
|
|
450
|
+
loadAll?(): Promise<void>;
|
|
445
451
|
}
|
|
446
452
|
|
|
447
453
|
/**
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
|
|
23
23
|
import type { ProcedurePath } from "../procedures/types.js";
|
|
24
24
|
import { closableUnread } from "../procedures/invoke.js";
|
|
25
|
-
|
|
25
|
+
// The codes only: the texts of ERROR_REGISTRY stay out of a bundle of the server (roadmap O95)
|
|
26
|
+
import { isKnownCode as isKnownErrorCode } from "../client/errors/known-codes.js";
|
|
26
27
|
import { errorCodeOf } from "../protocol.js";
|
|
27
28
|
import { errorResponse, realErrorOf } from "./error-response.js";
|
|
28
29
|
import { methodToPath } from "./method.js";
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* checks that have no token.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import
|
|
12
|
+
import * as z from "zod";
|
|
13
13
|
import { createProcedure } from "../procedures/define.js";
|
|
14
14
|
import { PURE_EFFECTS } from "../procedures/effects.js";
|
|
15
15
|
import type { AnyProcedure } from "../procedures/types.js";
|
|
@@ -69,13 +69,17 @@ export function hostProcedures(source: HostSource): AnyProcedure[] {
|
|
|
69
69
|
.input(z.object({}))
|
|
70
70
|
.output(healthOutput)
|
|
71
71
|
.meta({ description: "The health of the host: its state, its uptime and its calls in flight", effects: PURE_EFFECTS })
|
|
72
|
-
.handler(async () =>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
.handler(async () => {
|
|
73
|
+
// A lazy registry loads each package first: the count has each procedure (roadmap O93)
|
|
74
|
+
await source.server.loadAll();
|
|
75
|
+
return {
|
|
76
|
+
status: source.server.stopping ? ("stopping" as const) : ("ok" as const),
|
|
77
|
+
uptimeMs: Date.now() - source.startedAt,
|
|
78
|
+
procedures: source.server.procedureCount,
|
|
79
|
+
callsInFlight: source.server.inFlight(),
|
|
80
|
+
peerId: source.id,
|
|
81
|
+
};
|
|
82
|
+
})
|
|
79
83
|
.build();
|
|
80
84
|
|
|
81
85
|
const info = createProcedure()
|
|
@@ -835,6 +835,16 @@ export class ProcedureServer extends Server implements RepositoryProvider, StopP
|
|
|
835
835
|
return procedure ? (request) => this.invoke(procedure, request) : null;
|
|
836
836
|
}
|
|
837
837
|
|
|
838
|
+
/**
|
|
839
|
+
* A miss of a lazy registry loads the packages of the path, then the server looks up the
|
|
840
|
+
* handler again (roadmap O93). An eager registry, or a server that does not serve its registry,
|
|
841
|
+
* finds no handler here.
|
|
842
|
+
*/
|
|
843
|
+
protected override findLateHandler(method: Method, request: ServerRequest<unknown>): Promise<ServerHandler | null> | null {
|
|
844
|
+
if (!this.servesRegistry || !this.procedureRegistry.lazy) return null;
|
|
845
|
+
return this.procedureRegistry.resolve(methodToPath(method)).then(() => this.findHandler(method, request));
|
|
846
|
+
}
|
|
847
|
+
|
|
838
848
|
/**
|
|
839
849
|
* Run a procedure for a request. A keyed call to a procedure that is not repeatable runs one
|
|
840
850
|
* time for its key (R1).
|
|
@@ -1207,6 +1217,14 @@ export class ProcedureServer extends Server implements RepositoryProvider, StopP
|
|
|
1207
1217
|
return this.servedKeys().size;
|
|
1208
1218
|
}
|
|
1209
1219
|
|
|
1220
|
+
/**
|
|
1221
|
+
* This method loads each package of a lazy registry that the server serves (roadmap O93). Then
|
|
1222
|
+
* `procedureCount` and the lists count each procedure. For an eager registry it does nothing.
|
|
1223
|
+
*/
|
|
1224
|
+
async loadAll(): Promise<void> {
|
|
1225
|
+
if (this.servesRegistry && this.procedureRegistry.lazy) await this.procedureRegistry.loadAll();
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1210
1228
|
/**
|
|
1211
1229
|
* Check if the server serves a procedure now.
|
|
1212
1230
|
*
|
package/src/server/server.ts
CHANGED
|
@@ -309,7 +309,8 @@ export class Server {
|
|
|
309
309
|
|
|
310
310
|
try {
|
|
311
311
|
// Find handler
|
|
312
|
-
|
|
312
|
+
// A miss can load the procedure first (a lazy registry, roadmap O93). A hit does not wait.
|
|
313
|
+
const handler = this.findHandler(request.method, request) ?? (await this.findLateHandler(request.method, request));
|
|
313
314
|
if (!handler) {
|
|
314
315
|
throw new HandlerNotFoundError(request.method);
|
|
315
316
|
}
|
|
@@ -363,6 +364,14 @@ export class Server {
|
|
|
363
364
|
return null;
|
|
364
365
|
}
|
|
365
366
|
|
|
367
|
+
/**
|
|
368
|
+
* This method finds a handler after `findHandler()` found none. The default finds none at once.
|
|
369
|
+
* `ProcedureServer` loads the procedure of a lazy registry here (roadmap O93).
|
|
370
|
+
*/
|
|
371
|
+
protected findLateHandler(_method: Method, _request: ServerRequest<unknown>): Promise<ServerHandler | null> | null {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
|
|
366
375
|
/**
|
|
367
376
|
* Check if a method matches a matcher.
|
|
368
377
|
*/
|
|
@@ -20,12 +20,35 @@ export interface TraceContext {
|
|
|
20
20
|
readonly sampled: boolean;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* The size of the pool of random bytes. One fill gives 256 trace ids or 512 span ids. Before
|
|
25
|
+
* (roadmap O94), each id called `getRandomValues` for 8 or 16 bytes: in a chain of 200 steps, the
|
|
26
|
+
* random ids took about 10 % of the per-step time of the host.
|
|
27
|
+
*/
|
|
28
|
+
export const RANDOM_POOL_BYTES = 4096;
|
|
29
|
+
|
|
30
|
+
/** The two hex digits of each byte value. */
|
|
31
|
+
const HEX = Array.from({ length: 256 }, (_, value) => value.toString(16).padStart(2, "0"));
|
|
32
|
+
|
|
33
|
+
const pool = new Uint8Array(RANDOM_POOL_BYTES);
|
|
34
|
+
// The pool starts empty: the first id fills it
|
|
35
|
+
let next = RANDOM_POOL_BYTES;
|
|
36
|
+
|
|
37
|
+
/** This function fills the pool with new random bytes. Each byte goes to one id only. */
|
|
38
|
+
function refill(): void {
|
|
25
39
|
const crypto = (globalThis as { crypto?: { getRandomValues?: (array: Uint8Array) => Uint8Array } }).crypto;
|
|
26
|
-
if (crypto?.getRandomValues) crypto.getRandomValues(
|
|
27
|
-
else for (let index = 0; index <
|
|
28
|
-
|
|
40
|
+
if (crypto?.getRandomValues) crypto.getRandomValues(pool);
|
|
41
|
+
else for (let index = 0; index < RANDOM_POOL_BYTES; index++) pool[index] = Math.floor(Math.random() * 256);
|
|
42
|
+
next = 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** This function gives `bytes` random bytes of the pool as hex digits. `bytes` is 16 or less. */
|
|
46
|
+
function randomHex(bytes: number): string {
|
|
47
|
+
if (next + bytes > RANDOM_POOL_BYTES) refill();
|
|
48
|
+
let hex = "";
|
|
49
|
+
for (let index = next; index < next + bytes; index++) hex += HEX[pool[index]!]!;
|
|
50
|
+
next += bytes;
|
|
51
|
+
return hex;
|
|
29
52
|
}
|
|
30
53
|
|
|
31
54
|
/** This function makes a new trace id: 32 random hex digits. */
|