@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
@@ -277,4 +277,133 @@ describe("types", () => {
277
277
  expect(() => schema.parse({ recordId: validId, label: "hello" })).not.toThrow();
278
278
  expect(() => schema.parse({ recordId: "", label: "hello" })).toThrow();
279
279
  });
280
+ describe("the richer IR", () => {
281
+ const fields = [
282
+ { name: "mode", type: field_type_1.FieldType.string, enumValues: ["on", "off"] },
283
+ { name: "note", type: field_type_1.FieldType.string, optional: true },
284
+ {
285
+ name: "where",
286
+ type: field_type_1.FieldType.object,
287
+ fields: [
288
+ { name: "city", type: field_type_1.FieldType.string },
289
+ { name: "unit", type: field_type_1.FieldType.string, enumValues: ["F", "C"], optional: true },
290
+ ],
291
+ },
292
+ { name: "tags", type: field_type_1.FieldType.object, isArray: true },
293
+ ];
294
+ const valid = {
295
+ mode: "on",
296
+ where: { city: "Austin", unit: "F", extra: 1 },
297
+ tags: [{ a: 1 }],
298
+ };
299
+ it("fieldsToSchema checks types and keeps an object's keys", () => {
300
+ const schema = (0, types_1.fieldsToSchema)(fields);
301
+ expect(schema.parse(valid)).toEqual(valid);
302
+ // An empty optional field is removed first (withoutEmptyFields); null itself isn't a value.
303
+ expect(schema.safeParse({ ...valid, note: null }).success).toBe(false);
304
+ // Enum values and nested fields describe; the Runtime doesn't enforce them.
305
+ expect(schema.safeParse({ ...valid, mode: "dim", where: { city: 3 } }).success).toBe(true);
306
+ expect(schema.safeParse({ ...valid, mode: 1 }).success).toBe(false);
307
+ expect(schema.safeParse({ ...valid, mode: null }).success).toBe(false);
308
+ expect(schema.safeParse({ ...valid, where: "Austin" }).success).toBe(false);
309
+ });
310
+ it("fieldsToSchema still defers an object to an intersected author schema", () => {
311
+ const codeSchema = zod_1.z.object({ when: zod_1.z.object({ at: zod_1.z.coerce.date() }) });
312
+ const schema = (0, types_1.fieldsToSchema)([{ name: "when", type: field_type_1.FieldType.object }], codeSchema);
313
+ expect(schema.parse({ when: { at: "2026-01-01" } }).when.at).toBeInstanceOf(Date);
314
+ });
315
+ it("fieldsToShapeSchema checks enum values and nested fields", () => {
316
+ const schema = (0, types_1.fieldsToShapeSchema)(fields);
317
+ expect(schema.parse(valid)).toEqual(valid);
318
+ expect(schema.safeParse({ ...valid, mode: "dim" }).success).toBe(false);
319
+ expect(schema.safeParse({ ...valid, where: { city: 3 } }).success).toBe(false);
320
+ expect(schema.safeParse({ ...valid, where: { city: "Austin", unit: "K" } }).success).toBe(false);
321
+ const mixed = (0, types_1.fieldsToShapeSchema)([
322
+ { name: "size", type: field_type_1.FieldType.any, enumValues: [1, "large", true] },
323
+ { name: "one", type: field_type_1.FieldType.number, enumValues: [3] },
324
+ ]);
325
+ expect(mixed.safeParse({ size: true, one: 3 }).success).toBe(true);
326
+ expect(mixed.safeParse({ size: false, one: 3 }).success).toBe(false);
327
+ expect(mixed.safeParse({ size: 1, one: 4 }).success).toBe(false);
328
+ });
329
+ it("comparableTableFields keeps only the keys a table version is compared on", () => {
330
+ expect((0, types_1.comparableTableFields)([
331
+ {
332
+ type: field_type_1.FieldType.object,
333
+ name: "where",
334
+ defaultValue: {},
335
+ fields: [],
336
+ enumValues: ["x"],
337
+ optional: true,
338
+ future: 1,
339
+ },
340
+ ])).toEqual([{ name: "where", type: field_type_1.FieldType.object, optional: true }]);
341
+ });
342
+ });
343
+ describe("withoutEmptyFields", () => {
344
+ const fields = [
345
+ { name: "title", type: field_type_1.FieldType.string },
346
+ { name: "note", type: field_type_1.FieldType.string, optional: true },
347
+ {
348
+ name: "where",
349
+ type: field_type_1.FieldType.object,
350
+ optional: true,
351
+ fields: [
352
+ { name: "city", type: field_type_1.FieldType.string },
353
+ { name: "unit", type: field_type_1.FieldType.string, optional: true },
354
+ ],
355
+ },
356
+ {
357
+ name: "stops",
358
+ type: field_type_1.FieldType.object,
359
+ isArray: true,
360
+ fields: [{ name: "at", type: field_type_1.FieldType.string, optional: true }],
361
+ },
362
+ { name: "data", type: field_type_1.FieldType.any, optional: true },
363
+ ];
364
+ it("removes null and undefined optional fields, in declared nested fields too", () => {
365
+ const value = {
366
+ title: "a",
367
+ note: null,
368
+ where: { city: "Austin", unit: null },
369
+ stops: [{ at: null }, { at: "noon" }],
370
+ data: { inner: null },
371
+ extra: null,
372
+ };
373
+ expect((0, types_1.withoutEmptyFields)(fields, value)).toEqual({
374
+ title: "a",
375
+ where: { city: "Austin" },
376
+ stops: [{}, { at: "noon" }],
377
+ // Values the fields don't describe, and keys they don't list, are left as they are.
378
+ data: { inner: null },
379
+ extra: null,
380
+ });
381
+ expect(value.note).toBeNull();
382
+ expect(value.where.unit).toBeNull();
383
+ expect((0, types_1.withoutEmptyFields)(fields, { ...value, note: undefined, where: null })).not.toHaveProperty("where");
384
+ });
385
+ it("keeps an empty required field, and returns the value itself when nothing is removed", () => {
386
+ const required = { title: null, stops: [] };
387
+ expect((0, types_1.withoutEmptyFields)(fields, required)).toBe(required);
388
+ const clean = { title: "a", where: { city: "Austin" }, stops: [{ at: "noon" }] };
389
+ expect((0, types_1.withoutEmptyFields)(fields, clean)).toBe(clean);
390
+ expect((0, types_1.withoutEmptyFields)(fields, null)).toBeNull();
391
+ expect((0, types_1.withoutEmptyFields)(fields, ["a"])).toEqual(["a"]);
392
+ });
393
+ it("deletes own undefined keys at every described depth", () => {
394
+ const value = { note: undefined, where: { unit: undefined }, stops: [{ at: undefined }] };
395
+ const cleaned = (0, types_1.withoutEmptyFields)(fields, value);
396
+ expect(cleaned).toStrictEqual({ where: {}, stops: [{}] });
397
+ expect(value).toHaveProperty("note");
398
+ expect(value.where).toHaveProperty("unit");
399
+ });
400
+ it("lets parsing fill a default for null just as for a missing field", () => {
401
+ const defaulted = [{ name: "rank", type: field_type_1.FieldType.number, defaultValue: 5 }];
402
+ for (const value of [{ rank: null }, { rank: undefined }, {}]) {
403
+ const cleaned = (0, types_1.withoutEmptyFields)(defaulted, value);
404
+ expect(cleaned).toStrictEqual({});
405
+ expect((0, types_1.fieldsToSchema)(defaulted).parse(cleaned)).toStrictEqual({ rank: 5 });
406
+ }
407
+ });
408
+ });
280
409
  });
