@peers-app/peers-sdk 0.26.5 → 0.27.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 (194) hide show
  1. package/README.md +3 -1
  2. package/bin/peers-package.js +13 -0
  3. package/define/package.json +4 -0
  4. package/dist/contracts/__tests__/action-dispatch.test.js +56 -76
  5. package/dist/contracts/__tests__/also-implements.test.d.ts +1 -0
  6. package/dist/contracts/__tests__/also-implements.test.js +110 -0
  7. package/dist/contracts/__tests__/builder.test.js +45 -0
  8. package/dist/contracts/__tests__/contract-resolvers.test.js +134 -4
  9. package/dist/contracts/__tests__/frozen-contracts.test.d.ts +1 -0
  10. package/dist/contracts/__tests__/frozen-contracts.test.js +191 -0
  11. package/dist/contracts/__tests__/generic-tool-dispatch.test.js +13 -56
  12. package/dist/contracts/__tests__/host-contract-client.test.d.ts +1 -0
  13. package/dist/contracts/__tests__/host-contract-client.test.js +793 -0
  14. package/dist/contracts/__tests__/host-contract-transport.test.js +15 -0
  15. package/dist/contracts/__tests__/registry.test.js +68 -0
  16. package/dist/contracts/__tests__/target-dispatch.d.ts +11 -0
  17. package/dist/contracts/__tests__/target-dispatch.js +24 -0
  18. package/dist/contracts/__tests__/tool-catalog-service.test.js +37 -1
  19. package/dist/contracts/__tests__/tool-catalog.test.js +12 -3
  20. package/dist/contracts/__tests__/validate.test.js +177 -7
  21. package/dist/contracts/__tests__/workflow-context.test.js +0 -11
  22. package/dist/contracts/builder.d.ts +16 -2
  23. package/dist/contracts/builder.js +44 -3
  24. package/dist/contracts/contract-definition-schema.d.ts +251 -0
  25. package/dist/contracts/contract-definition-schema.js +48 -0
  26. package/dist/contracts/contract-provider-router.d.ts +2 -0
  27. package/dist/contracts/contract-provider-router.js +5 -2
  28. package/dist/contracts/contract-proxy.d.ts +52 -18
  29. package/dist/contracts/contract-proxy.js +144 -203
  30. package/dist/contracts/contract-resolvers.d.ts +6 -4
  31. package/dist/contracts/contract-resolvers.js +32 -9
  32. package/dist/contracts/frozen-contracts.d.ts +59 -0
  33. package/dist/contracts/frozen-contracts.js +86 -0
  34. package/dist/contracts/host-contract-client.d.ts +134 -0
  35. package/dist/contracts/host-contract-client.js +396 -0
  36. package/dist/contracts/host-contract-transport.d.ts +2 -0
  37. package/dist/contracts/host-contract-transport.js +8 -0
  38. package/dist/contracts/index.d.ts +6 -4
  39. package/dist/contracts/index.js +11 -5
  40. package/dist/contracts/registry.d.ts +31 -1
  41. package/dist/contracts/registry.js +100 -6
  42. package/dist/contracts/system/contract-catalog-contract.d.ts +61 -6
  43. package/dist/contracts/system/contract-catalog-contract.js +17 -5
  44. package/dist/contracts/tool-catalog-service.js +26 -6
  45. package/dist/contracts/tool-catalog.d.ts +1 -1
  46. package/dist/contracts/tool-catalog.js +4 -1
  47. package/dist/contracts/types.d.ts +32 -2
  48. package/dist/contracts/validate.d.ts +16 -1
  49. package/dist/contracts/validate.js +139 -25
  50. package/dist/data/change-tracking.d.ts +17 -5
  51. package/dist/data/change-tracking.js +22 -6
  52. package/dist/data/change-tracking.test.d.ts +1 -0
  53. package/dist/data/change-tracking.test.js +62 -0
  54. package/dist/data/group-members.d.ts +6 -1
  55. package/dist/data/group-members.js +12 -4
  56. package/dist/data/groups.d.ts +6 -1
  57. package/dist/data/groups.js +12 -4
  58. package/dist/data/orm/sql.data-source.d.ts +9 -0
  59. package/dist/data/orm/sql.data-source.js +47 -2
  60. package/dist/data/orm/sql.data-source.test.js +142 -0
  61. package/dist/data/orm/table-container.js +9 -9
  62. package/dist/data/orm/table-container.test.js +44 -0
  63. package/dist/data/orm/types.d.ts +41 -1
  64. package/dist/data/orm/types.js +159 -28
  65. package/dist/data/orm/types.test.js +129 -0
  66. package/dist/data/package-versions.d.ts +5 -1
  67. package/dist/data/package-versions.js +11 -4
  68. package/dist/data/packages.d.ts +7 -1
  69. package/dist/data/packages.js +13 -4
  70. package/dist/data/peer-types.d.ts +8 -62
  71. package/dist/data/persistent-vars.d.ts +4 -1
  72. package/dist/data/persistent-vars.js +7 -2
  73. package/dist/data/table-signing.test.d.ts +1 -0
  74. package/dist/data/table-signing.test.js +118 -0
  75. package/dist/data/tools.d.ts +29 -221
  76. package/dist/data/users.d.ts +1 -2
  77. package/dist/data/users.js +8 -5
  78. package/dist/define/also-implements.test.d.ts +1 -0
  79. package/dist/define/also-implements.test.js +123 -0
  80. package/dist/define/context.d.ts +77 -0
  81. package/dist/define/context.js +2 -0
  82. package/dist/define/contract-ref.d.ts +177 -0
  83. package/dist/define/contract-ref.js +67 -0
  84. package/dist/define/declare-contract.d.ts +167 -0
  85. package/dist/define/declare-contract.js +220 -0
  86. package/dist/define/declare-contract.test.d.ts +1 -0
  87. package/dist/define/declare-contract.test.js +573 -0
  88. package/dist/define/define-package.d.ts +203 -0
  89. package/dist/define/define-package.js +289 -0
  90. package/dist/define/define.test.d.ts +1 -0
  91. package/dist/define/define.test.js +1010 -0
  92. package/dist/define/empty-fields.d.ts +15 -0
  93. package/dist/define/empty-fields.js +134 -0
  94. package/dist/define/empty-fields.test.d.ts +1 -0
  95. package/dist/define/empty-fields.test.js +55 -0
  96. package/dist/define/events-observables.test.d.ts +1 -0
  97. package/dist/define/events-observables.test.js +1013 -0
  98. package/dist/define/events.d.ts +81 -0
  99. package/dist/define/events.js +81 -0
  100. package/dist/define/generated-field.d.ts +20 -0
  101. package/dist/define/generated-field.js +25 -0
  102. package/dist/define/index.d.ts +28 -0
  103. package/dist/define/index.js +62 -0
  104. package/dist/define/lazy-members.d.ts +8 -0
  105. package/dist/define/lazy-members.js +38 -0
  106. package/dist/define/manifest.d.ts +63 -0
  107. package/dist/define/manifest.js +653 -0
  108. package/dist/define/observables.d.ts +146 -0
  109. package/dist/define/observables.js +187 -0
  110. package/dist/define/serve-members.d.ts +21 -0
  111. package/dist/define/serve-members.js +152 -0
  112. package/dist/define/serve-members.test.d.ts +1 -0
  113. package/dist/define/serve-members.test.js +630 -0
  114. package/dist/define/serve.d.ts +22 -0
  115. package/dist/define/serve.js +58 -0
  116. package/dist/define/system-contracts.d.ts +41 -0
  117. package/dist/define/system-contracts.js +21 -0
  118. package/dist/define/table-handle.d.ts +13 -0
  119. package/dist/define/table-handle.js +36 -0
  120. package/dist/define/table.d.ts +128 -0
  121. package/dist/define/table.js +122 -0
  122. package/dist/define/tool.d.ts +129 -0
  123. package/dist/define/tool.js +91 -0
  124. package/dist/define/vars.d.ts +82 -0
  125. package/dist/define/vars.js +256 -0
  126. package/dist/define/vars.test.d.ts +1 -0
  127. package/dist/define/vars.test.js +39 -0
  128. package/dist/define/wire-value.d.ts +12 -0
  129. package/dist/define/wire-value.js +76 -0
  130. package/dist/define/worker-state.d.ts +15 -0
  131. package/dist/define/worker-state.js +45 -0
  132. package/dist/device/tx-encoding.test.js +10 -0
  133. package/dist/index.d.ts +3 -0
  134. package/dist/index.js +5 -1
  135. package/dist/observable.d.ts +42 -2
  136. package/dist/observable.js +25 -7
  137. package/dist/package-build/build-package.d.ts +60 -0
  138. package/dist/package-build/build-package.js +312 -0
  139. package/dist/package-build/build-package.test.d.ts +1 -0
  140. package/dist/package-build/build-package.test.js +204 -0
  141. package/dist/package-build/cli.d.ts +7 -0
  142. package/dist/package-build/cli.js +218 -0
  143. package/dist/package-build/contract-add.d.ts +35 -0
  144. package/dist/package-build/contract-add.js +97 -0
  145. package/dist/package-build/contract-add.test.d.ts +1 -0
  146. package/dist/package-build/contract-add.test.js +467 -0
  147. package/dist/package-build/contract-source.d.ts +59 -0
  148. package/dist/package-build/contract-source.js +471 -0
  149. package/dist/package-build/freeze-contracts.d.ts +56 -0
  150. package/dist/package-build/freeze-contracts.js +198 -0
  151. package/dist/package-build/freeze-contracts.test.d.ts +1 -0
  152. package/dist/package-build/freeze-contracts.test.js +355 -0
  153. package/dist/package-installer/package-creator.d.ts +4 -0
  154. package/dist/package-installer/package-creator.js +57 -16
  155. package/dist/package-installer/package-creator.test.js +86 -0
  156. package/dist/package-loader/contract-members.d.ts +53 -0
  157. package/dist/package-loader/contract-members.js +31 -0
  158. package/dist/package-loader/contract-package-loader.js +5 -1
  159. package/dist/package-loader/contract-package-loader.test.js +11 -0
  160. package/dist/package-loader/host-authority-loading.test.js +9 -5
  161. package/dist/package-loader/index.d.ts +1 -0
  162. package/dist/package-loader/index.js +1 -0
  163. package/dist/package-loader/isolated-artifact.d.ts +80 -17
  164. package/dist/package-loader/isolated-artifact.js +257 -51
  165. package/dist/package-loader/isolated-artifact.test.js +294 -21
  166. package/dist/package-loader/isolated-runtime.d.ts +22 -7
  167. package/dist/package-loader/isolated-table-gateway.js +30 -11
  168. package/dist/package-loader/isolated-table-gateway.test.js +243 -0
  169. package/dist/package-loader/package-loader.d.ts +8 -1
  170. package/dist/package-loader/package-loader.js +39 -20
  171. package/dist/package-loader/package-loader.test.js +47 -0
  172. package/dist/peer-id.d.ts +25 -0
  173. package/dist/peer-id.js +99 -0
  174. package/dist/serial-json.compat.test.d.ts +1 -0
  175. package/dist/serial-json.compat.test.js +369 -0
  176. package/dist/serial-json.d.ts +41 -5
  177. package/dist/serial-json.js +216 -157
  178. package/dist/serial-json.oracle.d.ts +21 -0
  179. package/dist/serial-json.oracle.js +222 -0
  180. package/dist/serial-json.test.js +315 -0
  181. package/dist/testing/index.d.ts +4 -0
  182. package/dist/testing/index.js +8 -0
  183. package/dist/testing/mock-host-contract.d.ts +123 -0
  184. package/dist/testing/mock-host-contract.js +434 -0
  185. package/dist/testing/mock-host-contract.test.d.ts +1 -0
  186. package/dist/testing/mock-host-contract.test.js +782 -0
  187. package/dist/types/field-type.d.ts +35 -18
  188. package/dist/types/field-type.js +4 -1
  189. package/dist/types/zod-types.d.ts +9 -0
  190. package/dist/types/zod-types.js +8 -2
  191. package/dist/utils.d.ts +1 -25
  192. package/dist/utils.js +10 -90
  193. package/package.json +9 -2
  194. package/testing/package.json +4 -0
