@peers-app/peers-sdk 0.28.6 → 0.28.8

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 (40) hide show
  1. package/README.md +2 -2
  2. package/dist/contracts/builder.d.ts +0 -6
  3. package/dist/contracts/builder.js +0 -11
  4. package/dist/contracts/contract-proxy.d.ts +7 -6
  5. package/dist/contracts/extract.d.ts +1 -1
  6. package/dist/contracts/system/contract-catalog-contract.d.ts +6 -0
  7. package/dist/contracts/system/contract-catalog-contract.js +1 -0
  8. package/dist/contracts/system/packages-contract.d.ts +19 -6
  9. package/dist/contracts/system/packages-contract.js +15 -2
  10. package/dist/data/package-version-resolver.js +1 -19
  11. package/dist/data/package-versions.d.ts +4 -0
  12. package/dist/data/package-versions.js +8 -0
  13. package/dist/data/packages.utils.d.ts +4 -0
  14. package/dist/data/packages.utils.js +5 -1
  15. package/dist/data/tools.d.ts +0 -10
  16. package/dist/define/context.d.ts +5 -7
  17. package/dist/define/observables.js +1 -5
  18. package/dist/package-build/cli.js +4 -3
  19. package/dist/package-build/contract-add.d.ts +4 -2
  20. package/dist/package-build/contract-add.js +3 -11
  21. package/dist/package-build/contract-source.d.ts +7 -4
  22. package/dist/package-build/contract-source.js +26 -13
  23. package/dist/package-build/index.d.ts +3 -0
  24. package/dist/package-build/index.js +11 -0
  25. package/dist/package-installer/package-cloner.js +1 -0
  26. package/dist/package-installer/package-creator.js +2 -0
  27. package/dist/package-installer/package-installer.d.ts +3 -1
  28. package/dist/package-installer/package-installer.js +37 -8
  29. package/dist/package-installer/package-sdk-version.d.ts +5 -0
  30. package/dist/package-installer/package-sdk-version.js +50 -0
  31. package/dist/package-installer/types.d.ts +11 -4
  32. package/dist/package-loader/isolated-artifact.d.ts +16 -29
  33. package/dist/package-loader/isolated-artifact.js +20 -33
  34. package/dist/package-loader/package-loader.d.ts +5 -5
  35. package/dist/package-loader/package-loader.js +14 -43
  36. package/dist/package-loader/package-runtime-state.d.ts +1 -2
  37. package/dist/sdk-version.d.ts +2 -0
  38. package/dist/sdk-version.js +5 -0
  39. package/dist/types/peers-package.d.ts +1 -7
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -32,7 +32,7 @@ NaCl-based key management, message signing and verification, box encryption, and
32
32
 
33
33
  ### Tools Framework
34
34
 
35
- Define AI-callable tools with `ITool` and `IToolInstance`. Tools are registered at runtime and available to built-in and user-defined AI assistants and workflows.
35
+ Declare AI-callable tools with `definePackage()` and provide their implementations through contract resolvers. Use `IContractToolImplementation` for provider implementations.
36
36
 
37
37
  ### Identity and Groups
38
38
 
@@ -58,7 +58,7 @@ The fastest way to build a Peers package is to start from the template:
58
58
  | `DataQuery` / `DataFilter` | Mongo-style query filters translated to SQL |
59
59
  | `observable()` / `computed()` | Reactive state primitives |
60
60
  | `deviceVar` / `userVar` / `groupVar` | Persistent variables — observables backed by the database |
61
- | `ITool` / `IToolInstance` | AI-callable tool definitions |
61
+ | `ITool` / `IContractToolImplementation` | AI-callable tool definitions |
62
62
  | `IPeersUI` / `IPeersUIRoute` | Screen and route declarations for package UIs |
63
63
  | `newid()` | Generate 25-character time-sortable peer IDs |
64
64
  | `newKeys()` / `hydrateKeys()` | Cryptographic key pair generation and hydration |
