@procedures/client 0.46.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 +23 -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
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
import type { IncomingMessage } from "http";
|
|
15
15
|
import type { Duplex } from "stream";
|
|
16
|
-
import {
|
|
16
|
+
import { createRequire } from "node:module";
|
|
17
|
+
import type { WebSocketServer, WebSocket } from "ws";
|
|
17
18
|
import type { ServerTransport, ServerRequest } from "../../../server/types.js";
|
|
18
19
|
import type { Message, ResponseItem, Transport } from "../../../client/types.js";
|
|
19
20
|
import { pathToMethod } from "../../../server/method.js";
|
|
@@ -44,6 +45,17 @@ import {
|
|
|
44
45
|
type Subscription,
|
|
45
46
|
} from "../../../events/remote.js";
|
|
46
47
|
|
|
48
|
+
/** The `readyState` of an open socket: `WebSocket.OPEN` of `ws`. */
|
|
49
|
+
const OPEN = 1;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* This function gives the `WebSocketServer` class of `ws`. The start of the first server loads the
|
|
53
|
+
* package, not the import of this file: a process without a socket does not load `ws` (roadmap O93).
|
|
54
|
+
*/
|
|
55
|
+
function webSocketServerClass(): typeof WebSocketServer {
|
|
56
|
+
return (createRequire(import.meta.url)("ws") as { WebSocketServer: typeof WebSocketServer }).WebSocketServer;
|
|
57
|
+
}
|
|
58
|
+
|
|
47
59
|
/**
|
|
48
60
|
* WebSocket server transport adapter.
|
|
49
61
|
*
|
|
@@ -192,7 +204,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
192
204
|
this.intakeStopped = false;
|
|
193
205
|
if (this.wss) return;
|
|
194
206
|
// The "no server" mode: upgrade() takes the upgrades of the path, and leaves the others alone
|
|
195
|
-
this.wss = new
|
|
207
|
+
this.wss = new (webSocketServerClass())({
|
|
196
208
|
noServer: true,
|
|
197
209
|
path: this.options.path,
|
|
198
210
|
perMessageDeflate: this.options.perMessageDeflate,
|
|
@@ -396,7 +408,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
396
408
|
const credentials = requestCredentials({ headers: req.headers as Record<string, unknown>, url: req.url, socket: req.socket }, { tokenQuery: "token" });
|
|
397
409
|
|
|
398
410
|
// The calls of this connection in the two directions (roadmap R24)
|
|
399
|
-
const isOpen = (): boolean => ws.readyState ===
|
|
411
|
+
const isOpen = (): boolean => ws.readyState === OPEN;
|
|
400
412
|
const send = (frame: WebSocketMessage): void => this.send(ws, frame);
|
|
401
413
|
this.channels.set(connectionId, {
|
|
402
414
|
incoming: new IncomingCalls({
|
|
@@ -573,7 +585,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
573
585
|
* the socket has written this frame, or the signal aborts.
|
|
574
586
|
*/
|
|
575
587
|
private sendWithBackpressure(ws: WebSocket, message: WebSocketMessage, signal: AbortSignal): Promise<void> {
|
|
576
|
-
if (ws.readyState !==
|
|
588
|
+
if (ws.readyState !== OPEN) return Promise.resolve();
|
|
577
589
|
return new Promise<void>((resolve) => {
|
|
578
590
|
let waiting = false;
|
|
579
591
|
const onAbort = (): void => resolve();
|
|
@@ -618,7 +630,7 @@ export class WebSocketServerTransport implements ServerTransport {
|
|
|
618
630
|
* Send message to client.
|
|
619
631
|
*/
|
|
620
632
|
private send(ws: WebSocket, message: WebSocketMessage): void {
|
|
621
|
-
if (ws.readyState ===
|
|
633
|
+
if (ws.readyState === OPEN) {
|
|
622
634
|
ws.send(JSON.stringify(message));
|
|
623
635
|
}
|
|
624
636
|
}
|
package/src/browser.ts
CHANGED
|
@@ -194,6 +194,10 @@ export {
|
|
|
194
194
|
registerModules,
|
|
195
195
|
createRegistry,
|
|
196
196
|
registerClientProcedures,
|
|
197
|
+
// The lazy registry (roadmap O93)
|
|
198
|
+
createLazyRegistry,
|
|
199
|
+
resolveIn,
|
|
200
|
+
loadAllIn,
|
|
197
201
|
callerOf,
|
|
198
202
|
// Procedure reference system (procedure-as-data)
|
|
199
203
|
PROCEDURE_SYMBOL,
|
|
@@ -224,6 +228,7 @@ export {
|
|
|
224
228
|
RAW_INPUT_TAG,
|
|
225
229
|
takesRawInput,
|
|
226
230
|
rawInputRule,
|
|
231
|
+
preloadRefPaths,
|
|
227
232
|
MAX_DATA_DEPTH,
|
|
228
233
|
LITERAL_KEY,
|
|
229
234
|
isLiteral,
|
|
@@ -261,6 +266,8 @@ export {
|
|
|
261
266
|
// Every core procedure (math, comparison, string, type, object, array, meta too). Not registered by default.
|
|
262
267
|
allCoreProcedures,
|
|
263
268
|
allCoreModules,
|
|
269
|
+
// Only the math procedures: a bundle that imports only these gets no other core procedure (roadmap O95)
|
|
270
|
+
mathProcedures,
|
|
264
271
|
chainProcedure,
|
|
265
272
|
traceProcedure,
|
|
266
273
|
parallelProcedure,
|
|
@@ -361,6 +368,10 @@ export type {
|
|
|
361
368
|
CallerOptions,
|
|
362
369
|
RegistrationOptions,
|
|
363
370
|
ProcedureRegistryLike,
|
|
371
|
+
LazyRegistryOptions,
|
|
372
|
+
RegistryResolver,
|
|
373
|
+
ProcedureRegistryOptions,
|
|
374
|
+
ResolvingRegistry,
|
|
364
375
|
ProcedureDefinition,
|
|
365
376
|
ProcedureStub,
|
|
366
377
|
InferProcedureInput,
|
package/src/client/client.ts
CHANGED
|
@@ -52,7 +52,7 @@ import type {
|
|
|
52
52
|
StreamingCallResponse,
|
|
53
53
|
ProcedureCallResult,
|
|
54
54
|
} from "./call-types.js";
|
|
55
|
-
import { buildResponse, toMiddlewareContext } from "./call-types.js";
|
|
55
|
+
import { buildResponse, flattenRoute, toMiddlewareContext } from "./call-types.js";
|
|
56
56
|
import { RouteResolver, type ResolvedRoute, type RouteResolutionResult } from "./route-resolver.js";
|
|
57
57
|
import { BatchExecutor, type ExecutionContext } from "./batch-executor.js";
|
|
58
58
|
import type { ProcedureRegistry } from "../procedures/registry.js";
|
|
@@ -88,6 +88,7 @@ import {
|
|
|
88
88
|
isAnyProcedureRef,
|
|
89
89
|
isPlainObject,
|
|
90
90
|
normalizeRef,
|
|
91
|
+
preloadRefPaths,
|
|
91
92
|
rawInputRule,
|
|
92
93
|
type ProcedureRef,
|
|
93
94
|
type AnyProcedureRef,
|
|
@@ -566,6 +567,15 @@ export class Client<TContext = {}> {
|
|
|
566
567
|
* `root` starts the root call.
|
|
567
568
|
*/
|
|
568
569
|
private runRef(ref: ProcedureRef, caller: CallerContext, root: (path: ProcedurePath, input: unknown) => unknown): Promise<unknown> {
|
|
570
|
+
// A lazy registry loads the packages of the paths of the program first: the raw-input rule
|
|
571
|
+
// below reads only the procedures that have loaded (roadmap O93). An eager registry does not wait.
|
|
572
|
+
const preload = preloadRefPaths(this.procedureRegistry, ref);
|
|
573
|
+
if (preload) return preload.then(() => this.startProgram(ref, caller, root));
|
|
574
|
+
return this.startProgram(ref, caller, root);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** This function starts a program on the kernel, after the paths of a lazy registry loaded. */
|
|
578
|
+
private startProgram(ref: ProcedureRef, caller: CallerContext, root: (path: ProcedurePath, input: unknown) => unknown): Promise<unknown> {
|
|
569
579
|
// A procedure that takes its input raw (control flow, the `runs-refs` and `raw-input` tags,
|
|
570
580
|
// `procedure.define`) gets its operand refs RAW: it runs them itself, with correct
|
|
571
581
|
// ordering/laziness/error-scoping. Eagerly hydrating here would run the operands up-front
|
|
@@ -703,7 +713,8 @@ export class Client<TContext = {}> {
|
|
|
703
713
|
input: unknown,
|
|
704
714
|
caller: CallerContext = {}
|
|
705
715
|
): Promise<TOutput> {
|
|
706
|
-
|
|
716
|
+
// A miss of a lazy registry loads the packages of the path (roadmap O93). A hit does not wait.
|
|
717
|
+
const procedure = this.procedureRegistry.get(path) ?? (this.procedureRegistry.lazy ? await this.procedureRegistry.resolve(path) : undefined);
|
|
707
718
|
if (procedure?.handler) {
|
|
708
719
|
const options = this.invokeOptions(caller);
|
|
709
720
|
return outputValue(await invokeProcedure<TOutput>(procedure, input, options), path);
|
|
@@ -726,7 +737,7 @@ export class Client<TContext = {}> {
|
|
|
726
737
|
): AsyncIterable<TOutput> {
|
|
727
738
|
const self = this;
|
|
728
739
|
return (async function* () {
|
|
729
|
-
const procedure = self.procedureRegistry.get(path);
|
|
740
|
+
const procedure = self.procedureRegistry.get(path) ?? (self.procedureRegistry.lazy ? await self.procedureRegistry.resolve(path) : undefined);
|
|
730
741
|
if (procedure?.handler) {
|
|
731
742
|
const options = self.invokeOptions(caller);
|
|
732
743
|
yield* outputItems(await invokeProcedure<TOutput>(procedure, input, options));
|
|
@@ -950,7 +961,7 @@ export class Client<TContext = {}> {
|
|
|
950
961
|
*
|
|
951
962
|
* @example
|
|
952
963
|
* ```typescript
|
|
953
|
-
* import
|
|
964
|
+
* import * as z from "zod";
|
|
954
965
|
*
|
|
955
966
|
* const client = new Client({ transport })
|
|
956
967
|
* .use(createZodMiddleware())
|
|
@@ -1099,6 +1110,8 @@ export class Client<TContext = {}> {
|
|
|
1099
1110
|
async route<TRoute extends Route>(
|
|
1100
1111
|
request: CallRequest<TRoute>
|
|
1101
1112
|
): Promise<CallResponse<TRoute>> {
|
|
1113
|
+
// A lazy registry loads the packages of the paths first: the resolver reads only what has loaded (roadmap O93)
|
|
1114
|
+
if (this.procedureRegistry.lazy) await this.loadRoutePaths(request.route);
|
|
1102
1115
|
const resolver = this.getRouteResolver();
|
|
1103
1116
|
const executor = this.getBatchExecutor();
|
|
1104
1117
|
|
|
@@ -1135,6 +1148,35 @@ export class Client<TContext = {}> {
|
|
|
1135
1148
|
routeStream<TRoute extends Route>(
|
|
1136
1149
|
request: CallRequest<TRoute>
|
|
1137
1150
|
): StreamingCallResponse<TRoute> {
|
|
1151
|
+
// A lazy registry without each path of the route: the stream starts after the packages loaded (roadmap O93)
|
|
1152
|
+
const registry = this.procedureRegistry;
|
|
1153
|
+
if (registry.lazy && flattenRoute(request.route).some((entry) => !registry.has(entry.path))) {
|
|
1154
|
+
const started = this.loadRoutePaths(request.route).then(() => this.routeStreamLoaded(request));
|
|
1155
|
+
const complete = started.then((response) => response.complete);
|
|
1156
|
+
// A caller that reads only the results gets the load error there: the promise is not an unhandled rejection
|
|
1157
|
+
complete.catch(() => undefined);
|
|
1158
|
+
return {
|
|
1159
|
+
results: (async function* () {
|
|
1160
|
+
yield* (await started).results;
|
|
1161
|
+
})(),
|
|
1162
|
+
complete,
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
return this.routeStreamLoaded(request);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/**
|
|
1169
|
+
* This function loads the packages of the paths of a route into a lazy registry. A package that
|
|
1170
|
+
* did not load rejects the promise, with the load error as the `cause`.
|
|
1171
|
+
*/
|
|
1172
|
+
private async loadRoutePaths(route: Route): Promise<void> {
|
|
1173
|
+
for (const entry of flattenRoute(route)) {
|
|
1174
|
+
if (!this.procedureRegistry.has(entry.path)) await this.procedureRegistry.resolve(entry.path);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
/** This function starts the stream of a route whose procedures the registry has. */
|
|
1179
|
+
private routeStreamLoaded<TRoute extends Route>(request: CallRequest<TRoute>): StreamingCallResponse<TRoute> {
|
|
1138
1180
|
const resolver = this.getRouteResolver();
|
|
1139
1181
|
const executor = this.getBatchExecutor();
|
|
1140
1182
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The known error codes of the client, without their texts (roadmap O95).
|
|
3
|
+
*
|
|
4
|
+
* The error policy of a server needs only the codes, not the summaries and the suggestions of
|
|
5
|
+
* `ERROR_REGISTRY`. This module gives the codes from `ErrorCode` and `ERROR_CODES`, so a bundle
|
|
6
|
+
* of the server engine gets no copy of the texts. A test makes sure that this set and the keys of
|
|
7
|
+
* `ERROR_REGISTRY` are equal.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ErrorCode } from "./codes.js";
|
|
11
|
+
import { ERROR_CODES } from "../../protocol.js";
|
|
12
|
+
|
|
13
|
+
/** The known error codes: each value of `ErrorCode` and each code of the wire protocol. */
|
|
14
|
+
export const KNOWN_ERROR_CODES: ReadonlySet<string> = new Set<string>([...Object.values(ErrorCode), ...Object.keys(ERROR_CODES)]);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* This function gives `true` for a known error code. It gives the same answer as
|
|
18
|
+
* `isKnownErrorCode` of the registry, but it does not import the texts of the registry.
|
|
19
|
+
*/
|
|
20
|
+
export function isKnownCode(code: unknown): code is string {
|
|
21
|
+
return typeof code === "string" && KNOWN_ERROR_CODES.has(code);
|
|
22
|
+
}
|
package/src/events/publisher.ts
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* events (`dropped`). The bus never waits for a subscriber.
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
-
import
|
|
35
|
+
import * as z from "zod";
|
|
36
36
|
import { createProcedure } from "../procedures/define.js";
|
|
37
37
|
import { InvocationError, type Invocation } from "../procedures/invoke.js";
|
|
38
38
|
import { capabilityRefusal } from "../procedures/capability.js";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* The namespace of all these procedures is "client": for example client.first and client.filter.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import * as z from "zod";
|
|
9
9
|
import { anyOutput, arrayOutput, booleanOutput, numberOutput } from "./outputs.js";
|
|
10
10
|
import { defineProcedure, namespace } from "../define.js";
|
|
11
11
|
import type { AnyProcedure, Procedure } from "../types.js";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* The namespace of all these procedures is "client": for example client.eq and client.gt.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import * as z from "zod";
|
|
9
9
|
import { booleanOutput } from "./outputs.js";
|
|
10
10
|
import { defineProcedure, namespace } from "../define.js";
|
|
11
11
|
import type { AnyProcedure, Procedure } from "../types.js";
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
*/
|
|
47
47
|
|
|
48
48
|
import { PURE_EFFECTS, REGISTRY_READ_EFFECTS, withEffects } from "../effects.js";
|
|
49
|
-
import
|
|
49
|
+
import * as z from "zod";
|
|
50
50
|
import { anyOutput, booleanOutput } from "./outputs.js";
|
|
51
51
|
import { defineProcedure, namespace } from "../define.js";
|
|
52
52
|
import type {
|
|
@@ -136,6 +136,7 @@ const TryCatchInputSchema = z.looseObject({
|
|
|
136
136
|
import {
|
|
137
137
|
isAnyProcedureRef,
|
|
138
138
|
normalizeRef,
|
|
139
|
+
preloadRefPaths,
|
|
139
140
|
createRefScope,
|
|
140
141
|
scopeOf,
|
|
141
142
|
getRefWhen,
|
|
@@ -349,6 +350,8 @@ const traceProcedure: TraceProcedure = defineProcedure({
|
|
|
349
350
|
if (!ctx?.client) throw new Error("client.trace: a run needs a client context");
|
|
350
351
|
const registry = ctx.registry ?? PROCEDURE_REGISTRY;
|
|
351
352
|
const limit = upTo ?? Number.POSITIVE_INFINITY;
|
|
353
|
+
// A lazy registry loads the paths of the steps first: the facts below read their effects (roadmap O93)
|
|
354
|
+
await preloadRefPaths(registry, steps);
|
|
352
355
|
|
|
353
356
|
// The facts of each step before the run: its name, its path and the declared effects of its procedure
|
|
354
357
|
const rows: TraceRow[] = steps.map((step, index) => {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* The namespace of all these procedures is "client": for example client.add and client.multiply.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import * as z from "zod";
|
|
9
9
|
import { numberOutput } from "./outputs.js";
|
|
10
10
|
import { defineProcedure, namespace } from "../define.js";
|
|
11
11
|
import type { AnyProcedure, Procedure } from "../types.js";
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* The namespace of all these procedures is "client": for example client.import and client.eval.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import * as z from "zod";
|
|
10
10
|
import { anyOutput, stringOutput } from "./outputs.js";
|
|
11
11
|
import { defineProcedure, namespace } from "../define.js";
|
|
12
12
|
import type { AnyProcedure, Procedure } from "../types.js";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* The namespace of all these procedures is "client": for example client.get and client.set.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import * as z from "zod";
|
|
9
9
|
import { anyOutput, arrayOutput, booleanOutput, recordOutput } from "./outputs.js";
|
|
10
10
|
import { defineProcedure, namespace } from "../define.js";
|
|
11
11
|
import type { AnyProcedure, Procedure } from "../types.js";
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
isAnyProcedureRef,
|
|
10
10
|
isOutputRef,
|
|
11
11
|
lookupOutputRef,
|
|
12
|
+
preloadRefPaths,
|
|
12
13
|
rawInputRule,
|
|
13
14
|
scopeOf,
|
|
14
15
|
RUNS_REFS_TAG,
|
|
@@ -41,7 +42,11 @@ function executorOf(ctx: ProcedureContext): <TIn, TOut>(path: ProcedurePath, inp
|
|
|
41
42
|
* each step of a chain (`client.trace`).
|
|
42
43
|
*/
|
|
43
44
|
export function runControlFlow(name: string, input: unknown, ctx: ProcedureContext, options: Pick<RunControlOptions, "onStep"> = {}): Promise<unknown> {
|
|
44
|
-
|
|
45
|
+
// A lazy registry loads the paths of the operands before the raw-input rule reads them (roadmap O93)
|
|
46
|
+
const preload = preloadRefPaths(ctx.registry, input);
|
|
47
|
+
const run = (): Promise<unknown> =>
|
|
48
|
+
runControl(name, input, { executor: executorOf(ctx), rawInput: rawRule(ctx), onRef: ctx.onRef, scope: scopeOf(input), onStep: options.onStep });
|
|
49
|
+
return preload ? preload.then(run) : run();
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
/**
|
|
@@ -70,6 +75,7 @@ export function substituteRefs(value: unknown, scope: RefScope): unknown {
|
|
|
70
75
|
* in `scope` and runs its nested refs, as `exec()` does for an input.
|
|
71
76
|
*/
|
|
72
77
|
export async function hydrateOperand(value: unknown, ctx: ProcedureContext, scope?: RefScope): Promise<unknown> {
|
|
78
|
+
await preloadRefPaths(ctx.registry, value);
|
|
73
79
|
return hydrateInput(value, executorOf(ctx), { scope, rawInput: rawRule(ctx), onRef: ctx.onRef });
|
|
74
80
|
}
|
|
75
81
|
|
|
@@ -86,6 +92,7 @@ export async function callOperand(
|
|
|
86
92
|
ctx: ProcedureContext,
|
|
87
93
|
options: { scope?: RefScope | undefined; extra?: Record<string, unknown> | undefined } = {},
|
|
88
94
|
): Promise<unknown> {
|
|
95
|
+
await preloadRefPaths(ctx.registry, ref);
|
|
89
96
|
return runOperand(ref, { executor: executorOf(ctx), rawInput: rawRule(ctx), onRef: ctx.onRef, scope: options.scope, extra: options.extra });
|
|
90
97
|
}
|
|
91
98
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* The namespace of all these procedures is "client": for example client.concat and client.split.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import * as z from "zod";
|
|
9
9
|
import { booleanOutput, numberOutput, stringOutput } from "./outputs.js";
|
|
10
10
|
import { defineProcedure, namespace } from "../define.js";
|
|
11
11
|
import type { AnyProcedure, Procedure } from "../types.js";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* The namespace of all these procedures is "client": for example client.typeof and client.isArray.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import * as z from "zod";
|
|
9
9
|
import { anyOutput, booleanOutput, stringOutput } from "./outputs.js";
|
|
10
10
|
import { defineProcedure, namespace } from "../define.js";
|
|
11
11
|
import type { AnyProcedure, Procedure } from "../types.js";
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
import { defineProcedure, validateProcedure } from "./define.js";
|
|
29
|
-
import { PROCEDURE_REGISTRY, assertValidPath } from "./registry.js";
|
|
29
|
+
import { PROCEDURE_REGISTRY, assertValidPath, loadAllIn, resolveIn } from "./registry.js";
|
|
30
30
|
import type {
|
|
31
31
|
Procedure,
|
|
32
32
|
ProcedurePath,
|
|
@@ -36,13 +36,14 @@ import type {
|
|
|
36
36
|
ProcedureRegistryLike,
|
|
37
37
|
Effects,
|
|
38
38
|
} from "./types.js";
|
|
39
|
-
import
|
|
39
|
+
import * as z from "zod";
|
|
40
40
|
import { anySchema } from "./core/schemas.js";
|
|
41
41
|
import { keyToPath, pathToKey } from "./types.js";
|
|
42
42
|
import {
|
|
43
43
|
createRefScope,
|
|
44
44
|
isAnyProcedureRef,
|
|
45
45
|
markDataDriven,
|
|
46
|
+
preloadRefPaths,
|
|
46
47
|
rawInputRule,
|
|
47
48
|
RUNS_REFS_TAG,
|
|
48
49
|
} from "./ref.js";
|
|
@@ -211,7 +212,11 @@ function createAggregationHandler(
|
|
|
211
212
|
const scope = createRefScope();
|
|
212
213
|
scope.outputs.set("input", input);
|
|
213
214
|
const executor = <TIn, TOut>(path: ProcedurePath, value: TIn): Promise<TOut> => ctx.client.call<TIn, TOut>(path, value);
|
|
214
|
-
const
|
|
215
|
+
const registry = ctx.registry ?? PROCEDURE_REGISTRY;
|
|
216
|
+
// A lazy registry loads the paths of the body before the raw-input rule reads them (roadmap O93)
|
|
217
|
+
const preload = preloadRefPaths(registry, aggregation);
|
|
218
|
+
if (preload) await preload;
|
|
219
|
+
const options = { scope, rawInput: rawInputRule(registry), onRef: ctx.onRef };
|
|
215
220
|
if (isAnyProcedureRef(aggregation)) {
|
|
216
221
|
return runProgram(aggregation, { ...options, executor });
|
|
217
222
|
}
|
|
@@ -327,7 +332,8 @@ export const defineProcedureProcedure: Procedure<
|
|
|
327
332
|
}
|
|
328
333
|
|
|
329
334
|
const pathKey = pathToKey(path);
|
|
330
|
-
|
|
335
|
+
// A lazy registry loads the package of the path first: a procedure of a package is not new (roadmap O93)
|
|
336
|
+
const existing = await resolveIn(registry, path);
|
|
331
337
|
// A procedure outside the expose rule of the host is not the caller's to replace (R17)
|
|
332
338
|
if (existing && ctx?.expose && !ctx.expose(path)) {
|
|
333
339
|
throw new InvocationError(
|
|
@@ -473,7 +479,8 @@ export const getProcedureProcedure: Procedure<
|
|
|
473
479
|
},
|
|
474
480
|
handler: async (input: ProcedureGetInput, ctx?: ProcedureContext) => {
|
|
475
481
|
const path = typeof input.path === "string" ? keyToPath(input.path) : input.path;
|
|
476
|
-
|
|
482
|
+
// A lazy registry loads the package of the path first (roadmap O93)
|
|
483
|
+
const procedure = await resolveIn(ctx?.registry ?? PROCEDURE_REGISTRY, path);
|
|
477
484
|
// A procedure outside the expose rule of the host is not found (R17)
|
|
478
485
|
if (!procedure || (ctx?.expose && !ctx.expose(procedure.path))) {
|
|
479
486
|
return { found: false as const };
|
|
@@ -513,7 +520,10 @@ export const listProceduresProcedure: Procedure<
|
|
|
513
520
|
output: "json",
|
|
514
521
|
},
|
|
515
522
|
handler: async (input: ProcedureListInput, ctx?: ProcedureContext) => {
|
|
516
|
-
|
|
523
|
+
const registry = ctx?.registry ?? PROCEDURE_REGISTRY;
|
|
524
|
+
// A lazy registry loads each package first: the list has each procedure (roadmap O93)
|
|
525
|
+
await loadAllIn(registry);
|
|
526
|
+
let all = registry.getAll();
|
|
517
527
|
// Only the procedures that the host exposes (R17)
|
|
518
528
|
if (ctx?.expose) all = all.filter((procedure) => ctx.expose!(procedure.path));
|
|
519
529
|
if (input.runtime) all = all.filter(isRuntimeDefined);
|
|
@@ -539,11 +549,15 @@ export const manifestProcedure: Procedure<{ namespace?: string | undefined }, Pr
|
|
|
539
549
|
shorts: { namespace: "n" },
|
|
540
550
|
output: "json",
|
|
541
551
|
},
|
|
542
|
-
handler: async (input: { namespace?: string | undefined }, ctx?: ProcedureContext) =>
|
|
543
|
-
|
|
552
|
+
handler: async (input: { namespace?: string | undefined }, ctx?: ProcedureContext) => {
|
|
553
|
+
const registry = ctx?.registry ?? PROCEDURE_REGISTRY;
|
|
554
|
+
// A lazy registry loads each package first: the manifest has each procedure (roadmap O93)
|
|
555
|
+
await loadAllIn(registry);
|
|
556
|
+
return describeRegistry(registry, {
|
|
544
557
|
...(ctx?.expose ? { expose: ctx.expose } : {}),
|
|
545
558
|
...(input.namespace ? { include: (procedure: AnyProcedure) => procedure.path[0] === input.namespace } : {}),
|
|
546
|
-
})
|
|
559
|
+
});
|
|
560
|
+
},
|
|
547
561
|
});
|
|
548
562
|
|
|
549
563
|
/**
|
|
@@ -568,7 +582,8 @@ export const deleteProcedureProcedure: Procedure<
|
|
|
568
582
|
if (ctx?.expose && !ctx.expose(input.path)) {
|
|
569
583
|
return { deleted: false };
|
|
570
584
|
}
|
|
571
|
-
|
|
585
|
+
// A lazy registry loads the package of the path first (roadmap O93)
|
|
586
|
+
const existing = await resolveIn(registry, input.path);
|
|
572
587
|
if (!existing) {
|
|
573
588
|
return { deleted: false };
|
|
574
589
|
}
|
package/src/procedures/index.ts
CHANGED
|
@@ -114,7 +114,8 @@ export { NARROWING_METADATA_KEY, NARROWING_HEADER, narrowingOf, narrowWithin, ad
|
|
|
114
114
|
export type { NarrowingNote, WireNarrowing, NarrowedCapability } from "./narrowing.js";
|
|
115
115
|
|
|
116
116
|
// Registry
|
|
117
|
-
export { ProcedureRegistry, RegistryError, PROCEDURE_REGISTRY, assertValidPath } from "./registry.js";
|
|
117
|
+
export { ProcedureRegistry, RegistryError, PROCEDURE_REGISTRY, assertValidPath, resolveIn, loadAllIn } from "./registry.js";
|
|
118
|
+
export type { RegistryResolver, ProcedureRegistryOptions, ResolvingRegistry } from "./registry.js";
|
|
118
119
|
|
|
119
120
|
// Definition helpers
|
|
120
121
|
export {
|
|
@@ -223,9 +224,10 @@ export {
|
|
|
223
224
|
moduleClosure,
|
|
224
225
|
registerModules,
|
|
225
226
|
createRegistry,
|
|
227
|
+
createLazyRegistry,
|
|
226
228
|
registerClientProcedures,
|
|
227
229
|
} from "./module.js";
|
|
228
|
-
export type { ModuleInput, PackageDefinition } from "./module.js";
|
|
230
|
+
export type { ModuleInput, PackageDefinition, LazyRegistryOptions } from "./module.js";
|
|
229
231
|
// One call for a registry of some modules, `invokePath` and `outputValue`
|
|
230
232
|
export { callerOf } from "./caller.js";
|
|
231
233
|
export type { Caller, CallerOptions } from "./caller.js";
|
|
@@ -262,6 +264,7 @@ export {
|
|
|
262
264
|
RAW_INPUT_TAG,
|
|
263
265
|
takesRawInput,
|
|
264
266
|
rawInputRule,
|
|
267
|
+
preloadRefPaths,
|
|
265
268
|
MAX_DATA_DEPTH,
|
|
266
269
|
LITERAL_KEY,
|
|
267
270
|
isLiteral,
|
|
@@ -338,6 +341,8 @@ export {
|
|
|
338
341
|
// Every core procedure (math, comparison, string, type, object, array, meta too). Not registered by default.
|
|
339
342
|
allCoreProcedures,
|
|
340
343
|
allCoreModules,
|
|
344
|
+
// Only the math procedures: a bundle that imports only these gets no other core procedure (roadmap O95)
|
|
345
|
+
mathProcedures,
|
|
341
346
|
chainProcedure,
|
|
342
347
|
traceProcedure,
|
|
343
348
|
type TraceRow,
|
package/src/procedures/invoke.ts
CHANGED
|
@@ -440,6 +440,13 @@ export async function invokeProcedure<TOutput = unknown>(
|
|
|
440
440
|
return dispatch(0, invocation, options) as Promise<ProcedureOutput<TOutput>>;
|
|
441
441
|
}
|
|
442
442
|
|
|
443
|
+
/** True when the declared effects of a procedure read or write the registry. */
|
|
444
|
+
function usesRegistry(procedure: AnyProcedure): boolean {
|
|
445
|
+
const effects = (procedure.metadata as { effects?: { reads?: unknown; writes?: unknown } } | undefined)?.effects;
|
|
446
|
+
const names = (list: unknown): boolean => Array.isArray(list) && list.includes("registry");
|
|
447
|
+
return names(effects?.reads) || names(effects?.writes);
|
|
448
|
+
}
|
|
449
|
+
|
|
443
450
|
/** The end of the chain: the validation, the context, the handler and the output. */
|
|
444
451
|
async function runInvocation<TOutput>(invocation: Invocation, options: InvokeOptions): Promise<ProcedureOutput<TOutput>> {
|
|
445
452
|
const { procedure, path, input } = invocation;
|
|
@@ -472,6 +479,11 @@ async function runInvocation<TOutput>(invocation: Invocation, options: InvokeOpt
|
|
|
472
479
|
data = parsed.value === input ? input : withScope(parsed.value, scopeOf(input));
|
|
473
480
|
}
|
|
474
481
|
|
|
482
|
+
// A procedure that declares a read or a write of the registry sees each procedure of a lazy
|
|
483
|
+
// registry (roadmap O93). An eager registry does not wait here.
|
|
484
|
+
const registry = options.registry;
|
|
485
|
+
if (registry?.lazy && usesRegistry(procedure)) await registry.loadAll();
|
|
486
|
+
|
|
475
487
|
// The handler starts here, before the first await (the site's trace depends on it)
|
|
476
488
|
const result: unknown = await handler(data, createProcedureContext(procedure, { ...options, capability: invocation.capability }, invocation));
|
|
477
489
|
// The output filter examines only what leaves the host
|
|
@@ -508,7 +520,9 @@ export async function invokePath<TOutput = unknown>(
|
|
|
508
520
|
input: unknown,
|
|
509
521
|
options: InvokeOptions = {},
|
|
510
522
|
): Promise<ProcedureOutput<TOutput>> {
|
|
511
|
-
const
|
|
523
|
+
const registry = options.registry ?? PROCEDURE_REGISTRY;
|
|
524
|
+
// A miss of a lazy registry loads the packages of the path (roadmap O93). A hit does not wait.
|
|
525
|
+
const procedure = registry.get(path) ?? (registry.lazy ? await registry.resolve(path) : undefined);
|
|
512
526
|
if (!procedure) {
|
|
513
527
|
throw new InvocationError("NOT_FOUND", `Procedure not found: ${pathToKey(path)}`, path);
|
|
514
528
|
}
|