@procedures/store 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +65 -2
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/errors.d.ts +16 -0
  6. package/dist/errors.d.ts.map +1 -0
  7. package/dist/errors.js +18 -0
  8. package/dist/errors.js.map +1 -0
  9. package/dist/index.d.ts +19 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +19 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/module.d.ts +15 -0
  14. package/dist/module.d.ts.map +1 -0
  15. package/dist/module.js +16 -0
  16. package/dist/module.js.map +1 -0
  17. package/dist/procedures.d.ts +13 -0
  18. package/dist/procedures.d.ts.map +1 -0
  19. package/dist/procedures.js +14 -0
  20. package/dist/procedures.js.map +1 -0
  21. package/dist/program-store/attach.d.ts +20 -0
  22. package/dist/program-store/attach.d.ts.map +1 -0
  23. package/dist/program-store/attach.js +36 -0
  24. package/dist/program-store/attach.js.map +1 -0
  25. package/dist/program-store/collection.d.ts +19 -0
  26. package/dist/program-store/collection.d.ts.map +1 -0
  27. package/dist/program-store/collection.js +34 -0
  28. package/dist/program-store/collection.js.map +1 -0
  29. package/dist/program-store/file.d.ts +29 -0
  30. package/dist/program-store/file.d.ts.map +1 -0
  31. package/dist/program-store/file.js +156 -0
  32. package/dist/program-store/file.js.map +1 -0
  33. package/dist/program-store/index.d.ts +11 -0
  34. package/dist/program-store/index.d.ts.map +1 -0
  35. package/dist/program-store/index.js +10 -0
  36. package/dist/program-store/index.js.map +1 -0
  37. package/dist/program-store/memory.d.ts +15 -0
  38. package/dist/program-store/memory.d.ts.map +1 -0
  39. package/dist/program-store/memory.js +25 -0
  40. package/dist/program-store/memory.js.map +1 -0
  41. package/dist/program-store/procedures.d.ts +23 -0
  42. package/dist/program-store/procedures.d.ts.map +1 -0
  43. package/dist/program-store/procedures.js +221 -0
  44. package/dist/program-store/procedures.js.map +1 -0
  45. package/dist/program-store/types.d.ts +50 -0
  46. package/dist/program-store/types.d.ts.map +1 -0
  47. package/dist/program-store/types.js +41 -0
  48. package/dist/program-store/types.js.map +1 -0
  49. package/dist/register.d.ts +8 -0
  50. package/dist/register.d.ts.map +1 -0
  51. package/dist/register.js +15 -0
  52. package/dist/register.js.map +1 -0
  53. package/dist/storage/adapter.d.ts +148 -0
  54. package/dist/storage/adapter.d.ts.map +1 -0
  55. package/dist/storage/adapter.js +214 -0
  56. package/dist/storage/adapter.js.map +1 -0
  57. package/dist/storage/api.d.ts +125 -0
  58. package/dist/storage/api.d.ts.map +1 -0
  59. package/dist/storage/api.js +220 -0
  60. package/dist/storage/api.js.map +1 -0
  61. package/dist/storage/factory.d.ts +134 -0
  62. package/dist/storage/factory.d.ts.map +1 -0
  63. package/dist/storage/factory.js +166 -0
  64. package/dist/storage/factory.js.map +1 -0
  65. package/dist/storage/hybrid.d.ts +193 -0
  66. package/dist/storage/hybrid.d.ts.map +1 -0
  67. package/dist/storage/hybrid.js +519 -0
  68. package/dist/storage/hybrid.js.map +1 -0
  69. package/dist/storage/index.d.ts +21 -0
  70. package/dist/storage/index.d.ts.map +1 -0
  71. package/dist/storage/index.js +20 -0
  72. package/dist/storage/index.js.map +1 -0
  73. package/dist/storage/procedures.d.ts +14 -0
  74. package/dist/storage/procedures.d.ts.map +1 -0
  75. package/dist/storage/procedures.js +98 -0
  76. package/dist/storage/procedures.js.map +1 -0
  77. package/dist/storage/serialization.d.ts +123 -0
  78. package/dist/storage/serialization.d.ts.map +1 -0
  79. package/dist/storage/serialization.js +216 -0
  80. package/dist/storage/serialization.js.map +1 -0
  81. package/dist/storage/synced-registry.d.ts +246 -0
  82. package/dist/storage/synced-registry.d.ts.map +1 -0
  83. package/dist/storage/synced-registry.js +575 -0
  84. package/dist/storage/synced-registry.js.map +1 -0
  85. package/dist/storage/types.d.ts +130 -0
  86. package/dist/storage/types.d.ts.map +1 -0
  87. package/dist/storage/types.js +8 -0
  88. package/dist/storage/types.js.map +1 -0
  89. package/dist/types.d.ts +7 -0
  90. package/dist/types.d.ts.map +1 -0
  91. package/dist/types.js +7 -0
  92. package/dist/types.js.map +1 -0
  93. package/package.json +79 -3
  94. package/src/errors.ts +20 -0
  95. package/src/index.ts +19 -0
  96. package/src/module.ts +18 -0
  97. package/src/procedures.ts +17 -0
  98. package/src/program-store/attach.ts +46 -0
  99. package/src/program-store/collection.ts +39 -0
  100. package/src/program-store/file.ts +157 -0
  101. package/src/program-store/index.ts +17 -0
  102. package/src/program-store/memory.ts +30 -0
  103. package/src/program-store/procedures.ts +344 -0
  104. package/src/program-store/types.ts +78 -0
  105. package/src/register.ts +18 -0
  106. package/src/storage/adapter.ts +273 -0
  107. package/src/storage/api.ts +335 -0
  108. package/src/storage/factory.ts +268 -0
  109. package/src/storage/hybrid.ts +676 -0
  110. package/src/storage/index.ts +61 -0
  111. package/src/storage/procedures.ts +146 -0
  112. package/src/storage/serialization.ts +283 -0
  113. package/src/storage/synced-registry.ts +698 -0
  114. package/src/storage/types.ts +180 -0
  115. package/src/types.ts +7 -0