@@ -18,7 +18,6 @@ export declare class ContractBuilder {
18
18
  private _events;
19
19
  private _alsoImplements;
20
20
  private _exposes;
21
- private warnedAboutToolInstances;
22
21
  /** Full table definitions for this contract (used by the install flow). */
23
22
  private _tableDefinitions;
24
23
  constructor(contractId: string, version: number, name: string, description?: string | undefined);
@@ -35,11 +34,6 @@ export declare class ContractBuilder {
35
34
  */
36
35
  set events(events: IContractEvent[]);
37
36
  get events(): IContractEvent[];
38
- /**
39
- * Discard executable assignments from bundles compiled against an older SDK.
40
- * @deprecated Implement tools with registerContractResolver instead.
41
- */
42
- set toolInstances(_instances: never);
43
37
  /** Full table definitions for runtime table registration. */
44
38
  set tableDefinitions(defs: ITableDefinition[]);
45
39
  get tableDefinitions(): ITableDefinition[];
@@ -26,7 +26,6 @@ class ContractBuilder {
26
26
  _events = [];
27
27
  _alsoImplements = [];
28
28
  _exposes = [];
29
- warnedAboutToolInstances = false;
30
29
  /** Full table definitions for this contract (used by the install flow). */
31
30
  _tableDefinitions = [];
32
31
  constructor(contractId, version, name, description) {
@@ -64,16 +63,6 @@ class ContractBuilder {
64
63
  get events() {
65
64
  return this._events;
66
65
  }
67
- /**
68
- * Discard executable assignments from bundles compiled against an older SDK.
69
- * @deprecated Implement tools with registerContractResolver instead.
70
- */
71
- set toolInstances(_instances) {
72
- if (this.warnedAboutToolInstances)
73
- return;
74
- this.warnedAboutToolInstances = true;
75
- console.warn(`Contract ${this.name} (${this.contractId}@${this.version}) assigned toolInstances, which no longer run; implement these tools with registerContractResolver (docs/Packages/contracts.md#migrating-from-toolinstances)`);
76
- }
77
66
  /** Full table definitions for runtime table registration. */
78
67
  set tableDefinitions(defs) {
79
68
  this._tableDefinitions = defs;
@@ -1,6 +1,7 @@
1
+ import type { z } from "zod";
1
2
  import type { DataContext } from "../context/data-context";
2
3
  import type { Table } from "../data/orm/table";
3
- import type { ITool, IToolInstance } from "../data/tools";
4
+ import type { ITool } from "../data/tools";
4
5
  import type { Event, ISubscriptionResult } from "../events";
5
6
  import { type Observable } from "../observable";
6
7
  import { type IIsolatedCallPrincipal } from "../package-loader/isolated-runtime";
@@ -359,15 +360,15 @@ export declare function createRemoteContractConsumer(contract: IContractDefiniti
359
360
  dataContextId?: string;
360
361
  }): IRemoteContractConsumer<IContractConsumer>;
361
362
  /**
362
- * Provider-local executable metadata. Existing IToolInstance values remain assignable;
363
- * new implementations need no catalog ID, persisted row, or executable registration.
363
+ * Provider-local executable metadata for contract tools.
364
+ * Implementations need no catalog ID, persisted row, or executable registration.
364
365
  */
365
366
  export interface IContractToolImplementation {
366
367
  /** Provider metadata; the contract member name supplies identity, without a Tools row. */
367
368
  tool: Pick<ITool, "name" | "usageDescription"> & Partial<Pick<ITool, "toolId" | "detailedDescription" | "inputSchema" | "outputSchema" | "accessLevel" | "authorityPropagation" | "remoteAccessLevel">>;
368
- toolFn: IToolInstance["toolFn"];
369
- inputSchema?: IToolInstance["inputSchema"];
370
- outputSchema?: IToolInstance["outputSchema"];
369
+ toolFn: (...args: any[]) => any;
370
+ inputSchema?: z.ZodObject<any>;
371
+ outputSchema?: z.ZodObject<any>;
371
372
  }
372
373
  /** Context-bound live members of a provider; executable ownership stays with the contract. */
373
374
  export interface IContractResolution {
@@ -18,7 +18,7 @@ export interface IExtractableTableDef {
18
18
  }
19
19
  /**
20
20
  * Minimal tool-instance shape accepted by extractToolShape.
21
- * Mirrors the subset of IToolInstance we need.
21
+ * Mirrors the subset of IContractToolImplementation we need.
22
22
  */
23
23
  export interface IExtractableToolInstance {
24
24
  /** Host-only workflow policy carried into the contract definition. */
@@ -71,6 +71,12 @@ export interface IContractDefinitionWithPolicy {
71
71
  definition: IContractDefinition;
72
72
  /** Each tool's policy, where the Runtime knows it. */
73
73
  tools?: IContractToolPolicy[];
74
+ /** Active provider's table settings, when known; absent on older hosts. */
75
+ tables?: {
76
+ name: string;
77
+ versionNumber?: number;
78
+ localOnly?: boolean;
79
+ }[];
74
80
  }
75
81
  /**
76
82
  * Consumer shape for the System Contract Catalog.
@@ -47,6 +47,7 @@ function getContractCatalogDefinition() {
47
47
  outputFields: [
48
48
  field("definition", field_type_1.FieldType.object, "The registered definition, in Peers IR."),
49
49
  field("tools", field_type_1.FieldType.object, "Each tool's name, accessLevel, and authorityPropagation on the active provider.", { isArray: true, optional: true }),
50
+ field("tables", field_type_1.FieldType.object, "Each table's name, versionNumber, and localOnly setting on the active provider.", { isArray: true, optional: true }),
50
51
  ],
51
52
  },
52
53
  ],
@@ -42,6 +42,8 @@ export interface IPackageObservedRuntime {
42
42
  export interface IPackageRequestedVersion {
43
43
  /** Group-wide active version (`Packages.activePackageVersionId`). */
44
44
  groupActivePackageVersionId?: string;
45
+ /** The group selected a version whose row is not on this device yet. */
46
+ groupVersionMissing?: boolean;
45
47
  /** This device's override, when set. */
46
48
  deviceActivePackageVersionId?: string;
47
49
  /** The version this device should run: the device override, else the group version. */
@@ -101,8 +103,8 @@ export interface IPackageOperationResult {
101
103
  /** True when `createPackage` scaffolded a new package instead of importing an existing folder. */
102
104
  created?: boolean;
103
105
  /**
104
- * Non-fatal problem the caller should show: for example a missing package template, or a
105
- * reloaded dev build that reuses a released version number and should be bumped.
106
+ * Non-fatal problem the caller should show, for example a missing package template.
107
+ * Older hosts also report released-number conflicts here instead of refusing reload.
106
108
  */
107
109
  warning?: string;
108
110
  /** The package after the operation. */
@@ -120,7 +122,7 @@ export interface IPackageReloadOutcome {
120
122
  changed?: boolean;
121
123
  /** Failure message when `ok` is false. */
122
124
  error?: string;
123
- /** Non-fatal problem, such as a dev build that reuses a released version number. */
125
+ /** Non-fatal problem; older hosts may warn here about a reused released number. */
124
126
  warning?: string;
125
127
  }
126
128
  /** Result of `reloadAllPackages`. */
@@ -141,11 +143,19 @@ export interface IPromotePackageVersionResult extends IPackageOperationResult {
141
143
  /** True when this device holds the package signing key and signed the promoted version. */
142
144
  signed: boolean;
143
145
  /**
144
- * True when the version this device was running now matches the group's follow policy
145
- * and was activated, for the group unless this device overrides group settings.
146
+ * True when activation ran, either explicitly with activateForGroup or automatically
147
+ * under the group follow policy. See groupRunsPromotedVersion for the group outcome.
146
148
  */
147
149
  activated: boolean;
150
+ /** Group selection after promotion, when one is set. */
151
+ groupActivePackageVersionId?: string;
152
+ /** True even when the group already selected this id before it was re-tagged. */
153
+ groupRunsPromotedVersion?: boolean;
154
+ /** What the group now runs, why selection stayed unchanged, and any next command. */
155
+ activationNote?: string;
148
156
  }
157
+ /** Explain a group version missing locally and how to select an available release. */
158
+ export declare function packageGroupVersionWarning(packageId: string, requested: Pick<IPackageRequestedVersion, "groupActivePackageVersionId" | "groupVersionMissing">): string | undefined;
149
159
  /**
150
160
  * Release channels a version may be promoted to from its current tag: `dev` to `beta` or
151
161
  * `stable`, and `beta` to `stable`. Missing or empty tags on legacy versions mean `dev`,
@@ -207,7 +217,7 @@ export interface IPackagesConsumer extends IContractConsumerBase {
207
217
  packageVersionId?: string;
208
218
  scope?: "device" | "group";
209
219
  }): Promise<IPackageOperationResult>;
210
- /** Rebuild the dev version from disk when it changed, then force-reload the package. */
220
+ /** Read local bundles, then force-reload the selected version even when unchanged. */
211
221
  reloadPackage(input: {
212
222
  packageId: string;
213
223
  }): Promise<IPackageOperationResult>;
@@ -228,10 +238,13 @@ export interface IPackagesConsumer extends IContractConsumerBase {
228
238
  * Promote a stored version to `beta` or `stable`: re-tag, re-sign with the package author
229
239
  * key when this device has it, record the promotion in the version's history, and
230
240
  * activate it when this device was running it and the group follows the new tag.
241
+ * activateForGroup explicitly selects it for the group and follows that selection here.
231
242
  */
232
243
  promotePackageVersion(input: {
233
244
  packageVersionId: string;
234
245
  versionTag: string;
246
+ /** Also select the promoted version for the group and follow it here (Admin). */
247
+ activateForGroup?: boolean;
235
248
  }): Promise<IPromotePackageVersionResult>;
236
249
  /** Install the default packages into the bound data context. */
237
250
  installDefaultPackages(): Promise<{
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PACKAGES_CONTRACT_VERSION = void 0;
4
+ exports.packageGroupVersionWarning = packageGroupVersionWarning;
4
5
  exports.packageVersionPromotionTargets = packageVersionPromotionTargets;
5
6
  exports.isPackageRepositoryUrl = isPackageRepositoryUrl;
6
7
  exports.getPackagesContractDefinition = getPackagesContractDefinition;
@@ -9,6 +10,12 @@ const field_type_1 = require("../../types/field-type");
9
10
  const system_contracts_1 = require("../system-contracts");
10
11
  /** Version of the built-in System Packages contract. */
11
12
  exports.PACKAGES_CONTRACT_VERSION = 1;
13
+ /** Explain a group version missing locally and how to select an available release. */
14
+ function packageGroupVersionWarning(packageId, requested) {
15
+ if (!requested.groupVersionMissing || !requested.groupActivePackageVersionId)
16
+ return undefined;
17
+ return `The group's selected version ${requested.groupActivePackageVersionId} isn't on this device; it may not have synced yet. To select an available beta or stable version for the group, run: peers packages activate ${packageId} <packageVersionId> --group`;
18
+ }
12
19
  /**
13
20
  * Release channels a version may be promoted to from its current tag: `dev` to `beta` or
14
21
  * `stable`, and `beta` to `stable`. Missing or empty tags on legacy versions mean `dev`,
@@ -138,7 +145,7 @@ function getPackagesContractDefinition() {
138
145
  },
139
146
  {
140
147
  name: "reloadPackage",
141
- usageDescription: "Rebuild the dev version from the package folder when it changed, then force-reload the package on this device.",
148
+ usageDescription: "Read local bundles, then force-reload the selected version even when unchanged. Changed builds at a released number are refused.",
142
149
  inputFields: [packageIdField],
143
150
  outputFields: operationResultFields,
144
151
  },
@@ -170,10 +177,11 @@ function getPackagesContractDefinition() {
170
177
  },
171
178
  {
172
179
  name: "promotePackageVersion",
173
- usageDescription: "Promote a stored package version to beta or stable (dev to beta or stable, beta to stable). Re-signs it with the package author key when this device has it, records the promotion in its history, and activates it when this device was running it and the group follows the new tag.",
180
+ usageDescription: "Promote a stored package version to beta or stable (dev to beta or stable, beta to stable). Re-signs it with the package author key when this device has it, records the promotion in its history, and activates it when this device was running it and the group follows the new tag. Optionally selects it for the group with activateForGroup. Reports the group selection and any next command.",
174
181
  inputFields: [
175
182
  field("packageVersionId", field_type_1.FieldType.id, "Version to promote."),
176
183
  field("versionTag", field_type_1.FieldType.string, "New release channel: beta or stable."),
184
+ field("activateForGroup", field_type_1.FieldType.boolean, "Also select this version for the group and follow it here (Admin).", { optional: true }),
177
185
  ],
178
186
  outputFields: [
179
187
  ...operationResultFields,
@@ -181,6 +189,11 @@ function getPackagesContractDefinition() {
181
189
  field("version", field_type_1.FieldType.string, "Its version number."),
182
190
  field("versionTag", field_type_1.FieldType.string, "Its new release channel."),
183
191
  field("signed", field_type_1.FieldType.boolean, "True when this device signed the promoted version."),
192
+ field("groupActivePackageVersionId", field_type_1.FieldType.id, "Group selection after promotion.", {
193
+ optional: true,
194
+ }),
195
+ field("groupRunsPromotedVersion", field_type_1.FieldType.boolean, "Whether the group now selects the promoted version, including an already-selected id.", { optional: true }),
196
+ field("activationNote", field_type_1.FieldType.string, "What the group now runs and any next command.", { optional: true }),
184
197
  field("activated", field_type_1.FieldType.boolean, "True when the promotion also activated the version for the group or this device."),
185
198
  ],
186
199
  },
@@ -18,14 +18,12 @@ exports.hasDeviceFollowOverride = hasDeviceFollowOverride;
18
18
  exports.readStoredPackageLocalPath = readStoredPackageLocalPath;
19
19
  const user_context_singleton_1 = require("../context/user-context-singleton");
20
20
  const keys_1 = require("../keys");
21
- const assistants_1 = require("./assistants");
22
21
  const file_types_1 = require("./files/file.types");
23
22
  const group_member_roles_1 = require("./group-member-roles");
24
23
  const group_permissions_1 = require("./group-permissions");
25
24
  const package_versions_1 = require("./package-versions");
26
25
  const packages_1 = require("./packages");
27
26
  const persistent_vars_1 = require("./persistent-vars");
28
- const workflows_1 = require("./workflows");
29
27
  /**
30
28
  * Reactive, cached observable holding device-local preferences for a single
31
29
  * package. Backed by a `groupDeviceVar` so the UI can subscribe via
@@ -344,9 +342,7 @@ async function resolveDevicePackageVersion(pkg, dataContext, opts) {
344
342
  }
345
343
  return { loaded: false, upgraded: false };
346
344
  }
347
- // Load succeeded - install assistants/workflows, then persist the activation
348
- // either as a group-level active version or as device-local state.
349
- await installPackageContents(dataContext, pkg, instance);
345
+ // Load succeeded; persist activation as a group default or device-local state.
350
346
  // When the device is following group settings (no device-level follow
351
347
  // override) and the upgraded version is non-dev, also advance the group's
352
348
  // activePackageVersionId so other devices stay in sync. Only admin+ users
@@ -436,20 +432,6 @@ async function loadOlderCompatiblePackageVersion(pkg, dataContext, unavailableVe
436
432
  }
437
433
  return undefined;
438
434
  }
439
- /**
440
- * Save a loaded package's assistants and workflows into the data context.
441
- * Contract-installed packages are skipped because their content is already
442
- * managed by the contract loader.
443
- */
444
- async function installPackageContents(dataContext, pkg, instance) {
445
- if (dataContext.packageLoader.isContractInstalled(pkg.packageId))
446
- return;
447
- const saves = [
448
- ...(instance.assistants?.map((a) => (0, assistants_1.Assistants)(dataContext).save(a)) ?? []),
449
- ...(instance.workflows?.map((w) => (0, workflows_1.Workflows)(dataContext).save(w)) ?? []),
450
- ];
451
- await Promise.all(saves);
452
- }
453
435
  /**
454
436
  * Returns true when the device has an explicit follow-policy override
455
437
  * (`followRange`, `followTags`, or `pinned`). Used by the UI to drive the
@@ -115,6 +115,10 @@ declare const schema: z.ZodObject<{
115
115
  }>;
116
116
  /** A signed package build and its bundle hashes, tag, and lifecycle history. */
117
117
  export type IPackageVersion = z.infer<typeof schema>;
118
+ /** Find a beta or stable build with the same package and version number, excluding a supplied id. */
119
+ export declare function releasedVersionWithNumber(versions: readonly IPackageVersion[], candidate: Pick<IPackageVersion, "packageId" | "version"> & {
120
+ packageVersionId?: string;
121
+ }): IPackageVersion | undefined;
118
122
  /** Package version table with signature and group promotion permission checks. */
119
123
  export declare class PackageVersionsTable extends Table<IPackageVersion> {
120
124
  static isPassthrough: boolean;
@@ -35,6 +35,7 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
35
35
  };
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
37
  exports.PackageVersionsTable = void 0;
38
+ exports.releasedVersionWithNumber = releasedVersionWithNumber;
38
39
  exports.PackageVersions = PackageVersions;
39
40
  exports.computePackageVersionHash = computePackageVersionHash;
40
41
  exports.isVersionInRange = isVersionInRange;
@@ -100,6 +101,13 @@ const metaData = {
100
101
  fields: (0, types_1.schemaToFields)(schema),
101
102
  indexes: [{ fields: ["packageId"] }],
102
103
  };
104
+ /** Find a beta or stable build with the same package and version number, excluding a supplied id. */
105
+ function releasedVersionWithNumber(versions, candidate) {
106
+ return versions.find((other) => other.packageId === candidate.packageId &&
107
+ other.packageVersionId !== candidate.packageVersionId &&
108
+ other.version === candidate.version &&
109
+ (other.versionTag === "beta" || other.versionTag === "stable"));
110
+ }
103
111
  // Symbol.for keeps signer registration shared by separately installed SDK copies.
104
112
  const signingKey = Symbol.for("@peers-app/peers-sdk/package-version-signing");
105
113
  const signingGlobals = globalThis;
@@ -1,3 +1,7 @@
1
1
  import type { DataContext } from "../context/data-context";
2
2
  import { type IPackage } from "./packages";
3
+ /**
4
+ * Copy a package and its active version's bundles into another context.
5
+ * Restores an uninstalled package; uninstall keeps version and file rows.
6
+ */
3
7
  export declare function copyPackageToAnotherDataContext(packageId: string, fromDataContext: DataContext, toDataContext: DataContext): Promise<IPackage>;
@@ -15,6 +15,10 @@ async function copyBundleFile(fileId, fromFiles, toFiles, label) {
15
15
  console.warn(`${label} file ${fileId} not found in source context`);
16
16
  }
17
17
  }
18
+ /**
19
+ * Copy a package and its active version's bundles into another context.
20
+ * Restores an uninstalled package; uninstall keeps version and file rows.
21
+ */
18
22
  async function copyPackageToAnotherDataContext(packageId, fromDataContext, toDataContext) {
19
23
  const fromPackages = (0, packages_1.Packages)(fromDataContext);
20
24
  const toPackages = (0, packages_1.Packages)(toDataContext);
@@ -35,5 +39,5 @@ async function copyPackageToAnotherDataContext(packageId, fromDataContext, toDat
35
39
  await toPvTable.signAndSave(pv, { saveAsSnapshot: true });
36
40
  }
37
41
  }
38
- return toPackages.signAndSave(pkg);
42
+ return toPackages.signAndSave(pkg, { restoreIfDeleted: true, saveAsSnapshot: true });
39
43
  }
@@ -172,16 +172,6 @@ export declare const toolSchema: z.ZodObject<{
172
172
  }>;
173
173
  /** A tool definition stored in the Tools table. */
174
174
  export type ITool = z.infer<typeof toolSchema>;
175
- /**
176
- * Historical tool instance shape used by older packages.
177
- * @deprecated Use IContractToolImplementation with a contract resolver.
178
- */
179
- export interface IToolInstance {
180
- tool: ITool;
181
- toolFn: (...args: any[]) => any;
182
- inputSchema?: z.ZodObject<any>;
183
- outputSchema?: z.ZodObject<any>;
184
- }
185
175
  /**
186
176
  * Accessor for the Tools table.
187
177
  *
@@ -14,7 +14,7 @@ export interface IPackageProviderOptions {
14
14
  providerPackageId?: string;
15
15
  }
16
16
  /**
17
- * The host functions the Runtime gives one handler call (guest API 2 and 3).
17
+ * The host functions the Runtime gives one guest API 3 handler call.
18
18
  *
19
19
  * Every tool handler gets its own `ctx`, and host calls made through it act for that call's
20
20
  * caller, even while other calls run. The table and contract handles in this module take a
@@ -75,15 +75,13 @@ export interface IPackageContext {
75
75
  watchOwnedTable(tableName: string, handler: (event: IDataChangedEvent<unknown>) => unknown): Promise<PackageUnsubscribe>;
76
76
  /**
77
77
  * Set the value of an in-memory observable this package provides (one declared without a
78
- * `persistentVar`). The host checks it against the observable's `valueType`, keeps it for
79
- * readers, and pushes it to subscribers. Allowed at any time while the worker runs.
78
+ * `persistentVar`). The host checks it against the observable's `valueType` and pushes it
79
+ * to subscribers. Allowed at any time while the worker runs.
80
80
  */
81
81
  publishOwnedObservable(contractId: string, version: number, observableName: string, value: unknown): Promise<unknown>;
82
82
  /**
83
- * Take guest API 2 consumers' writes to a writable in-memory observable this package
84
- * provides. `handler` gets the value with no caller authority, stores and publishes it, and
85
- * returns what it stored. A later call replaces the handler. Guest API 3 instead routes
86
- * writes through the contract member handler under the caller's authority.
83
+ * Compatibility no-op retained for built guest API 3 bundles. Consumer writes are
84
+ * served through the contract member handler under the caller's authority.
87
85
  */
88
86
  onOwnedObservableSet(contractId: string, version: number, observableName: string, handler: (value: unknown) => unknown): void;
89
87
  }
@@ -162,8 +162,7 @@ async function serveObservable(observable, targets, ctx) {
162
162
  });
163
163
  serving.set(observable, () => subscription.dispose());
164
164
  if (definition.writable) {
165
- // Guest API 3 calls this from its member handler under the caller's authority; guest
166
- // API 2 uses onOwnedObservableSet without a caller frame. Both parse before writing.
165
+ // The member handler runs this under the caller's authority and parses before writing.
167
166
  const write = async (value) => {
168
167
  const next = schema ? schema.parse((0, empty_fields_1.withoutEmptySchemaFields)(schema, value)) : value;
169
168
  applying = true;
@@ -177,9 +176,6 @@ async function serveObservable(observable, targets, ctx) {
177
176
  return publish(source());
178
177
  };
179
178
  writers.set(observable, write);
180
- for (const target of targets) {
181
- ctx.onOwnedObservableSet(target.contractId, target.version, name, write);
182
- }
183
179
  }
184
180
  // A successful start guarantees readers have a published value. Later background failures
185
181
  // are logged, but a failed initial publish must fail startup rather than silently serve null.
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.main = main;
4
4
  const node_fs_1 = require("node:fs");
5
5
  const path = require("node:path");
6
+ const sdk_version_1 = require("../sdk-version");
6
7
  const build_package_1 = require("./build-package");
7
8
  const contract_add_1 = require("./contract-add");
8
9
  const freeze_contracts_1 = require("./freeze-contracts");
@@ -11,7 +12,7 @@ const USAGE = `Usage: peers-package <command> [options]
11
12
  Commands:
12
13
  manifest Write dist/manifest.json from the package's declarations (src/package.ts).
13
14
  build Write the manifest and dist/package.bundle.js, the artifact the Runtime
14
- installs.
15
+ installs. Prints the SDK version used to build it.
15
16
  contract add <source> Write src/contracts/<name>.v<version>.ts, a declaration of a contract this
16
17
  package implements or calls, generated from <source>: a declaration module
17
18
  and its export (path/to/file.ts#Name), a provider's manifest.json or
@@ -96,13 +97,13 @@ async function main(argv) {
96
97
  console.error(`Build failed: ${result.message}`);
97
98
  return;
98
99
  }
99
- console.log(`Built ${relative(result.artifactPath)} (${summarize(result)}; provider ${Math.round(result.providerChars / 1024)} KB)`);
100
+ console.log(`Built ${relative(result.artifactPath)} (${summarize(result)}; SDK ${sdk_version_1.SDK_VERSION}; provider ${Math.round(result.providerChars / 1024)} KB)`);
100
101
  });
101
102
  console.log("Watching for changes");
102
103
  return new Promise(() => undefined);
103
104
  }
104
105
  const result = await (0, build_package_1.buildPackage)(options);
105
- console.log(`Built ${relative(result.artifactPath)} (${summarize(result)}; provider ${Math.round(result.providerChars / 1024)} KB)`);
106
+ console.log(`Built ${relative(result.artifactPath)} (${summarize(result)}; SDK ${sdk_version_1.SDK_VERSION}; provider ${Math.round(result.providerChars / 1024)} KB)`);
106
107
  return 0;
107
108
  }
108
109
  /** `peers-package contract add <source> [options]` or `peers-package contract freeze [options]`. */
@@ -1,11 +1,13 @@
1
+ import { type IContractSource } from "./contract-source";
1
2
  /** What {@link addContract} takes. */
2
3
  export interface IContractAddOptions {
3
4
  /**
4
5
  * Where the contract comes from: a declaration module and its export
5
6
  * (`../voice-hub/src/contracts/voice-skill.ts#VoiceSkill`), a provider's `manifest.json` or
6
- * `package.bundle.js`, or the JSON `getContractDefinition` returned.
7
+ * `package.bundle.js`, the JSON `getContractDefinition` returned, or an in-memory
8
+ * contract source (for example from `contractSourceFromDefinition`).
7
9
  */
8
- source: string;
10
+ source: string | IContractSource;
9
11
  /** `contractId@version`, for a manifest that provides more than one contract. */
10
12
  contract?: string;
11
13
  /** Package root. Defaults to the current directory. */
@@ -19,9 +19,10 @@ const contract_source_1 = require("./contract-source");
19
19
  */
20
20
  async function addContract(options) {
21
21
  const root = path.resolve(options.root ?? process.cwd());
22
- const contract = await readContractSource(root, options);
22
+ const contract = typeof options.source === "string"
23
+ ? await readContractSource(root, { ...options, source: options.source })
24
+ : options.source;
23
25
  const generated = (0, contract_source_1.generateContractDeclaration)(contract, {
24
- from: sourceLabel(root, options.source),
25
26
  ...(options.name ? { exportName: options.name } : {}),
26
27
  });
27
28
  const target = path.join(path.resolve(root, options.outDir ?? "src/contracts"), generated.fileName);
@@ -41,15 +42,6 @@ function splitSource(source) {
41
42
  ? { file: source.slice(0, hash), exportName: source.slice(hash + 1) }
42
43
  : { file: source };
43
44
  }
44
- /**
45
- * The source as the file's header names it: relative to the package root, with `/`, so the
46
- * generated file (and `--check`) doesn't depend on where the command ran or on local paths.
47
- */
48
- function sourceLabel(root, source) {
49
- const { file, exportName } = splitSource(source);
50
- const relative = path.relative(root, path.resolve(file)).split(path.sep).join("/");
51
- return exportName === undefined ? relative : `${relative}#${exportName}`;
52
- }
53
45
  async function readContractSource(root, options) {
54
46
  const { file, exportName } = splitSource(options.source);
55
47
  if (exportName !== undefined) {
@@ -49,11 +49,14 @@ export declare function parseContractSelector(selector: string): {
49
49
  * propagation. Refinements (a regex, a minimum) and defaults made by code aren't in the IR, so
50
50
  * generated schemas check only what the IR describes. Objects keep keys their fields don't list.
51
51
  *
52
+ * Table storage settings are preserved: implementing the declaration uses its tables.
53
+ * Default settings (schema version 1 and synced storage) are omitted.
54
+ *
52
55
  * @param input - The contract and its tables.
53
- * @param options - Where it came from (for the file's header), and an export name to use
54
- * instead of the one made from the contract's name.
56
+ * @param options - An export name to use instead of the one made from the contract's name.
55
57
  */
56
- export declare function generateContractDeclaration(input: IContractSource, options: {
57
- from: string;
58
+ export declare function generateContractDeclaration(input: IContractSource, options?: {
59
+ /** Ignored for compatibility with older callers; generated headers never name a source. */
60
+ from?: string;
58
61
  exportName?: string;
59
62
  }): IGeneratedContractDeclaration;
@@ -67,6 +67,7 @@ function contractSourceFromDefinition(input) {
67
67
  throw new Error("Expected getContractDefinition's result: { definition, tools }");
68
68
  }
69
69
  const policy = new Map((input.tools ?? []).map((tool) => [tool.name, tool]));
70
+ const tablePolicy = new Map((input.tables ?? []).map((table) => [table.name, table]));
70
71
  const tools = definition.tools.map((tool) => {
71
72
  const toolPolicy = policy.get(tool.name);
72
73
  if (toolPolicy?.accessLevel === undefined) {
@@ -101,12 +102,17 @@ function contractSourceFromDefinition(input) {
101
102
  ...(definition.observables?.length ? { observables: definition.observables } : {}),
102
103
  ...(definition.events?.length ? { events: definition.events } : {}),
103
104
  },
104
- tables: (definition.tables ?? []).map((table) => ({
105
- name: table.name,
106
- description: table.description,
107
- primaryKeyName: table.primaryKeyName,
108
- fields: table.fields,
109
- })),
105
+ tables: (definition.tables ?? []).map((table) => {
106
+ const settings = tablePolicy.get(table.name);
107
+ return {
108
+ name: table.name,
109
+ description: table.description,
110
+ primaryKeyName: table.primaryKeyName,
111
+ fields: table.fields,
112
+ ...(settings?.versionNumber === undefined ? {} : { versionNumber: settings.versionNumber }),
113
+ ...(settings?.localOnly ? { localOnly: true } : {}),
114
+ };
115
+ }),
110
116
  };
111
117
  }
112
118
  /**
@@ -131,11 +137,13 @@ function parseContractSelector(selector) {
131
137
  * propagation. Refinements (a regex, a minimum) and defaults made by code aren't in the IR, so
132
138
  * generated schemas check only what the IR describes. Objects keep keys their fields don't list.
133
139
  *
140
+ * Table storage settings are preserved: implementing the declaration uses its tables.
141
+ * Default settings (schema version 1 and synced storage) are omitted.
142
+ *
134
143
  * @param input - The contract and its tables.
135
- * @param options - Where it came from (for the file's header), and an export name to use
136
- * instead of the one made from the contract's name.
144
+ * @param options - An export name to use instead of the one made from the contract's name.
137
145
  */
138
- function generateContractDeclaration(input, options) {
146
+ function generateContractDeclaration(input, options = {}) {
139
147
  const { contract, tables } = input;
140
148
  assertVersion(contract.version, "Contract version");
141
149
  const exportName = options.exportName ?? pascalCase(contract.name);
@@ -145,7 +153,7 @@ function generateContractDeclaration(input, options) {
145
153
  const imports = new Set(["declareContract"]);
146
154
  const code = new Code(imports);
147
155
  const lines = [];
148
- lines.push(`// Generated by \`peers-package contract add\` from ${docText(options.from)}.`, "// Regenerate it rather than edit it; a stable package release freezes the contract's shape.", "");
156
+ lines.push(`// Generated contract declaration. Regenerate with \`peers contracts add ${docText(contract.contractId)}@${contract.version}\``, "// or `peers-package contract add <source>`; do not edit by hand.", "");
149
157
  const eventNames = new Map();
150
158
  for (const event of contract.events ?? []) {
151
159
  const name = uniqueName(`${camelCase(exportName)}${pascalCase(event.name)}`, new Set([...RESERVED_NAMES, exportName, ...eventNames.values()]));
@@ -223,7 +231,7 @@ class Code {
223
231
  ["description", JSON.stringify(table.description ?? "")],
224
232
  ["primaryKeyName", JSON.stringify(table.primaryKeyName)],
225
233
  ["schema", this.objectSchema(table.fields, depth + 1)],
226
- ...(table.versionNumber === undefined
234
+ ...(table.versionNumber === undefined || table.versionNumber === 1
227
235
  ? []
228
236
  : [["versionNumber", String(table.versionNumber)]]),
229
237
  ...(table.localOnly ? [["localOnly", "true"]] : []),
@@ -237,7 +245,7 @@ class Code {
237
245
  ["name", JSON.stringify(tool.name)],
238
246
  ["usageDescription", JSON.stringify(tool.usageDescription)],
239
247
  ["accessLevel", this.accessLevel(level)],
240
- ...(tool.authorityPropagation
248
+ ...(tool.authorityPropagation && tool.authorityPropagation !== "cap"
241
249
  ? [["authorityPropagation", JSON.stringify(tool.authorityPropagation)]]
242
250
  : []),
243
251
  ...(tool.remoteAccessLevel === undefined
@@ -323,7 +331,10 @@ class Code {
323
331
  return `generatedField(${schema}, ${this.shapeSource(metadata)}${valueType === undefined ? "" : `, ${this.fieldType(valueType)}`})`;
324
332
  }
325
333
  shapeSource(shape) {
326
- const entries = Object.entries(shape).map(([key, value]) => {
334
+ const entries = Object.entries(shape)
335
+ .filter(([, value]) => value !== undefined)
336
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
337
+ .map(([key, value]) => {
327
338
  const source = key === "type"
328
339
  ? this.fieldType(value)
329
340
  : key === "fields"
@@ -422,6 +433,8 @@ function dataSource(value) {
422
433
  return `[${value.map(dataSource).join(", ")}]`;
423
434
  if (value && typeof value === "object") {
424
435
  return `{ ${Object.entries(value)
436
+ .filter(([, entry]) => entry !== undefined)
437
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
425
438
  .map(([key, entry]) => `${propertyKey(key)}: ${dataSource(entry)}`)
426
439
  .join(", ")} }`;
427
440
  }