@@ -107,16 +107,19 @@ declare const schema: z.ZodObject<{
107
107
  uiBundleFileHash?: string | undefined;
108
108
  packageAuthorSignature?: string | undefined;
109
109
  }>;
110
+ /** A signed package build and its bundle hashes, tag, and lifecycle history. */
110
111
  export type IPackageVersion = z.infer<typeof schema>;
112
+ /** Package version table with signature and group promotion permission checks. */
111
113
  export declare class PackageVersionsTable extends Table<IPackageVersion> {
112
114
  static isPassthrough: boolean;
113
115
  insert(packageVersion: IPackageVersion, opts?: ISaveOptions): Promise<IPackageVersion>;
114
116
  update(packageVersion: IPackageVersion, opts?: ISaveOptions): Promise<IPackageVersion>;
115
117
  save(packageVersion: IPackageVersion, opts?: ISaveOptions): Promise<IPackageVersion>;
116
118
  signAndSave(packageVersion: IPackageVersion, opts?: ISaveOptions): Promise<IPackageVersion>;
117
- private static addSignatureToPackageVersion;
119
+ /** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
118
120
  static enablePackageVersionSigning(fn: (packageVersion: IPackageVersion) => IPackageVersion): void;
119
121
  }
122
+ /** Returns the PackageVersions table for the given context or the current default context. */
120
123
  export declare function PackageVersions(dataContext?: DataContext): PackageVersionsTable;
121
124
  /**
122
125
  * Compute a content hash from bundle file hashes. The hash represents code
@@ -134,6 +137,7 @@ export declare function PackageVersions(dataContext?: DataContext): PackageVersi
134
137
  export declare function computePackageVersionHash(version: string,
135
138
  /** @deprecated Ignored — will be removed in a future release. */
136
139
  _versionTag: string, packageBundleFileHash: string, routesBundleFileHash?: string, uiBundleFileHash?: string): string;