@@ -0,0 +1,344 @@
1
+ /**
2
+ * The procedures of the program store (roadmap 3.2):
3
+ *
4
+ * - `procedure.store`: write the program of a runtime-defined procedure to the store.
5
+ * - `procedure.load`: define the stored programs again, through `procedure.define`.
6
+ * - `procedure.sync`: push the programs of the registry, pull the stored ones, or both.
7
+ * - `procedure.remote`: the status of the store. A store's backend is fixed when a host makes
8
+ * it, so `connect` and `disconnect` are NOT_IMPLEMENTED: a procedure that could point the
9
+ * store at a new endpoint would give a caller network reach.
10
+ *
11
+ * They use the program store of the caller's registry (`attachProgramStore`). A loaded program
12
+ * is a runtime-defined procedure again: data-driven, so a server with an expose rule lets it
13
+ * call only the exposed procedures. Load programs only from a store that you trust: a host with
14
+ * no expose rule runs whatever a program calls.
15
+ */
16
+
17
+ import { z } from "zod";
18
+ import {
19
+ createProcedure,
20
+ getAllRuntimeProcedures,
21
+ keyToPath,
22
+ outputSchema,
23
+ pathToKey,
24
+ runtimeProgramOf,
25
+ zodAdapter,
26
+ type AnyProcedure,
27
+ type ProcedureContext,
28
+ type ProcedurePath,
29
+ } from "@procedures/client";
30
+ import { ProcedureStoreError } from "../errors.js";
31
+ import { callerRegistry, getProgramStore, requireProgramStore } from "./attach.js";
32
+ import type { ProgramStore, StoredProgram } from "./types.js";
33
+
34
+ // =============================================================================
35
+ // Shared
36
+ // =============================================================================
37
+
38
+ const pathField = z
39
+ .union([z.array(z.string().min(1)).min(1), z.string().min(1)])
40
+ .describe('The path of the procedure: a list of segments, or a dotted key such as "reports.weekly"');
41
+
42
+ function toPath(path: ProcedurePath | string): ProcedurePath {
43
+ return typeof path === "string" ? keyToPath(path) : path;
44
+ }
45
+
46
+ /** The metadata to store: the procedure's metadata without the program (the store keeps that). */
47
+ function storedMetadata(metadata: Record<string, unknown> | undefined): Record<string, unknown> | undefined {
48
+ if (!metadata) return undefined;
49
+ const { aggregation: _aggregation, generatedFrom: _generatedFrom, ...rest } = metadata;
50
+ return Object.keys(rest).length > 0 ? rest : undefined;
51
+ }
52
+
53
+ /** The record of a runtime-defined procedure, or a NOT_A_PROGRAM error for a code procedure. */
54
+ function recordOf(ctx: ProcedureContext, path: ProcedurePath): StoredProgram {
55
+ const key = pathToKey(path);
56
+ const procedure = callerRegistry(ctx).get(path);
57
+ if (!procedure) {
58
+ throw new Error(`Procedure not found: ${key}`);
59
+ }
60
+ const program = runtimeProgramOf(procedure);
61
+ if (!program) {
62
+ throw new ProcedureStoreError(
63
+ "NOT_A_PROGRAM",
64
+ `${key} is a code procedure. Only a procedure that procedure.define made is a program: code lives in a package.`
65
+ );
66
+ }
67
+ return { key, path: [...procedure.path], program, metadata: storedMetadata(procedure.metadata), storedAt: Date.now() };
68
+ }
69
+
70
+ interface LoadResult {
71
+ loaded: number;
72
+ conflicts: Array<{ path: ProcedurePath; reason: string }>;
73
+ }
74
+
75
+ /** Define each program again in the caller's registry, through procedure.define. */
76
+ async function definePrograms(ctx: ProcedureContext, programs: StoredProgram[]): Promise<LoadResult> {
77
+ const result: LoadResult = { loaded: 0, conflicts: [] };
78
+ for (const stored of programs) {
79
+ try {
80
+ await ctx.client.call(["procedure", "define"], {
81
+ path: stored.path,
82
+ aggregation: stored.program,
83
+ ...(stored.metadata ? { metadata: stored.metadata } : {}),
84
+ // A stored program replaces a runtime-defined procedure. procedure.define never replaces
85
+ // a code procedure: that is a conflict.
86
+ replace: true,
87
+ });
88
+ result.loaded++;
89
+ } catch (error) {
90
+ result.conflicts.push({ path: stored.path, reason: error instanceof Error ? error.message : String(error) });
91
+ }
92
+ }
93
+ return result;
94
+ }
95
+
96
+ // =============================================================================
97
+ // procedure.store
98
+ // =============================================================================
99
+
100
+ interface StoreInput {
101
+ path: ProcedurePath | string;
102
+ }
103
+
104
+ interface StoreOutput {
105
+ stored: true;
106
+ path: ProcedurePath;
107
+ storedAt: number;
108
+ }
109
+
110
+ export const procedureStoreProcedure: AnyProcedure = createProcedure()
111
+ .path(["procedure", "store"])
112
+ .input(zodAdapter<StoreInput>(z.object({ path: pathField })))
113
+ .output(
114
+ zodAdapter<StoreOutput>(
115
+ z.object({
116
+ stored: z.literal(true).describe("True: the store has the program"),
117
+ path: z.array(z.string()).describe("The path of the procedure"),
118
+ storedAt: z.number().describe("When the store got the program, in milliseconds since 1970"),
119
+ })
120
+ )
121
+ )
122
+ .meta({
123
+ description: "Write the program of a procedure that procedure.define made to the program store",
124
+ tags: ["procedure", "registry", "storage"],
125
+ args: ["path"],
126
+ })
127
+ .handler(async (input: StoreInput, ctx: ProcedureContext): Promise<StoreOutput> => {
128
+ const store = requireProgramStore(ctx, "procedure.store");
129
+ const record = recordOf(ctx, toPath(input.path));
130
+ await store.put(record);
131
+ return { stored: true, path: record.path, storedAt: record.storedAt };
132
+ })
133
+ .build();
134
+
135
+ // =============================================================================
136
+ // procedure.load
137
+ // =============================================================================
138
+
139
+ interface LoadInput {
140
+ path?: ProcedurePath | string | undefined;
141
+ prefix?: ProcedurePath | string | undefined;
142
+ all?: boolean | undefined;
143
+ }
144
+
145
+ interface LoadOutput {
146
+ /** The number of programs that the load defined */
147
+ loaded: number;
148
+ /** The paths of the stored programs that matched */
149
+ paths: ProcedurePath[];
150
+ /** The programs that the load could not define (a code procedure at the path, an invalid program) */
151
+ conflicts: Array<{ path: ProcedurePath; reason: string }>;
152
+ message: string;
153
+ }
154
+
155
+ const conflictsField = z
156
+ .array(z.object({ path: z.array(z.string()), reason: z.string() }))
157
+ .describe("The programs that could not be defined, with the reason");
158
+
159
+ export const procedureLoadProcedure: AnyProcedure = createProcedure()
160
+ .path(["procedure", "load"])
161
+ .input(
162
+ zodAdapter<LoadInput>(
163
+ z.object({
164
+ path: pathField.optional(),
165
+ prefix: pathField.optional().describe("Load the programs of this path and under it"),
166
+ all: z.boolean().optional().describe("Load every stored program"),
167
+ })
168
+ )
169
+ )
170
+ .output(
171
+ zodAdapter<LoadOutput>(
172
+ z.object({
173
+ loaded: z.number().describe("The number of programs that the load defined"),
174
+ paths: z.array(z.array(z.string())).describe("The paths of the stored programs that matched"),
175
+ conflicts: conflictsField,
176
+ message: z.string(),
177
+ })
178
+ )
179
+ )
180
+ .meta({
181
+ description: "Define the stored programs again (procedure.define), after a restart",
182
+ tags: ["procedure", "registry", "storage"],
183
+ calls: ["procedure.define"],
184
+ })
185
+ .handler(async (input: LoadInput, ctx: ProcedureContext): Promise<LoadOutput> => {
186
+ const chosen = [input.path !== undefined, input.prefix !== undefined, input.all === true].filter(Boolean).length;
187
+ if (chosen !== 1) {
188
+ throw new Error("procedure.load needs exactly one of `path`, `prefix` or `all: true`");
189
+ }
190
+ const store = requireProgramStore(ctx, "procedure.load");
191
+ let programs: StoredProgram[];
192
+ let what: string;
193
+ if (input.path !== undefined) {
194
+ what = pathToKey(toPath(input.path));
195
+ const program = await store.get(what);
196
+ programs = program ? [program] : [];
197
+ } else if (input.prefix !== undefined) {
198
+ const prefix = pathToKey(toPath(input.prefix));
199
+ what = `${prefix}.*`;
200
+ programs = await store.list(prefix);
201
+ } else {
202
+ what = "all programs";
203
+ programs = await store.list();
204
+ }
205
+ const result = await definePrograms(ctx, programs);
206
+ return {
207
+ loaded: result.loaded,
208
+ paths: programs.map((p) => p.path),
209
+ conflicts: result.conflicts,
210
+ message: `Loaded ${result.loaded} of ${programs.length} stored program(s) for ${what}`,
211
+ };
212
+ })
213
+ .build();
214
+
215
+ // =============================================================================
216
+ // procedure.sync
217
+ // =============================================================================
218
+
219
+ interface SyncInput {
220
+ direction?: "push" | "pull" | "both" | undefined;
221
+ }
222
+
223
+ interface SyncOutput {
224
+ pushed: number;
225
+ pulled: number;
226
+ conflicts: Array<{ path: ProcedurePath; reason: string }>;
227
+ message: string;
228
+ }
229
+
230
+ /** Write each runtime-defined procedure of the caller's registry to the store. */
231
+ async function push(ctx: ProcedureContext, store: ProgramStore): Promise<number> {
232
+ let pushed = 0;
233
+ for (const procedure of getAllRuntimeProcedures(callerRegistry(ctx))) {
234
+ await store.put(recordOf(ctx, procedure.path));
235
+ pushed++;
236
+ }
237
+ return pushed;
238
+ }
239
+
240
+ export const procedureSyncProcedure: AnyProcedure = createProcedure()
241
+ .path(["procedure", "sync"])
242
+ .input(
243
+ zodAdapter<SyncInput>(
244
+ z.object({
245
+ direction: z
246
+ .enum(["push", "pull", "both"])
247
+ .optional()
248
+ .describe(
249
+ "push: write the programs of the registry to the store. pull: define the stored programs. " +
250
+ "both (the default): push, then pull the stored programs that the registry does not have"
251
+ ),
252
+ })
253
+ )
254
+ )
255
+ .output(
256
+ zodAdapter<SyncOutput>(
257
+ z.object({
258
+ pushed: z.number().describe("The number of programs written to the store"),
259
+ pulled: z.number().describe("The number of stored programs defined in the registry"),
260
+ conflicts: conflictsField,
261
+ message: z.string(),
262
+ })
263
+ )
264
+ )
265
+ .meta({
266
+ description: "Synchronize the runtime-defined procedures of the registry with the program store",
267
+ tags: ["procedure", "registry", "storage"],
268
+ calls: ["procedure.define"],
269
+ })
270
+ .handler(async (input: SyncInput, ctx: ProcedureContext): Promise<SyncOutput> => {
271
+ const direction = input.direction ?? "both";
272
+ const store = requireProgramStore(ctx, "procedure.sync");
273
+ const pushed = direction === "pull" ? 0 : await push(ctx, store);
274
+ let pulled = 0;
275
+ let conflicts: SyncOutput["conflicts"] = [];
276
+ if (direction !== "push") {
277
+ const registry = callerRegistry(ctx);
278
+ const stored = await store.list();
279
+ // With "both", the registry wins: a program that it has was pushed, and is not pulled back
280
+ const toPull = direction === "pull" ? stored : stored.filter((p) => !registry.get(p.path));
281
+ const result = await definePrograms(ctx, toPull);
282
+ pulled = result.loaded;
283
+ conflicts = result.conflicts;
284
+ }
285
+ return { pushed, pulled, conflicts, message: `Sync ${direction}: pushed ${pushed}, pulled ${pulled}` };
286
+ })
287
+ .build();
288
+
289
+ // =============================================================================
290
+ // procedure.remote
291
+ // =============================================================================
292
+
293
+ interface RemoteInput {
294
+ action: "status" | "connect" | "disconnect";
295
+ }
296
+
297
+ interface RemoteOutput {
298
+ /** True when the caller's registry has a program store */
299
+ configured: boolean;
300
+ /** The kind of the store: memory, file, collection */
301
+ kind?: string | undefined;
302
+ message: string;
303
+ }
304
+
305
+ export const procedureRemoteProcedure: AnyProcedure = createProcedure()
306
+ .path(["procedure", "remote"])
307
+ .input(
308
+ zodAdapter<RemoteInput>(
309
+ z.object({
310
+ action: z
311
+ .enum(["status", "connect", "disconnect"])
312
+ .describe("status: the store of the registry. connect and disconnect: not implemented (the host makes the store)"),
313
+ })
314
+ )
315
+ )
316
+ .output(outputSchema<RemoteOutput>())
317
+ .meta({
318
+ description: "The status of the program store of the registry",
319
+ tags: ["procedure", "registry", "storage"],
320
+ args: ["action"],
321
+ })
322
+ .handler(async (input: RemoteInput, ctx: ProcedureContext): Promise<RemoteOutput> => {
323
+ if (input.action === "status") {
324
+ const store = getProgramStore(callerRegistry(ctx));
325
+ return store
326
+ ? { configured: true, kind: store.kind, message: `The registry has a ${store.kind} program store` }
327
+ : { configured: false, message: "The registry has no program store" };
328
+ }
329
+ throw new ProcedureStoreError(
330
+ "NOT_IMPLEMENTED",
331
+ `procedure.remote ${input.action} cannot change the store of a registry at run time. A host makes the store, ` +
332
+ "for example new CollectionProgramStore(new ApiStorage(client, { collection: \"programs\" })), and gives it " +
333
+ "to the registry with attachProgramStore()."
334
+ );
335
+ })
336
+ .build();
337
+
338
+ /** The procedures of the program store. */
339
+ export const programStoreProcedures: AnyProcedure[] = [
340
+ procedureStoreProcedure,
341
+ procedureLoadProcedure,
342
+ procedureSyncProcedure,
343
+ procedureRemoteProcedure,
344
+ ];
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The types of the program store.
3
+ *
4
+ * A program is procedure-as-data: the aggregation that `procedure.define` turns into a
5
+ * procedure. A program store keeps programs after the process ends. `procedure.load` defines
6
+ * them again, so a stored program is a runtime-defined procedure again: a data-driven one, which
7
+ * a server with an expose rule lets call only the exposed procedures.
8
+ */
9
+
10
+ import type { AggregationDefinition, ProcedurePath } from "@procedures/client";
11
+
12
+ /** One stored program. */
13
+ export interface StoredProgram {
14
+ /** The dotted key of the path: `reports.weekly`. */
15
+ key: string;
16
+ /** The path of the procedure. */
17
+ path: ProcedurePath;
18
+ /** The program: the aggregation of `procedure.define`. */
19
+ program: AggregationDefinition;
20
+ /** The metadata of the procedure (its description, its tags), without the program. */
21
+ metadata?: Record<string, unknown> | undefined;
22
+ /** When the store got this version, in milliseconds since 1970. */
23
+ storedAt: number;
24
+ }
25
+
26
+ /**
27
+ * A store of programs. Each method gives a Promise: a caller cannot tell a local store from a
28
+ * remote one.
29
+ */
30
+ export interface ProgramStore {
31
+ /** The kind of the backend: "memory", "file", "collection" or another name. */
32
+ readonly kind: string;
33
+ /** The program of a key, or undefined. */
34
+ get(key: string): Promise<StoredProgram | undefined>;
35
+ /** The programs, sorted by key. With a prefix, only the key and the keys under it (`prefix.x`). */
36
+ list(prefix?: string): Promise<StoredProgram[]>;
37
+ /** Write a program. A program of the same key is replaced. */
38
+ put(program: StoredProgram): Promise<void>;
39
+ /** Delete the program of a key. True when it existed. */
40
+ delete(key: string): Promise<boolean>;
41
+ /** Release the resources of the store. */
42
+ close?(): Promise<void>;
43
+ }
44
+
45
+ /** True when a key is the prefix or under it (`a.b` for the prefix `a`). */
46
+ export function keyMatches(key: string, prefix: string | undefined): boolean {
47
+ return prefix === undefined || prefix === "" || key === prefix || key.startsWith(`${prefix}.`);
48
+ }
49
+
50
+ /** Programs sorted by key, so a listing is the same for each backend. */
51
+ export function byKey(programs: StoredProgram[]): StoredProgram[] {
52
+ return programs.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
53
+ }
54
+
55
+ /**
56
+ * The record to store, checked: a stored program comes back from a file or a remote service, so
57
+ * its shape is not trusted.
58
+ */
59
+ export function checkStoredProgram(value: unknown): StoredProgram {
60
+ if (typeof value !== "object" || value === null) {
61
+ throw new TypeError("A stored program must be an object");
62
+ }
63
+ const record = value as Partial<StoredProgram>;
64
+ if (!Array.isArray(record.path) || record.path.length === 0 || !record.path.every((s) => typeof s === "string" && s !== "")) {
65
+ throw new TypeError("A stored program needs a path: a list of non-empty strings");
66
+ }
67
+ if (record.key !== record.path.join(".")) {
68
+ throw new TypeError(`The key of a stored program must be its path with dots: ${record.path.join(".")}`);
69
+ }
70
+ const program = record.program as { $proc?: unknown } | undefined;
71
+ if (typeof program !== "object" || program === null || !Array.isArray(program.$proc)) {
72
+ throw new TypeError(`The program of ${record.key} must be a procedure ref: { "$proc": [...], "input": ... }`);
73
+ }
74
+ if (typeof record.storedAt !== "number" || !Number.isFinite(record.storedAt)) {
75
+ throw new TypeError(`The program of ${record.key} needs storedAt, a time in milliseconds`);
76
+ }
77
+ return record as StoredProgram;
78
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Importing this file registers the procedures of @procedures/store in the global
3
+ * registry (DECISIONS-2026-10, "Every package root registers its procedures"). The procedures as
4
+ * data are in `module.ts`, which registers nothing: a host with its own registry uses that one.
5
+ */
6
+
7
+ import { PROCEDURE_REGISTRY } from "@procedures/client";
8
+ import { storeModule } from "./module.js";
9
+
10
+ export * from "./module.js";
11
+
12
+ export function register(): void {
13
+ // Override makes the registration idempotent (two copies of the package in one process)
14
+ PROCEDURE_REGISTRY.registerAll(storeModule.procedures, { override: true });
15
+ }
16
+
17
+ // Auto-register
18
+ register();