@@ -0,0 +1,653 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.irFields = irFields;
4
+ exports.schemaValueType = schemaValueType;
5
+ exports.fitsValueType = fitsValueType;
6
+ exports.contractManifest = contractManifest;
7
+ exports.buildPackageManifest = buildPackageManifest;
8
+ const zod_1 = require("zod");
9
+ const validate_1 = require("../contracts/validate");
10
+ const types_1 = require("../data/orm/types");
11
+ const isolated_artifact_1 = require("../package-loader/isolated-artifact");
12
+ const peer_id_1 = require("../peer-id");
13
+ const field_type_1 = require("../types/field-type");
14
+ const events_1 = require("./events");
15
+ const generated_field_1 = require("./generated-field");
16
+ const observables_1 = require("./observables");
17
+ const OPTIONAL_INPUT_TYPES = new Set(["ZodDefault", "ZodOptional", "ZodCatch"]);
18
+ /**
19
+ * The scalar type of an enum or literal, which `schemaToFields` leaves as `any`: an enum or a
20
+ * string literal is `string`, as for event payloads and observable values.
21
+ */
22
+ function literalType(schema) {
23
+ let def = schema._def;
24
+ let array = false;
25
+ while (def.innerType ||
26
+ def.schema ||
27
+ def.typeName === "ZodBranded" ||
28
+ def.typeName === "ZodArray") {
29
+ // These wrappers can produce null, nested arrays, or a transformed value. Narrowing
30
+ // their former `any` type would make the host refuse valid inputs or stored rows.
31
+ if (def.typeName === "ZodNullable" ||
32
+ (def.typeName === "ZodEffects" && def.effect.type === "transform") ||
33
+ (array && (def.typeName === "ZodArray" || def.typeName === "ZodOptional"))) {
34
+ return undefined;
35
+ }
36
+ if (def.typeName === "ZodArray")
37
+ array = true;
38
+ def = (def.innerType ?? def.schema ?? def.type)._def;
39
+ }
40
+ if (def.typeName === "ZodEnum")
41
+ return field_type_1.FieldType.string;
42
+ if (def.typeName !== "ZodLiteral")
43
+ return undefined;
44
+ if (typeof def.value === "string")
45
+ return field_type_1.FieldType.string;
46
+ if (typeof def.value === "number")
47
+ return field_type_1.FieldType.number;
48
+ if (typeof def.value === "boolean")
49
+ return field_type_1.FieldType.boolean;
50
+ return undefined;
51
+ }
52
+ /** The scalar values an enum, a literal, or a union of them allows; undefined for anything else. */
53
+ function enumValuesOf(schema) {
54
+ const def = schema._def;
55
+ switch (def.typeName) {
56
+ case "ZodEnum":
57
+ return [...def.values];
58
+ case "ZodNativeEnum": {
59
+ // A numeric TypeScript enum maps each value back to its name too; skip those keys.
60
+ const values = def.values;
61
+ return Object.keys(values)
62
+ .filter((key) => typeof values[values[key]] !== "number")
63
+ .map((key) => values[key]);
64
+ }
65
+ case "ZodLiteral":
66
+ return ["string", "number", "boolean"].includes(typeof def.value) ? [def.value] : undefined;
67
+ case "ZodUnion": {
68
+ const values = [];
69
+ for (const option of def.options) {
70
+ const optionValues = enumValuesOf(option);
71
+ if (!optionValues) {
72
+ return undefined;
73
+ }
74
+ values.push(...optionValues);
75
+ }
76
+ return values;
77
+ }
78
+ default:
79
+ return undefined;
80
+ }
81
+ }
82
+ /**
83
+ * What a field's schema says beyond its type: an object's own fields and the values an enum or
84
+ * literal allows. Walks the wrappers `schemaToFields` walks, into one array's elements, and
85
+ * stops at a transform, a preprocess, a pipeline, or a nested array, where the schema no longer
86
+ * describes the value in IR terms. Only details that fit `type` are kept, preserving the
87
+ * field's existing scalar type.
88
+ *
89
+ * A field has one empty value in Peers, missing, so `.nullable()` is refused unless the field is
90
+ * also `.optional()` (a `null` sent for it is removed) and has no default (an empty field gets
91
+ * the default). `.optional().nullable()` is marked `optional`, which `schemaToFields` misses.
92
+ */
93
+ function fieldDetails(schema, shape, input, depth, path, event = false, wholeValue = false) {
94
+ const { type } = shape;
95
+ let current = schema;
96
+ let nullable = false;
97
+ let optional = false;
98
+ let defaulted = false;
99
+ let inArray = false;
100
+ let emptyElement = false;
101
+ walk: while (true) {
102
+ const def = current._def;
103
+ switch (def.typeName) {
104
+ case "ZodNullable":
105
+ nullable ||= !inArray;
106
+ emptyElement ||= inArray;
107
+ current = def.innerType;
108
+ continue;
109
+ case "ZodOptional":
110
+ optional ||= !inArray;
111
+ emptyElement ||= inArray;
112
+ current = def.innerType;
113
+ continue;
114
+ case "ZodDefault":
115
+ case "ZodCatch":
116
+ defaulted ||= !inArray;
117
+ current = def.innerType;
118
+ continue;
119
+ case "ZodReadonly":
120
+ current = def.innerType;
121
+ continue;
122
+ case "ZodBranded":
123
+ current = def.type;
124
+ continue;
125
+ case "ZodEffects":
126
+ if (def.effect.type !== "refinement") {
127
+ break walk;
128
+ }
129
+ current = def.schema;
130
+ continue;
131
+ case "ZodArray":
132
+ if (inArray) {
133
+ break walk;
134
+ }
135
+ inArray = true;
136
+ current = def.type;
137
+ continue;
138
+ default:
139
+ break walk;
140
+ }
141
+ }
142
+ if (!wholeValue && nullable && defaulted) {
143
+ throw new Error(`Field "${path}" has a default and uses .nullable(); an empty field gets its default in Peers, so drop .nullable()`);
144
+ }
145
+ if (!wholeValue && nullable && !optional) {
146
+ throw new Error(`Field "${path}" uses .nullable(); an empty field is a missing field in Peers, so use .optional() (a null sent for it is removed)`);
147
+ }
148
+ const details = nullable || (wholeValue && optional) ? { optional: true } : {};
149
+ if (current._def.typeName === "ZodObject" &&
150
+ type === field_type_1.FieldType.object &&
151
+ depth < isolated_artifact_1.ISOLATED_MAX_FIELD_DEPTH) {
152
+ details.fields = event
153
+ ? eventFields(current, depth + 1, path)
154
+ : irFields(current, input, depth + 1, path);
155
+ }
156
+ const values = enumValuesOf(current);
157
+ if (!emptyElement && values?.length && values.every((value) => (0, isolated_artifact_1.enumValueFits)(value, type))) {
158
+ details.enumValues = [...new Set(values)];
159
+ }
160
+ return details;
161
+ }
162
+ /** Run `build`, naming `label` in an error it throws. */
163
+ function describing(label, build) {
164
+ try {
165
+ return build();
166
+ }
167
+ catch (error) {
168
+ throw new Error(`${label}: ${error instanceof Error ? error.message : String(error)}`);
169
+ }
170
+ }
171
+ /**
172
+ * A zod object schema as Peers IR. Defaults stay in the package. Defaulted tool inputs may be
173
+ * omitted; table fields describe complete stored rows, after the provider parses a save. Shared
174
+ * with the mock host; not a public SDK export.
175
+ *
176
+ * @internal
177
+ * @param schema - A tool's input or output, a table's row, or an event's payload schema.
178
+ * @param input - True for tool inputs, where a defaulted field may be omitted.
179
+ * @param depth - How deep this field list nests in object `fields` (top level is 1).
180
+ * @param parent - The path of the object field these fields belong to, for error messages.
181
+ */
182
+ function irFields(schema, input = false, depth = 1, parent = "") {
183
+ return (0, types_1.schemaToFields)(schema).map((schemaField) => {
184
+ const path = parent ? `${parent}.${schemaField.name}` : schemaField.name;
185
+ return irField(schema.shape[schemaField.name], schemaField, input, depth, path);
186
+ });
187
+ }
188
+ /** Describe a field, or a synthetic field holding an observable's whole value. */
189
+ function irField(schema, schemaField, input, depth, path, wholeValue = false) {
190
+ const generated = (0, generated_field_1.generatedFieldMetadata)(schema);
191
+ if (generated)
192
+ return { name: schemaField.name, ...generated.shape };
193
+ const literal = schemaField.type === field_type_1.FieldType.any ? literalType(schema) : undefined;
194
+ const field = literal ? { ...schemaField, type: literal } : schemaField;
195
+ const { defaultValue: _default, ...rest } = field;
196
+ const result = typeof _default === "function" ? rest : field;
197
+ const details = fieldDetails(schema, field, input, depth, path, false, wholeValue);
198
+ // Tool callers may omit a defaulted input: the guest, rather than the host, fills it in.
199
+ // Walk refinements, readonly, and brands too, without executing schema/default code.
200
+ // `.catch()` accepts any input, a missing one included.
201
+ let current = input ? schema : undefined;
202
+ while (current) {
203
+ const def = current._def;
204
+ if (OPTIONAL_INPUT_TYPES.has(def.typeName)) {
205
+ return { ...result, optional: true, ...details };
206
+ }
207
+ current = def.innerType ?? def.schema ?? (def.typeName === "ZodBranded" ? def.type : undefined);
208
+ }
209
+ return { ...result, ...details };
210
+ }
211
+ function toolManifest(tool) {
212
+ const definition = tool.definition;
213
+ const withSchemas = "inputSchema" in definition;
214
+ return {
215
+ name: definition.name,
216
+ usageDescription: definition.usageDescription,
217
+ suggestedAccessLevel: definition.accessLevel,
218
+ ...(definition.authorityPropagation === undefined
219
+ ? {}
220
+ : { authorityPropagation: definition.authorityPropagation }),
221
+ ...(definition.private === undefined ? {} : { private: definition.private }),
222
+ ...(definition.remoteAccessLevel === undefined
223
+ ? {}
224
+ : { remoteAccessLevel: definition.remoteAccessLevel }),
225
+ ...(definition.timeoutMs === undefined ? {} : { timeoutMs: definition.timeoutMs }),
226
+ inputFields: withSchemas
227
+ ? describing(`Tool ${definition.name} input`, () => irFields(definition.inputSchema, true))
228
+ : definition.inputFields,
229
+ outputFields: withSchemas
230
+ ? describing(`Tool ${definition.name} output`, () => irFields(definition.outputSchema))
231
+ : definition.outputFields,
232
+ };
233
+ }
234
+ /** zod types whose values JSON can't carry. */
235
+ const NON_JSON_TYPES = new Set([
236
+ "ZodDate",
237
+ "ZodBigInt",
238
+ "ZodSymbol",
239
+ "ZodFunction",
240
+ "ZodMap",
241
+ "ZodSet",
242
+ "ZodPromise",
243
+ "ZodNaN",
244
+ ]);
245
+ /** Reject explicitly non-JSON schemas even when nested or hidden by a nullable wrapper. */
246
+ function assertJsonSchema(name, schema, seen = new Set()) {
247
+ if (seen.has(schema)) {
248
+ return;
249
+ }
250
+ seen.add(schema);
251
+ const def = schema._def;
252
+ if (NON_JSON_TYPES.has(def.typeName) ||
253
+ (def.typeName === "ZodLiteral" && ["bigint", "symbol"].includes(typeof def.value))) {
254
+ throw new Error(`Observable ${name} has a ${def.typeName} schema; its value must be JSON`);
255
+ }
256
+ const children = [];
257
+ switch (def.typeName) {
258
+ case "ZodObject":
259
+ children.push(...Object.values(schema.shape), def.catchall);
260
+ break;
261
+ case "ZodArray":
262
+ case "ZodBranded":
263
+ children.push(def.type);
264
+ break;
265
+ case "ZodRecord":
266
+ children.push(def.valueType);
267
+ break;
268
+ case "ZodTuple":
269
+ children.push(...def.items, def.rest);
270
+ break;
271
+ case "ZodUnion":
272
+ case "ZodDiscriminatedUnion":
273
+ children.push(...def.options);
274
+ break;
275
+ case "ZodIntersection":
276
+ children.push(def.left, def.right);
277
+ break;
278
+ case "ZodPipeline":
279
+ children.push(def.in, def.out);
280
+ break;
281
+ case "ZodLazy":
282
+ children.push(def.getter());
283
+ break;
284
+ default:
285
+ children.push(def.innerType, def.schema);
286
+ }
287
+ for (const child of children) {
288
+ if (child) {
289
+ assertJsonSchema(name, child, seen);
290
+ }
291
+ }
292
+ }
293
+ /**
294
+ * The IR value type for an observable's schema, which the host checks values against.
295
+ * Values are JSON, so a Date can't be one. Shared with the mock host; not a public SDK export.
296
+ *
297
+ * @internal
298
+ * @param name - The observable's name, for schema errors.
299
+ * @param schema - The observable's value schema.
300
+ */
301
+ function schemaValueType(name, schema) {
302
+ assertJsonSchema(name, schema);
303
+ const generated = (0, generated_field_1.generatedFieldMetadata)(schema);
304
+ if (generated?.valueType !== undefined)
305
+ return generated.valueType;
306
+ // Walk defaults, refinements, readonly, and brands to the type underneath.
307
+ let def = schema._def;
308
+ while (true) {
309
+ if (def._fieldType) {
310
+ return def._fieldType;
311
+ }
312
+ switch (def.typeName) {
313
+ // A null or missing value (published as null) fits only `any`. Do not run user
314
+ // refinements/defaults to discover this: inference must not execute schema code.
315
+ case "ZodNullable":
316
+ case "ZodOptional":
317
+ return field_type_1.FieldType.any;
318
+ case "ZodEffects":
319
+ // A transform can change the output type. Zod carries no inspectable output schema.
320
+ if (def.effect.type === "transform") {
321
+ return field_type_1.FieldType.any;
322
+ }
323
+ break;
324
+ case "ZodString":
325
+ case "ZodEnum":
326
+ return field_type_1.FieldType.string;
327
+ case "ZodNumber":
328
+ return field_type_1.FieldType.number;
329
+ case "ZodBoolean":
330
+ return field_type_1.FieldType.boolean;
331
+ case "ZodObject":
332
+ case "ZodRecord":
333
+ case "ZodArray":
334
+ case "ZodTuple":
335
+ return field_type_1.FieldType.object;
336
+ case "ZodNativeEnum":
337
+ return Object.values(def.values).some((value) => typeof value === "number")
338
+ ? field_type_1.FieldType.any
339
+ : field_type_1.FieldType.string;
340
+ case "ZodLiteral":
341
+ return typeof def.value === "string"
342
+ ? field_type_1.FieldType.string
343
+ : typeof def.value === "number"
344
+ ? field_type_1.FieldType.number
345
+ : typeof def.value === "boolean"
346
+ ? field_type_1.FieldType.boolean
347
+ : field_type_1.FieldType.any;
348
+ }
349
+ const inner = def.innerType ?? def.schema ?? (def.typeName === "ZodBranded" ? def.type : undefined);
350
+ if (!inner) {
351
+ return field_type_1.FieldType.any;
352
+ }
353
+ def = inner._def;
354
+ }
355
+ }
356
+ /**
357
+ * True when the host's observable `valueType` check accepts `value`. Shared with the mock host;
358
+ * not a public SDK export.
359
+ *
360
+ * @internal
361
+ * @param value - An incoming observable write or published value.
362
+ * @param type - The manifest's declared value type.
363
+ */
364
+ function fitsValueType(value, type) {
365
+ switch (type) {
366
+ case field_type_1.FieldType.any:
367
+ return true;
368
+ case field_type_1.FieldType.id:
369
+ return typeof value === "string" && (0, peer_id_1.isid)(value);
370
+ case field_type_1.FieldType.string:
371
+ return typeof value === "string";
372
+ case field_type_1.FieldType.number:
373
+ return typeof value === "number" && Number.isFinite(value);
374
+ case field_type_1.FieldType.boolean:
375
+ return typeof value === "boolean";
376
+ case field_type_1.FieldType.object:
377
+ return value !== null && typeof value === "object";
378
+ default:
379
+ return false;
380
+ }
381
+ }
382
+ function observableManifest(observable) {
383
+ if (!(0, observables_1.isPackageObservable)(observable)) {
384
+ return { ...observable };
385
+ }
386
+ const { definition } = observable;
387
+ const persistentVar = observable.persistentVar;
388
+ const schema = "schema" in definition ? definition.schema : undefined;
389
+ if (schema && persistentVar && !schema.safeParse(persistentVar.defaultValue).success) {
390
+ throw new Error(`Observable ${definition.name}: variable "${persistentVar.name}"'s defaultValue doesn't match the schema`);
391
+ }
392
+ const valueType = schema
393
+ ? schemaValueType(definition.name, schema)
394
+ : definition.valueType;
395
+ // A declared observable has no source yet: its implementation's is checked when provided.
396
+ if (!persistentVar && (0, observables_1.isImplementedObservable)(observable)) {
397
+ // The worker publishes the source's value when it starts (undefined as null), and the host
398
+ // rejects one that doesn't fit, which fails the start. Catch it here instead.
399
+ const initial = observable.definition.source();
400
+ if (!fitsValueType(initial ?? null, valueType)) {
401
+ const shown = initial === undefined || initial === null
402
+ ? String(initial)
403
+ : `a ${Array.isArray(initial) ? "array" : typeof initial}`;
404
+ throw new Error(`Observable ${definition.name}'s source starts as ${shown}, which doesn't match its valueType ${valueType}; the worker publishes it at start, so give the source a matching initial value or widen the schema`);
405
+ }
406
+ }
407
+ // The value's full shape follows field rules: `valueType` stays what the host checks.
408
+ const value = schema ? observableValueShape(definition.name, schema) : undefined;
409
+ return {
410
+ name: definition.name,
411
+ description: definition.description,
412
+ valueType,
413
+ ...(value ? { value } : {}),
414
+ writable: definition.writable ?? false,
415
+ ...(persistentVar
416
+ ? {
417
+ persistentVar: {
418
+ scope: persistentVar.scope,
419
+ name: persistentVar.name,
420
+ defaultValue: persistentVar.defaultValue,
421
+ },
422
+ }
423
+ : {}),
424
+ };
425
+ }
426
+ /** The output shape of one parsed event field, restricted to what event IR can express. */
427
+ function eventValueShape(schema) {
428
+ const def = schema._def;
429
+ if (def._fieldType) {
430
+ return { type: def._fieldType };
431
+ }
432
+ switch (def.typeName) {
433
+ case "ZodOptional":
434
+ case "ZodNullable":
435
+ return { ...eventValueShape(def.innerType), optional: true };
436
+ case "ZodDefault":
437
+ case "ZodReadonly":
438
+ return eventValueShape(def.innerType);
439
+ case "ZodBranded":
440
+ return eventValueShape(def.type);
441
+ case "ZodEffects":
442
+ return def.effect.type === "transform"
443
+ ? { type: field_type_1.FieldType.any, optional: true }
444
+ : eventValueShape(def.schema);
445
+ case "ZodPipeline":
446
+ return eventValueShape(def.out);
447
+ case "ZodString":
448
+ case "ZodEnum":
449
+ return { type: field_type_1.FieldType.string };
450
+ case "ZodNumber":
451
+ return { type: field_type_1.FieldType.number };
452
+ case "ZodBoolean":
453
+ return { type: field_type_1.FieldType.boolean };
454
+ case "ZodDate":
455
+ return { type: field_type_1.FieldType.Date };
456
+ case "ZodObject":
457
+ case "ZodRecord":
458
+ return { type: field_type_1.FieldType.object };
459
+ case "ZodArray": {
460
+ const element = eventValueShape(def.type);
461
+ return {
462
+ // Event IR can't express nullable elements or nested arrays with a scalar type.
463
+ type: element.optional || element.isArray ? field_type_1.FieldType.any : element.type,
464
+ isArray: true,
465
+ };
466
+ }
467
+ case "ZodTuple":
468
+ return { type: field_type_1.FieldType.any, isArray: true };
469
+ case "ZodLiteral":
470
+ if (typeof def.value === "string")
471
+ return { type: field_type_1.FieldType.string };
472
+ if (typeof def.value === "number")
473
+ return { type: field_type_1.FieldType.number };
474
+ if (typeof def.value === "boolean")
475
+ return { type: field_type_1.FieldType.boolean };
476
+ }
477
+ // Unions, catches, and opaque schemas may produce null or omit a field. The package
478
+ // parses the complete payload before sending; the host must accept every valid output.
479
+ return { type: field_type_1.FieldType.any, optional: true };
480
+ }
481
+ function eventManifest(event) {
482
+ if (!(0, events_1.isPackageEvent)(event)) {
483
+ return { ...event };
484
+ }
485
+ const { definition } = event;
486
+ const schema = "payloadSchema" in definition ? definition.payloadSchema : undefined;
487
+ return {
488
+ name: definition.name,
489
+ description: definition.description,
490
+ payloadFields: schema
491
+ ? describing(`Event ${definition.name} payload`, () => eventFields(schema))
492
+ : "payloadFields" in definition
493
+ ? (definition.payloadFields ?? [])
494
+ : [],
495
+ };
496
+ }
497
+ /** Event fields describe parsed output at every depth, including transforms and pipelines. */
498
+ function eventFields(schema, depth = 1, parent = "") {
499
+ return (0, types_1.schemaToFields)(schema).map((field) => {
500
+ const generated = (0, generated_field_1.generatedFieldMetadata)(schema.shape[field.name]);
501
+ if (generated)
502
+ return { name: field.name, ...generated.shape };
503
+ const { type: _type, optional: _optional, isArray: _array, defaultValue, ...metadata } = field;
504
+ const shape = eventValueShape(schema.shape[field.name]);
505
+ const path = parent ? `${parent}.${field.name}` : field.name;
506
+ return {
507
+ ...metadata,
508
+ ...(typeof defaultValue !== "function" && defaultValue !== undefined ? { defaultValue } : {}),
509
+ ...shape,
510
+ ...fieldDetails(schema.shape[field.name], shape, false, depth, path, true),
511
+ };
512
+ });
513
+ }
514
+ /**
515
+ * An observable value's shape, by field rules. The value itself may be empty (null on the wire,
516
+ * marked `optional`): it isn't a field of a record, so a nullable value isn't refused.
517
+ */
518
+ function observableValueShape(name, schema) {
519
+ const generated = (0, generated_field_1.generatedFieldMetadata)(schema);
520
+ if (generated)
521
+ return generated.shape;
522
+ let root = schema;
523
+ let empty = false;
524
+ while (root._def.typeName === "ZodNullable" || root._def.typeName === "ZodOptional") {
525
+ empty = true;
526
+ root = root._def.innerType;
527
+ }
528
+ const { name: _name, ...shape } = describing(`Observable ${name} value`, () => irField(root, (0, types_1.schemaToFields)(zod_1.z.object({ value: root }))[0], false, 1, "value", true));
529
+ return empty ? { ...shape, optional: true } : shape;
530
+ }
531
+ function tableManifest(table) {
532
+ const { name, description, primaryKeyName, schema, versionNumber, localOnly } = table.definition;
533
+ return {
534
+ name,
535
+ description,
536
+ primaryKeyName,
537
+ fields: describing(`Table ${name}`, () => irFields(schema)),
538
+ ...(versionNumber === undefined ? {} : { versionNumber }),
539
+ ...(localOnly ? { localOnly: true } : {}),
540
+ };
541
+ }
542
+ /** One contract's manifest entry, with name-only references to its tables. */
543
+ function providedContractManifest(contract) {
544
+ return {
545
+ contractId: contract.contractId,
546
+ version: contract.version,
547
+ name: contract.name,
548
+ ...(contract.description === undefined ? {} : { description: contract.description }),
549
+ ...(contract.tables.length
550
+ ? { tables: contract.tables.map((table) => ({ name: table.definition.name })) }
551
+ : {}),
552
+ tools: contract.tools.map(toolManifest),
553
+ ...(contract.observables.length
554
+ ? { observables: contract.observables.map(observableManifest) }
555
+ : {}),
556
+ ...(contract.events.length ? { events: contract.events.map(eventManifest) } : {}),
557
+ ...(contract.exposes?.length
558
+ ? {
559
+ exposes: contract.exposes.map(({ contractId, version }) => ({ contractId, version })),
560
+ }
561
+ : {}),
562
+ };
563
+ }
564
+ /**
565
+ * A contract's manifest entry and its tables' definitions, from a contract or a declaration:
566
+ * the IR a provider of it would register. Used to generate a declaration from another one; not
567
+ * a public SDK export.
568
+ *
569
+ * @internal
570
+ * @param contract - From `defineContract`, `implementContract`, or a declaration's members.
571
+ */
572
+ function contractManifest(contract) {
573
+ return {
574
+ contract: providedContractManifest(contract),
575
+ tables: contract.tables.map((table) => tableManifest(table)),
576
+ };
577
+ }
578
+ /**
579
+ * Turn a package definition into its manifest: plain JSON in Peers IR, which the Runtime reads
580
+ * without running any package code. `peers-package manifest` writes it to `dist/manifest.json`.
581
+ *
582
+ * @param definition - The default export of the package's `src/package.ts`.
583
+ * @param options - The package's frozen contracts, for `alsoImplements` by version number.
584
+ */
585
+ function buildPackageManifest(definition, options = {}) {
586
+ if (definition?.kind !== "package") {
587
+ throw new Error("buildPackageManifest takes the result of definePackage(...)");
588
+ }
589
+ const provides = definition.provides.map((contract) => providedContractManifest(contract));
590
+ const manifest = {
591
+ packageId: definition.packageId,
592
+ version: definition.version,
593
+ guestApi: isolated_artifact_1.ISOLATED_GUEST_API_VERSION,
594
+ ...(definition.executionMode === undefined ? {} : { executionMode: definition.executionMode }),
595
+ provides,
596
+ consumes: definition.consumes.map(({ ref, optional, providerPackageIds, fallbackToDefaultProvider }) => ({
597
+ contractId: ref.contractId,
598
+ version: ref.version,
599
+ ...(optional ? { optional: true } : {}),
600
+ ...(providerPackageIds === undefined
601
+ ? {}
602
+ : { providerPackageIds: [...providerPackageIds] }),
603
+ ...(fallbackToDefaultProvider === undefined ? {} : { fallbackToDefaultProvider }),
604
+ })),
605
+ ...(definition.tables.length ? { tables: definition.tables.map(tableManifest) } : {}),
606
+ ...(definition.network === undefined ? {} : { network: definition.network }),
607
+ ...(definition.appNavs.length ? { appNavs: [...definition.appNavs] } : {}),
608
+ };
609
+ const providedKeys = new Set(provides.map(({ contractId, version }) => `${contractId}@${version}`));
610
+ definition.provides.forEach((contract, index) => {
611
+ if (!contract.alsoImplements?.length)
612
+ return;
613
+ provides[index] = {
614
+ ...provides[index],
615
+ alsoImplements: contract.alsoImplements.map((target) => alsoImplementsEntry(contract, provides[index], manifest.tables ?? [], target, {
616
+ providedKeys,
617
+ frozen: options.frozen,
618
+ })),
619
+ };
620
+ });
621
+ // The manifest is data: drop anything JSON can't carry, so what's checked is what ships.
622
+ return JSON.parse(JSON.stringify(manifest));
623
+ }
624
+ /**
625
+ * One `alsoImplements` entry, carrying the target's definition so the host can register it on a
626
+ * cold start: an older version of the contract from `frozen-contracts.json`, or another
627
+ * contract from its declaration. The contract's shape must satisfy it.
628
+ */
629
+ function alsoImplementsEntry(contract, provided, tables, target, context) {
630
+ const label = `Contract ${contract.name} (${contract.contractId}@${contract.version})`;
631
+ let definition;
632
+ if (typeof target === "number") {
633
+ const frozen = context.frozen?.contracts.find((entry) => entry.contractId === contract.contractId && entry.version === target);
634
+ if (!frozen) {
635
+ throw new Error(`${label} also implements v${target}, which frozen-contracts.json doesn't record. Run peers-package contract freeze for the stable release that provided v${target}, or provide v${target} with pkg.provides.`);
636
+ }
637
+ const { frozenBy: _frozenBy, ...shape } = frozen;
638
+ definition = shape;
639
+ }
640
+ else {
641
+ const declared = contractManifest(target.declaration);
642
+ definition = (0, isolated_artifact_1.isolatedContractToDefinition)(declared.contract, declared.tables);
643
+ }
644
+ const key = `${definition.contractId}@${definition.version}`;
645
+ if (context.providedKeys.has(key)) {
646
+ throw new Error(`${label} also implements ${key}, which the package provides with pkg.provides`);
647
+ }
648
+ const satisfied = (0, validate_1.validateProviderSatisfiesContract)((0, isolated_artifact_1.isolatedContractToDefinition)(provided, tables), definition);
649
+ if (!satisfied.valid) {
650
+ throw new Error(`${label} doesn't satisfy ${definition.name} (${key}), which it also implements: ${satisfied.errors.map(({ message }) => message).join("; ")}`);
651
+ }
652
+ return { contractId: definition.contractId, version: definition.version, definition };
653
+ }