140
+ /** Checks whether an incoming version falls within the selected automatic update range. */
137
141
  export declare function isVersionInRange(activeVersion: string, incomingVersion: string, range: "pinned" | "patch" | "minor" | "latest"): boolean;
138
142
  /**
139
143
  * Returns true if incomingVersion is strictly newer than activeVersion (semver comparison).
@@ -92,6 +92,10 @@ const metaData = {
92
92
  fields: (0, types_1.schemaToFields)(schema),
93
93
  indexes: [{ fields: ["packageId"] }],
94
94
  };
95
+ // Symbol.for keeps signer registration shared by separately installed SDK copies.
96
+ const signingKey = Symbol.for("@peers-app/peers-sdk/package-version-signing");
97
+ const signingGlobals = globalThis;
98
+ /** Package version table with signature and group promotion permission checks. */
95
99
  let PackageVersionsTable = (() => {
96
100
  let _classSuper = table_1.Table;
97
101
  let _instanceExtraInitializers = [];
@@ -122,15 +126,16 @@ let PackageVersionsTable = (() => {
122
126
  return super.save(packageVersion, opts);
123
127
  }
124
128
  async signAndSave(packageVersion, opts) {
125
- if (!PackageVersionsTable.addSignatureToPackageVersion) {
129
+ const sign = signingGlobals[signingKey];
130
+ if (!sign) {
126
131
  throw new Error("Package version signing is not enabled. Call PackageVersionsTable.enablePackageVersionSigning(fn) to enable it.");
127
132
  }
128
- packageVersion = await PackageVersionsTable.addSignatureToPackageVersion(packageVersion);
133
+ packageVersion = await sign.call(PackageVersionsTable, packageVersion);
129
134
  return this.save(packageVersion, opts);
130
135
  }
131
- static addSignatureToPackageVersion = undefined;
136
+ /** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
132
137
  static enablePackageVersionSigning(fn) {
133
- PackageVersionsTable.addSignatureToPackageVersion = fn;
138
+ signingGlobals[signingKey] = fn;
134
139
  }
135
140
  constructor() {
136
141
  super(...arguments);
@@ -140,6 +145,7 @@ let PackageVersionsTable = (() => {
140
145
  })();
141
146
  exports.PackageVersionsTable = PackageVersionsTable;
142
147
  (0, table_definitions_system_1.registerSystemTableDefinition)(metaData, schema, PackageVersionsTable);
148
+ /** Returns the PackageVersions table for the given context or the current default context. */
143
149
  function PackageVersions(dataContext) {
144
150
  return (0, context_1.getTableContainer)(dataContext).getTable(metaData, schema, PackageVersionsTable);
145
151
  }
@@ -161,6 +167,7 @@ function computePackageVersionHash(version,
161
167
  _versionTag, packageBundleFileHash, routesBundleFileHash, uiBundleFileHash) {
162
168
  return (0, keys_1.hashValue)([version, packageBundleFileHash, routesBundleFileHash ?? "", uiBundleFileHash ?? ""].join(":"));
163
169
  }
170
+ /** Checks whether an incoming version falls within the selected automatic update range. */
164
171
  function isVersionInRange(activeVersion, incomingVersion, range) {
165
172
  if (range === "pinned")
166
173
  return false;
@@ -42,19 +42,25 @@ declare const schema: z.ZodObject<{
42
42
  followVersionTags?: string | undefined;
43
43
  updateUrl?: string | undefined;
44
44
  }>;
45
+ /** A signed package record with publisher identity and version selection policy. */
45
46
  export type IPackage = z.infer<typeof schema>;
47
+ /** Package table with signature enforcement outside personal contexts. */
46
48
  export declare class PackagesTable extends Table<IPackage> {
47
49
  static isPassthrough: boolean;
48
50
  insert(packageObj: IPackage, opts?: ISaveOptions): Promise<IPackage>;
49
51
  update(packageObj: IPackage, opts?: ISaveOptions): Promise<IPackage>;
50
52
  save(packageObj: IPackage, opts?: ISaveOptions): Promise<IPackage>;
51
53
  signAndSave(packageObj: IPackage, opts?: ISaveOptions): Promise<IPackage>;
52
- private static addSignatureToPackage;
54
+ /** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
53
55
  static enablePackageSigning(fn: (packageObj: IPackage) => IPackage): void;
54
56
  delete(packageId: string): Promise<void>;
55
57
  }
58
+ /** Returns the Packages table for the given context or the current default context. */
56
59
  export declare function Packages(dataContext?: DataContext): PackagesTable;
60
+ /** Default directory for locally stored package files. */
57
61
  export declare const packagesRootDir = "~/peers/packages";
62
+ /** Device preference controlling package reloads when the UI refreshes. */
58
63
  export declare const reloadPackagesOnPageRefresh: import("./persistent-vars").PersistentVar<boolean>;
64
+ /** Device preference enabling automatic peers-core updates. */
59
65
  export declare const autoUpdatePeersCore: import("./persistent-vars").PersistentVar<boolean>;
60
66
  export {};
@@ -83,6 +83,10 @@ const metaData = {
83
83
  iconClassName: "bi bi-box-fill",
84
84
  fields: (0, types_1.schemaToFields)(schema),
85
85
  };
86
+ // Symbol.for keeps signer registration shared by separately installed SDK copies.
87
+ const signingKey = Symbol.for("@peers-app/peers-sdk/package-signing");
88
+ const signingGlobals = globalThis;
89
+ /** Package table with signature enforcement outside personal contexts. */
86
90
  let PackagesTable = (() => {
87
91
  let _classSuper = table_1.Table;
88
92
  let _instanceExtraInitializers = [];
@@ -113,15 +117,16 @@ let PackagesTable = (() => {
113
117
  return super.save(packageObj, opts);
114
118
  }
115
119
  async signAndSave(packageObj, opts) {
116
- if (!PackagesTable.addSignatureToPackage) {
120
+ const sign = signingGlobals[signingKey];
121
+ if (!sign) {
117
122
  throw new Error("Package signing is not enabled. Call PackagesTable.enablePackageSigning(fn) to enable it.");
118
123
  }
119
- packageObj = await PackagesTable.addSignatureToPackage(packageObj);
124
+ packageObj = await sign.call(PackagesTable, packageObj);
120
125
  return this.save(packageObj, opts);
121
126
  }
122
- static addSignatureToPackage = undefined;
127
+ /** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
123
128
  static enablePackageSigning(fn) {
124
- PackagesTable.addSignatureToPackage = fn;
129
+ signingGlobals[signingKey] = fn;
125
130
  }
126
131
  async delete(packageId) {
127
132
  // anyone (with write permissions) can delete packages since that isn't a security risk (unless something else is relying on a package to be present?)
@@ -141,11 +146,15 @@ let PackagesTable = (() => {
141
146
  })();
142
147
  exports.PackagesTable = PackagesTable;
143
148
  (0, table_definitions_system_1.registerSystemTableDefinition)(metaData, schema, PackagesTable);
149
+ /** Returns the Packages table for the given context or the current default context. */
144
150
  function Packages(dataContext) {
145
151
  return (0, context_1.getTableContainer)(dataContext).getTable(metaData, schema, PackagesTable);
146
152
  }
153
+ /** Default directory for locally stored package files. */
147
154
  exports.packagesRootDir = "~/peers/packages";
155
+ /** Device preference controlling package reloads when the UI refreshes. */
148
156
  exports.reloadPackagesOnPageRefresh = (0, persistent_vars_1.deviceVar)("reloadPackagesOnPageRefresh", {
149
157
  defaultValue: true,
150
158
  });
159
+ /** Device preference enabling automatic peers-core updates. */
151
160
  exports.autoUpdatePeersCore = (0, persistent_vars_1.deviceVar)("autoUpdatePeersCore", { defaultValue: true });
@@ -14,60 +14,22 @@ declare const valueTypeSchema: z.ZodObject<{
14
14
  optional: z.ZodOptional<z.ZodBoolean>;
15
15
  isArray: z.ZodOptional<z.ZodBoolean>;
16
16
  subType: z.ZodOptional<z.ZodString>;
17
- }, "strip", z.ZodTypeAny, {
18
- name: string;
19
- type: import("..").FieldType;
20
- optional?: boolean | undefined;
21
- description?: string | undefined;
22
- defaultValue?: any;
23
- isArray?: boolean | undefined;
24
- subType?: string | undefined;
25
- }, {
26
- name: string;
27
- type: import("..").FieldType;
28
- optional?: boolean | undefined;
29
- description?: string | undefined;
30
- defaultValue?: any;
31
- isArray?: boolean | undefined;
32
- subType?: string | undefined;
33
- }>, "many">>;
17
+ fields: z.ZodOptional<z.ZodLazy<z.ZodArray<z.ZodType<import("..").IField>>>>;
18
+ enumValues: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
19
+ }, "strip", z.ZodTypeAny, import("..").IField, import("..").IField>, "many">>;
34
20
  }, "strip", z.ZodTypeAny, {
35
21
  type: import("./tools").IOSchemaType;
36
- fields: {
37
- name: string;
38
- type: import("..").FieldType;
39
- optional?: boolean | undefined;
40
- description?: string | undefined;
41
- defaultValue?: any;
42
- isArray?: boolean | undefined;
43
- subType?: string | undefined;
44
- }[];
22
+ fields: import("..").IField[];
45
23
  }, {
46
24
  type: import("./tools").IOSchemaType;
47
- fields?: {
48
- name: string;
49
- type: import("..").FieldType;
50
- optional?: boolean | undefined;
51
- description?: string | undefined;
52
- defaultValue?: any;
53
- isArray?: boolean | undefined;
54
- subType?: string | undefined;
55
- }[] | undefined;
25
+ fields?: import("..").IField[] | undefined;
56
26
  }>;
57
27
  iconClass: z.ZodOptional<z.ZodString>;
58
28
  }, "strip", z.ZodTypeAny, {
59
29
  name: string;
60
30
  schema: {
61
31
  type: import("./tools").IOSchemaType;
62
- fields: {
63
- name: string;
64
- type: import("..").FieldType;
65
- optional?: boolean | undefined;
66
- description?: string | undefined;
67
- defaultValue?: any;
68
- isArray?: boolean | undefined;
69
- subType?: string | undefined;
70
- }[];
32
+ fields: import("..").IField[];
71
33
  };
72
34
  peerTypeId: string;
73
35
  description?: string | undefined;
@@ -76,15 +38,7 @@ declare const valueTypeSchema: z.ZodObject<{
76
38
  name: string;
77
39
  schema: {
78
40
  type: import("./tools").IOSchemaType;
79
- fields?: {
80
- name: string;
81
- type: import("..").FieldType;
82
- optional?: boolean | undefined;
83
- description?: string | undefined;
84
- defaultValue?: any;
85
- isArray?: boolean | undefined;
86
- subType?: string | undefined;
87
- }[] | undefined;
41
+ fields?: import("..").IField[] | undefined;
88
42
  };
89
43
  peerTypeId: string;
90
44
  description?: string | undefined;
@@ -95,15 +49,7 @@ export declare function PeerTypes(dataContext?: DataContext): import("..").Table
95
49
  name: string;
96
50
  schema: {
97
51
  type: import("./tools").IOSchemaType;
98
- fields: {
99
- name: string;
100
- type: import("..").FieldType;
101
- optional?: boolean | undefined;
102
- description?: string | undefined;
103
- defaultValue?: any;
104
- isArray?: boolean | undefined;
105
- subType?: string | undefined;
106
- }[];
52
+ fields: import("..").IField[];
107
53
  };
108
54
  peerTypeId: string;
109
55
  description?: string | undefined;
@@ -61,7 +61,10 @@ export declare class PersistentVarsTable extends Table<IPersistentVar> {
61
61
  insert(persistentVar: IPersistentVar): Promise<IPersistentVar>;
62
62
  /** Update a persistent variable through the canonical save path. */
63
63
  update(persistentVar: IPersistentVar): Promise<IPersistentVar>;
64
- /** Save a variable, assigning its deterministic ID and encrypting new secret plaintext. */
64
+ /**
65
+ * Save a variable, assigning its deterministic ID and encrypting new secret plaintext. Saves as
66
+ * a snapshot and restores a deleted row unless `opts` says otherwise.
67
+ */
65
68
  save(persistentVar: IPersistentVar, opts?: ISaveOptions): Promise<IPersistentVar>;
66
69
  /**
67
70
  * Replace a secret variable's stored ciphertext as-is, without passing it through
@@ -128,7 +128,10 @@ let PersistentVarsTable = (() => {
128
128
  async update(persistentVar) {
129
129
  return this.save(persistentVar);
130
130
  }
131
- /** Save a variable, assigning its deterministic ID and encrypting new secret plaintext. */
131
+ /**
132
+ * Save a variable, assigning its deterministic ID and encrypting new secret plaintext. Saves as
133
+ * a snapshot and restores a deleted row unless `opts` says otherwise.
134
+ */
132
135
  async save(persistentVar, opts) {
133
136
  if (!persistentVar.persistentVarId) {
134
137
  persistentVar.persistentVarId = (0, utils_1.deterministicPvarId)(persistentVar.name);
@@ -145,7 +148,9 @@ let PersistentVarsTable = (() => {
145
148
  }
146
149
  persistentVar.modifiedAt = Date.now();
147
150
  opts ??= {};
148
- opts = { saveAsSnapshot: true, ...opts };
151
+ // The ID comes from the name, so setting a deleted pvar again, or re-creating it under a new
152
+ // scope, reuses a deleted ID; change tracking refuses that save unless it restores the row.
153
+ opts = { saveAsSnapshot: true, restoreIfDeleted: true, ...opts };
149
154
  return super.save(persistentVar, opts);
150
155
  }
151
156
  /**
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tableNames = ["package-version", "package", "group", "group-member", "user"];
4
+ // These key strings are the contract between installed SDK versions; do not rename them.
5
+ const signingKey = (name) => Symbol.for(`@peers-app/peers-sdk/${name}-signing`);
6
+ // Restore process-global registrations so these tests never leave test signers installed.
7
+ const priorSigners = tableNames.map((name) => {
8
+ const key = signingKey(name);
9
+ return { key, descriptor: Object.getOwnPropertyDescriptor(globalThis, key) };
10
+ });
11
+ beforeEach(() => {
12
+ for (const { key } of priorSigners) {
13
+ Reflect.deleteProperty(globalThis, key);
14
+ }
15
+ });
16
+ afterEach(() => {
17
+ for (const { key, descriptor } of priorSigners) {
18
+ if (descriptor) {
19
+ Object.defineProperty(globalThis, key, descriptor);
20
+ }
21
+ else {
22
+ Reflect.deleteProperty(globalThis, key);
23
+ }
24
+ }
25
+ });
26
+ function loadSigningTables() {
27
+ let tables;
28
+ jest.isolateModules(() => {
29
+ const { PackageVersionsTable } = require("./package-versions");
30
+ const { PackagesTable } = require("./packages");
31
+ const { GroupsTable } = require("./groups");
32
+ const { GroupMembersTable } = require("./group-members");
33
+ const { UsersTable } = require("./users");
34
+ tables = {
35
+ "package-version": {
36
+ prototype: PackageVersionsTable.prototype,
37
+ enable: PackageVersionsTable.enablePackageVersionSigning,
38
+ },
39
+ package: { prototype: PackagesTable.prototype, enable: PackagesTable.enablePackageSigning },
40
+ group: { prototype: GroupsTable.prototype, enable: GroupsTable.enableGroupSigning },
41
+ "group-member": {
42
+ prototype: GroupMembersTable.prototype,
43
+ enable: GroupMembersTable.enableGroupMemberSigning,
44
+ },
45
+ user: { prototype: UsersTable.prototype, enable: UsersTable.enableUserSigning },
46
+ };
47
+ });
48
+ return tables;
49
+ }
50
+ describe("table signing across SDK module instances", () => {
51
+ it.each(tableNames)("shares and replaces the %s signer across copies", async (name) => {
52
+ const first = loadSigningTables()[name];
53
+ const second = loadSigningTables()[name];
54
+ expect(first.prototype).not.toBe(second.prototype);
55
+ // Stub persistence only: exercise the real signAndSave and registration methods.
56
+ const receiver = { save: jest.fn(async (record) => record) };
57
+ const row = { name: "Example", signature: "" };
58
+ const opts = { saveAsSnapshot: false };
59
+ await expect(second.prototype.signAndSave.call(receiver, row, opts)).rejects.toThrow(/signing.*enabled/i);
60
+ expect(receiver.save).not.toHaveBeenCalled();
61
+ const sign = jest.fn((record) => ({ ...record, signature: "first" }));
62
+ first.enable(sign);
63
+ expect(Reflect.get(globalThis, signingKey(name))).toBe(sign);
64
+ await expect(second.prototype.signAndSave.call(receiver, row, opts)).resolves.toEqual({
65
+ ...row,
66
+ signature: "first",
67
+ });
68
+ expect(sign).toHaveBeenCalledWith(row);
69
+ expect(sign.mock.contexts.at(-1)).toBe(second.prototype.constructor);
70
+ expect(receiver.save).toHaveBeenLastCalledWith({ ...row, signature: "first" }, name === "group-member" ? { ...opts, restoreIfDeleted: true } : opts);
71
+ // Loading another copy must preserve a signer that is already registered.
72
+ const third = loadSigningTables()[name];
73
+ expect(third.prototype).not.toBe(first.prototype);
74
+ await expect(third.prototype.signAndSave.call(receiver, row)).resolves.toEqual({
75
+ ...row,
76
+ signature: "first",
77
+ });
78
+ expect(sign.mock.contexts.at(-1)).toBe(third.prototype.constructor);
79
+ second.enable((record) => ({ ...record, signature: "replacement" }));
80
+ for (const copy of [first, third]) {
81
+ await expect(copy.prototype.signAndSave.call(receiver, row)).resolves.toEqual({
82
+ ...row,
83
+ signature: "replacement",
84
+ });
85
+ }
86
+ receiver.save.mockClear();
87
+ const failsOnce = jest
88
+ .fn((record) => ({ ...record, signature: "recovered" }))
89
+ .mockImplementationOnce(() => {
90
+ throw new Error("signing failed");
91
+ });
92
+ first.enable(failsOnce);
93
+ await expect(second.prototype.signAndSave.call(receiver, row)).rejects.toThrow("signing failed");
94
+ expect(receiver.save).not.toHaveBeenCalled();
95
+ await expect(third.prototype.signAndSave.call(receiver, row)).resolves.toEqual({
96
+ ...row,
97
+ signature: "recovered",
98
+ });
99
+ expect(failsOnce).toHaveBeenCalledTimes(2);
100
+ receiver.save.mockRejectedValueOnce(new Error("save failed"));
101
+ await expect(second.prototype.signAndSave.call(receiver, row)).rejects.toThrow("save failed");
102
+ await expect(first.prototype.signAndSave.call(receiver, row)).resolves.toEqual({
103
+ ...row,
104
+ signature: "recovered",
105
+ });
106
+ });
107
+ it("keeps the five signer registrations independent", async () => {
108
+ const first = loadSigningTables();
109
+ const second = loadSigningTables();
110
+ for (const name of tableNames) {
111
+ first[name].enable((record) => ({ ...record, signature: name }));
112
+ }
113
+ const receiver = { save: jest.fn(async (record) => record) };
114
+ for (const name of tableNames) {
115
+ await expect(second[name].prototype.signAndSave.call(receiver, { name: "Example", signature: "" })).resolves.toEqual({ name: "Example", signature: name });
116
+ }
117
+ });
118
+ });