@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.
Files changed (138) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +1 -1
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/adapters/http/server/transport.d.ts +5 -0
  5. package/dist/adapters/http/server/transport.d.ts.map +1 -1
  6. package/dist/adapters/http/server/transport.js +7 -0
  7. package/dist/adapters/http/server/transport.js.map +1 -1
  8. package/dist/adapters/local/client/transport.d.ts.map +1 -1
  9. package/dist/adapters/local/client/transport.js +12 -1
  10. package/dist/adapters/local/client/transport.js.map +1 -1
  11. package/dist/adapters/websocket/client/node.d.ts.map +1 -1
  12. package/dist/adapters/websocket/client/node.js +9 -2
  13. package/dist/adapters/websocket/client/node.js.map +1 -1
  14. package/dist/adapters/websocket/server/transport.d.ts.map +1 -1
  15. package/dist/adapters/websocket/server/transport.js +14 -5
  16. package/dist/adapters/websocket/server/transport.js.map +1 -1
  17. package/dist/browser.d.ts +2 -2
  18. package/dist/browser.d.ts.map +1 -1
  19. package/dist/browser.js +7 -3
  20. package/dist/browser.js.map +1 -1
  21. package/dist/client/client.d.ts +10 -1
  22. package/dist/client/client.d.ts.map +1 -1
  23. package/dist/client/client.js +46 -5
  24. package/dist/client/client.js.map +1 -1
  25. package/dist/client/errors/known-codes.d.ts +16 -0
  26. package/dist/client/errors/known-codes.d.ts.map +1 -0
  27. package/dist/client/errors/known-codes.js +20 -0
  28. package/dist/client/errors/known-codes.js.map +1 -0
  29. package/dist/client/validation/index.d.ts +1 -1
  30. package/dist/client/validation/index.js +1 -1
  31. package/dist/events/publisher.js +1 -1
  32. package/dist/events/publisher.js.map +1 -1
  33. package/dist/procedures/core/array.js +1 -1
  34. package/dist/procedures/core/array.js.map +1 -1
  35. package/dist/procedures/core/comparison.js +1 -1
  36. package/dist/procedures/core/comparison.js.map +1 -1
  37. package/dist/procedures/core/index.d.ts.map +1 -1
  38. package/dist/procedures/core/index.js +4 -2
  39. package/dist/procedures/core/index.js.map +1 -1
  40. package/dist/procedures/core/math.js +1 -1
  41. package/dist/procedures/core/math.js.map +1 -1
  42. package/dist/procedures/core/meta.js +1 -1
  43. package/dist/procedures/core/meta.js.map +1 -1
  44. package/dist/procedures/core/object.js +1 -1
  45. package/dist/procedures/core/object.js.map +1 -1
  46. package/dist/procedures/core/operands.d.ts.map +1 -1
  47. package/dist/procedures/core/operands.js +7 -2
  48. package/dist/procedures/core/operands.js.map +1 -1
  49. package/dist/procedures/core/outputs.d.ts +1 -1
  50. package/dist/procedures/core/outputs.d.ts.map +1 -1
  51. package/dist/procedures/core/outputs.js +1 -1
  52. package/dist/procedures/core/outputs.js.map +1 -1
  53. package/dist/procedures/core/string.js +1 -1
  54. package/dist/procedures/core/string.js.map +1 -1
  55. package/dist/procedures/core/type.js +1 -1
  56. package/dist/procedures/core/type.js.map +1 -1
  57. package/dist/procedures/define-procedure.d.ts.map +1 -1
  58. package/dist/procedures/define-procedure.js +28 -12
  59. package/dist/procedures/define-procedure.js.map +1 -1
  60. package/dist/procedures/index.d.ts +6 -5
  61. package/dist/procedures/index.d.ts.map +1 -1
  62. package/dist/procedures/index.js +6 -4
  63. package/dist/procedures/index.js.map +1 -1
  64. package/dist/procedures/invoke.d.ts.map +1 -1
  65. package/dist/procedures/invoke.js +14 -1
  66. package/dist/procedures/invoke.js.map +1 -1
  67. package/dist/procedures/module.d.ts +34 -0
  68. package/dist/procedures/module.d.ts.map +1 -1
  69. package/dist/procedures/module.js +90 -0
  70. package/dist/procedures/module.js.map +1 -1
  71. package/dist/procedures/ref.d.ts +10 -1
  72. package/dist/procedures/ref.d.ts.map +1 -1
  73. package/dist/procedures/ref.js +64 -0
  74. package/dist/procedures/ref.js.map +1 -1
  75. package/dist/procedures/registry.d.ts +64 -0
  76. package/dist/procedures/registry.d.ts.map +1 -1
  77. package/dist/procedures/registry.js +60 -0
  78. package/dist/procedures/registry.js.map +1 -1
  79. package/dist/procedures/segments.d.ts.map +1 -1
  80. package/dist/procedures/segments.js +13 -3
  81. package/dist/procedures/segments.js.map +1 -1
  82. package/dist/procedures/types.d.ts +6 -0
  83. package/dist/procedures/types.d.ts.map +1 -1
  84. package/dist/procedures/types.js.map +1 -1
  85. package/dist/server/error-policy.d.ts.map +1 -1
  86. package/dist/server/error-policy.js +2 -1
  87. package/dist/server/error-policy.js.map +1 -1
  88. package/dist/server/host-procedures.d.ts.map +1 -1
  89. package/dist/server/host-procedures.js +12 -8
  90. package/dist/server/host-procedures.js.map +1 -1
  91. package/dist/server/procedure-server.d.ts +11 -0
  92. package/dist/server/procedure-server.d.ts.map +1 -1
  93. package/dist/server/procedure-server.js +18 -0
  94. package/dist/server/procedure-server.js.map +1 -1
  95. package/dist/server/server.d.ts +5 -0
  96. package/dist/server/server.d.ts.map +1 -1
  97. package/dist/server/server.js +9 -1
  98. package/dist/server/server.js.map +1 -1
  99. package/dist/testing/transport-contract.js +1 -1
  100. package/dist/testing/transport-contract.js.map +1 -1
  101. package/dist/tracing/traceparent.d.ts +6 -0
  102. package/dist/tracing/traceparent.d.ts.map +1 -1
  103. package/dist/tracing/traceparent.js +27 -6
  104. package/dist/tracing/traceparent.js.map +1 -1
  105. package/package.json +4 -4
  106. package/src/adapters/http/server/transport.ts +8 -0
  107. package/src/adapters/local/client/transport.ts +11 -1
  108. package/src/adapters/websocket/client/node.ts +11 -2
  109. package/src/adapters/websocket/server/transport.ts +17 -5
  110. package/src/browser.ts +11 -0
  111. package/src/client/client.ts +46 -4
  112. package/src/client/errors/known-codes.ts +22 -0
  113. package/src/client/validation/index.ts +1 -1
  114. package/src/events/publisher.ts +1 -1
  115. package/src/procedures/core/array.ts +1 -1
  116. package/src/procedures/core/comparison.ts +1 -1
  117. package/src/procedures/core/index.ts +4 -1
  118. package/src/procedures/core/math.ts +1 -1
  119. package/src/procedures/core/meta.ts +1 -1
  120. package/src/procedures/core/object.ts +1 -1
  121. package/src/procedures/core/operands.ts +8 -1
  122. package/src/procedures/core/outputs.ts +1 -1
  123. package/src/procedures/core/string.ts +1 -1
  124. package/src/procedures/core/type.ts +1 -1
  125. package/src/procedures/define-procedure.ts +25 -10
  126. package/src/procedures/index.ts +7 -2
  127. package/src/procedures/invoke.ts +15 -1
  128. package/src/procedures/module.ts +113 -2
  129. package/src/procedures/ref.ts +58 -1
  130. package/src/procedures/registry.ts +99 -0
  131. package/src/procedures/segments.ts +12 -3
  132. package/src/procedures/types.ts +6 -0
  133. package/src/server/error-policy.ts +2 -1
  134. package/src/server/host-procedures.ts +12 -8
  135. package/src/server/procedure-server.ts +18 -0
  136. package/src/server/server.ts +10 -1
  137. package/src/testing/transport-contract.ts +1 -1
  138. 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 { WebSocketServer, WebSocket } from "ws";
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 WebSocketServer({
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 === WebSocket.OPEN;
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 !== WebSocket.OPEN) return Promise.resolve();
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 === WebSocket.OPEN) {
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,
@@ -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
- const procedure = this.procedureRegistry.get(path);
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 { z } from "zod";
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
+ }
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @example
8
8
  * ```typescript
9
- * import { z } from "zod";
9
+ * import * as z from "zod";
10
10
  * import { Client } from "client";
11
11
  * import { createZodMiddleware, ValidationError } from "client/client/validation";
12
12
  *
@@ -32,7 +32,7 @@
32
32
  * events (`dropped`). The bus never waits for a subscriber.
33
33
  */
34
34
 
35
- import { z } from "zod";
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 { z } from "zod";
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 { z } from "zod";
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 { z } from "zod";
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 { z } from "zod";
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 { z } from "zod";
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 { z } from "zod";
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
- return runControl(name, input, { executor: executorOf(ctx), rawInput: rawRule(ctx), onRef: ctx.onRef, scope: scopeOf(input), onStep: options.onStep });
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
 
@@ -6,7 +6,7 @@
6
6
  * helpers give each output a real zod 4 schema with a description.
7
7
  */
8
8
 
9
- import { z } from "zod";
9
+ import * as z from "zod";
10
10
 
11
11
  /**
12
12
  * Any JavaScript number, also `NaN` and `Infinity`. zod 4's `z.number()` refuses both, but
@@ -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 { z } from "zod";
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 { z } from "zod";
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 { z } from "zod";
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 options = { scope, rawInput: rawInputRule(ctx.registry ?? PROCEDURE_REGISTRY), onRef: ctx.onRef };
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
- const existing = registry.get(path);
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
- const procedure = (ctx?.registry ?? PROCEDURE_REGISTRY).get(path);
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
- let all = (ctx?.registry ?? PROCEDURE_REGISTRY).getAll();
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
- describeRegistry(ctx?.registry ?? PROCEDURE_REGISTRY, {
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
- const existing = registry.get(input.path);
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
  }
@@ -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,
@@ -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 procedure = (options.registry ?? PROCEDURE_REGISTRY).get(path);
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
  }