@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
@@ -0,0 +1,3 @@
1
+ /** Node-only helpers for generating package contract declarations. */
2
+ export { addContract, type IContractAddOptions, type IContractAddResult } from "./contract-add";
3
+ export { contractSourceFromDefinition, contractSourceFromManifest, generateContractDeclaration, type IContractSource, type IGeneratedContractDeclaration, parseContractSelector, } from "./contract-source";
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseContractSelector = exports.generateContractDeclaration = exports.contractSourceFromManifest = exports.contractSourceFromDefinition = exports.addContract = void 0;
4
+ /** Node-only helpers for generating package contract declarations. */
5
+ var contract_add_1 = require("./contract-add");
6
+ Object.defineProperty(exports, "addContract", { enumerable: true, get: function () { return contract_add_1.addContract; } });
7
+ var contract_source_1 = require("./contract-source");
8
+ Object.defineProperty(exports, "contractSourceFromDefinition", { enumerable: true, get: function () { return contract_source_1.contractSourceFromDefinition; } });
9
+ Object.defineProperty(exports, "contractSourceFromManifest", { enumerable: true, get: function () { return contract_source_1.contractSourceFromManifest; } });
10
+ Object.defineProperty(exports, "generateContractDeclaration", { enumerable: true, get: function () { return contract_source_1.generateContractDeclaration; } });
11
+ Object.defineProperty(exports, "parseContractSelector", { enumerable: true, get: function () { return contract_source_1.parseContractSelector; } });
@@ -126,5 +126,6 @@ async function clonePackage(dataContext, deps, opts) {
126
126
  localPath: location,
127
127
  remoteRepo: opts.remoteRepo,
128
128
  packageVersion: result.packageVersion,
129
+ warning: result.warning,
129
130
  };
130
131
  }
@@ -313,6 +313,7 @@ async function importExistingPackage(dataContext, deps, opts) {
313
313
  packageId,
314
314
  localPath: opts.location,
315
315
  packageVersion: result.packageVersion,
316
+ warning: result.warning,
316
317
  };
317
318
  }
318
319
  /**
@@ -413,6 +414,7 @@ async function createPackage(dataContext, deps, opts) {
413
414
  packageId,
414
415
  localPath: location,
415
416
  packageVersion: result.packageVersion,
417
+ warning: result.warning,
416
418
  templateWarning,
417
419
  };
418
420
  }
@@ -9,7 +9,9 @@ import type { IInstallOpts, IInstallResult, IPackageInfo, IPackageInstallerDeps
9
9
  * only when content has changed, upserts the PackageVersion, and optionally
10
10
  * triggers the package loader and device version resolution.
11
11
  *
12
- * When the bundle hash matches the existing PackageVersion, no files or PV are
12
+ * A matching released version is returned without any writes or loading, even when
13
+ * the group has no selected version. Changed bundles at a released number are refused.
14
+ * When the bundle hash matches the existing dev PackageVersion, no files or PV are
13
15
  * rewritten. An Admin's Packages row still gets a missing `activePackageVersionId`
14
16
  * (and description), and when the id was missing the version is activated on
15
17
  * this device unless `skipResolve`.
@@ -14,8 +14,10 @@ const package_version_resolver_1 = require("../data/package-version-resolver");
14
14
  const package_versions_1 = require("../data/package-versions");
15
15
  const packages_1 = require("../data/packages");
16
16
  const persistent_vars_1 = require("../data/persistent-vars");
17
+ const sdk_version_1 = require("../sdk-version");
17
18
  const utils_1 = require("../utils");
18
19
  const package_author_signer_1 = require("./package-author-signer");
20
+ const package_sdk_version_1 = require("./package-sdk-version");
19
21
  /**
20
22
  * Install or update a dev PackageVersion from locally-built bundle files.
21
23
  *
@@ -24,7 +26,9 @@ const package_author_signer_1 = require("./package-author-signer");
24
26
  * only when content has changed, upserts the PackageVersion, and optionally
25
27
  * triggers the package loader and device version resolution.
26
28
  *
27
- * When the bundle hash matches the existing PackageVersion, no files or PV are
29
+ * A matching released version is returned without any writes or loading, even when
30
+ * the group has no selected version. Changed bundles at a released number are refused.
31
+ * When the bundle hash matches the existing dev PackageVersion, no files or PV are
28
32
  * rewritten. An Admin's Packages row still gets a missing `activePackageVersionId`
29
33
  * (and description), and when the id was missing the version is activated on
30
34
  * this device unless `skipResolve`.
@@ -39,6 +43,7 @@ async function installPackageFromBundles(dataContext, deps, packageId, opts) {
39
43
  // 2. Read package info
40
44
  const info = await getPackageInfo(deps, localPath);
41
45
  const version = info.version ?? "0.0.1";
46
+ const warning = await installedSdkWarning(deps, localPath);
42
47
  // 3. Read bundle files
43
48
  const distDir = deps.resolvePath(localPath, "dist");
44
49
  const packageBundlePath = deps.resolvePath(distDir, "package.bundle.js");
@@ -60,12 +65,26 @@ async function installPackageFromBundles(dataContext, deps, packageId, opts) {
60
65
  const uiHash = uiBundle ? (0, file_types_1.computeFileHash)(uiBundle) : undefined;
61
66
  // 5. Compute PV hash
62
67
  const pvHash = (0, package_versions_1.computePackageVersionHash)(version, "", packageHash, routesHash, uiHash);
63
- // 6. Early exit if unchanged. Still repair missing appNavs (see syncDevBundles
64
- // skipLoad), and still fill an empty activePackageVersionId (e.g. reimport after
65
- // the Packages row was deleted). If it was empty, activate on the device unless
66
- // skipResolve.
68
+ // 6. Check released numbers before looking for a mutable local build.
67
69
  const pvTable = (0, package_versions_1.PackageVersions)(dataContext);
68
- const existingPv = await pvTable.findOne({ packageId, versionTag });
70
+ const released = (0, package_versions_1.releasedVersionWithNumber)(await pvTable.list({ packageId }), {
71
+ packageId,
72
+ version,
73
+ });
74
+ if (released) {
75
+ if (released.packageBundleFileHash !== packageHash ||
76
+ released.routesBundleFileHash !== routesHash ||
77
+ released.uiBundleFileHash !== uiHash) {
78
+ throw new Error(`v${version} is already released as ${released.versionTag}; bump the version in package.json, rebuild, and reload`);
79
+ }
80
+ return { packageVersion: released, loaded: undefined, unchanged: true, warning };
81
+ }
82
+ // Released rows are immutable builds: a new number gets a new row even when
83
+ // an SDK caller explicitly installs with a release tag.
84
+ const existingPv = versionTag === "beta" || versionTag === "stable"
85
+ ? undefined
86
+ : await pvTable.findOne({ packageId, versionTag });
87
+ // An unchanged dev build can still repair appNavs and an empty group selection.
69
88
  if (existingPv && existingPv.packageVersionHash === pvHash) {
70
89
  let loaded;
71
90
  let unchanged = true;
@@ -77,7 +96,7 @@ async function installPackageFromBundles(dataContext, deps, packageId, opts) {
77
96
  skipResolve: opts?.skipResolve,
78
97
  resolveOnlyWhenActivated: true,
79
98
  });
80
- return { packageVersion: existingPv, loaded, unchanged };
99
+ return { packageVersion: existingPv, loaded, unchanged, warning };
81
100
  }
82
101
  // 7. Save bundle files (deduped by hash)
83
102
  const packageFile = await saveBundleFile(dataContext, packageBundle, `${packageId}-package.bundle.js`, packageId);
@@ -122,7 +141,17 @@ async function installPackageFromBundles(dataContext, deps, packageId, opts) {
122
141
  await activateInstalledPackageVersion(dataContext, packageId, savedPv, info, localPath, {
123
142
  skipResolve: opts?.skipResolve,
124
143
  });
125
- return { packageVersion: savedPv, loaded, unchanged: false };
144
+ return { packageVersion: savedPv, loaded, unchanged: false, warning };
145
+ }
146
+ async function installedSdkWarning(deps, localPath) {
147
+ try {
148
+ const metadata = await deps.fs.readJson(deps.resolvePath(localPath, "node_modules", "@peers-app", "peers-sdk", "package.json"));
149
+ return (0, package_sdk_version_1.packageSdkVersionWarning)(metadata?.version, sdk_version_1.SDK_VERSION);
150
+ }
151
+ catch {
152
+ // Packages may ship without node_modules, or the host may not be able to read it.
153
+ return undefined;
154
+ }
126
155
  }
127
156
  /**
128
157
  * Sync the Packages row with an installed PackageVersion, then activate it on
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Warn when a package's installed SDK is newer than the host's, using SemVer precedence.
3
+ * Missing or invalid versions are ignored; build metadata does not affect precedence.
4
+ */
5
+ export declare function packageSdkVersionWarning(packageVersion: unknown, hostVersion: unknown): string | undefined;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.packageSdkVersionWarning = packageSdkVersionWarning;
4
+ /**
5
+ * Warn when a package's installed SDK is newer than the host's, using SemVer precedence.
6
+ * Missing or invalid versions are ignored; build metadata does not affect precedence.
7
+ */
8
+ function packageSdkVersionWarning(packageVersion, hostVersion) {
9
+ const incoming = parseVersion(packageVersion);
10
+ const host = parseVersion(hostVersion);
11
+ if (!incoming || !host || compareVersions(incoming, host) <= 0)
12
+ return undefined;
13
+ return `Built with @peers-app/peers-sdk ${packageVersion}, newer than this Peers (${hostVersion}); update Peers, or pin the SDK to ${hostVersion} in package.json`;
14
+ }
15
+ function parseVersion(value) {
16
+ if (typeof value !== "string")
17
+ return undefined;
18
+ const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(value);
19
+ if (!match || match[0] !== value)
20
+ return undefined;
21
+ const prerelease = match[4]?.split(".") ?? [];
22
+ if (prerelease.some((part) => /^0\d+$/.test(part)))
23
+ return undefined;
24
+ return { core: match.slice(1, 4), prerelease };
25
+ }
26
+ function compareIdentifiers(a, b) {
27
+ const aNumeric = /^\d+$/.test(a);
28
+ const bNumeric = /^\d+$/.test(b);
29
+ if (aNumeric && bNumeric && a.length !== b.length)
30
+ return a.length - b.length;
31
+ if (aNumeric !== bNumeric)
32
+ return aNumeric ? -1 : 1;
33
+ return a === b ? 0 : a > b ? 1 : -1;
34
+ }
35
+ function compareVersions(a, b) {
36
+ for (let index = 0; index < a.core.length; index++) {
37
+ const difference = compareIdentifiers(a.core[index], b.core[index]);
38
+ if (difference)
39
+ return difference;
40
+ }
41
+ if (!a.prerelease.length || !b.prerelease.length) {
42
+ return Number(!a.prerelease.length) - Number(!b.prerelease.length);
43
+ }
44
+ for (let index = 0; index < Math.min(a.prerelease.length, b.prerelease.length); index++) {
45
+ const difference = compareIdentifiers(a.prerelease[index], b.prerelease[index]);
46
+ if (difference)
47
+ return difference;
48
+ }
49
+ return a.prerelease.length - b.prerelease.length;
50
+ }
@@ -45,7 +45,7 @@ export interface IPackageInstallerDeps {
45
45
  export interface IInstallOpts {
46
46
  /** Override local path; otherwise read from the packageLocalPath device var. */
47
47
  localPath?: string;
48
- /** Version tag for the created PackageVersion. Defaults to `"dev"`. */
48
+ /** Tag for a new build (default `"dev"`). An identical existing release is returned as-is. */
49
49
  versionTag?: string;
50
50
  /** Skip keeping the package in the loader cache for activation. AppNavs are still backfilled when missing. */
51
51
  skipLoad?: boolean;
@@ -54,12 +54,15 @@ export interface IInstallOpts {
54
54
  }
55
55
  /** Result of {@link installPackageFromBundles}. */
56
56
  export interface IInstallResult {
57
+ /** Non-fatal SDK compatibility warning for a folder install. */
58
+ warning?: string;
57
59
  packageVersion: IPackageVersion;
58
60
  loaded: IPeersPackage | undefined;
59
61
  /**
60
62
  * True when the on-disk bundles match the existing PackageVersion hash and this
61
63
  * call persisted no new appNavs. Bundle files and the PackageVersion row are not
62
- * rewritten; the Packages row and device activation may still be updated.
64
+ * rewritten; for a dev version the Packages row and device activation may still
65
+ * be updated. A matching beta/stable version causes no writes or loading.
63
66
  */
64
67
  unchanged: boolean;
65
68
  }
@@ -109,11 +112,13 @@ export interface ITemplateCacheResult {
109
112
  }
110
113
  /** Result of {@link createPackage}. */
111
114
  export interface ICreatePackageResult {
115
+ /** Non-fatal SDK compatibility warning from installing the folder's bundles. */
116
+ warning?: string;
112
117
  /** New or imported package ID. */
113
118
  packageId: string;
114
119
  /** On-disk package directory. */
115
120
  localPath: string;
116
- /** Installed local/dev package version record. */
121
+ /** Installed build, or an identical already-released version. */
117
122
  packageVersion: IPackageVersion;
118
123
  /**
119
124
  * Present when the default template cache could not be refreshed and a
@@ -136,12 +141,14 @@ export interface IClonePackageOpts {
136
141
  }
137
142
  /** Result of {@link clonePackage}. */
138
143
  export interface IClonePackageResult {
144
+ /** Non-fatal SDK compatibility warning from installing the folder's bundles. */
145
+ warning?: string;
139
146
  /** Imported package ID from the cloned repo. */
140
147
  packageId: string;
141
148
  /** On-disk clone directory. */
142
149
  localPath: string;
143
150
  /** Git URL that was cloned. */
144
151
  remoteRepo: string;
145
- /** Installed local/dev package version record. */
152
+ /** Installed build, or an identical already-released version. */
146
153
  packageVersion: IPackageVersion;
147
154
  }
@@ -48,34 +48,17 @@ export interface IIsolatedContractTool extends Omit<IContractTool, "inputFields"
48
48
  * {@link ISOLATED_TOOL_TIMEOUT_MAX_MS}. Host calls made during the invoke,
49
49
  * including System HTTP, count against this deadline, as does time a handler spends
50
50
  * waiting on the package's own locks. A call that times out is rejected; its handler keeps
51
- * running unless the worker is stuck, in either guest API.
51
+ * running unless the worker is stuck.
52
52
  */
53
53
  timeoutMs?: number;
54
54
  }
55
55
  /**
56
- * The latest guest API version an isolated manifest targets.
57
- *
58
- * - `3`: as `2`, and the package serves its own contract members. The host sends consumers'
59
- * table calls, observable reads and writes, and subscriptions to the provider's worker
60
- * (`contractMemberHandlerName`), and passes every table change, event, and observable
61
- * value bound for a subscriber through it first (`contractDeliveryHandlerName`).
62
- * - `2`: handlers receive `(args, ctx)` and calls run concurrently; the package orders any
63
- * state it shares between calls itself. The host serves contract tables and stored
64
- * observables from storage, without the worker.
65
- * - Omitted: the legacy global helpers, with one call at a time.
66
- *
67
- * Hosts that predate a version reject it.
56
+ * The guest API version built and run by Peers. Consumers' contract member calls and
57
+ * notifications pass through the provider's worker under the caller's authority.
68
58
  */
69
59
  export declare const ISOLATED_GUEST_API_VERSION = 3;
70
- /** Guest API versions a manifest may declare. */
71
- export type IsolatedGuestApi = 2 | 3;
72
- /**
73
- * True when the package serves its contract members itself (guest API 3): every consumer call
74
- * and outgoing message goes through its worker.
75
- *
76
- * @param manifest - The package's manifest.
77
- */
78
- export declare function servesContractMembers(manifest: Pick<IIsolatedPackageManifest, "guestApi">): boolean;
60
+ /** Guest API version for packages built with peers-package. */
61
+ export type IsolatedGuestApi = 3;
79
62
  /** Longest per-tool invoke deadline an isolated manifest may declare. */
80
63
  export declare const ISOLATED_TOOL_TIMEOUT_MAX_MS = 120000;
81
64
  /**
@@ -152,9 +135,9 @@ export interface IIsolatedContractPersistentVarBinding {
152
135
  }
153
136
  /**
154
137
  * Observable contract member. With `persistentVar`, a package-owned persistent variable holds
155
- * the value. Without it (guest API 2 or later), the worker holds the value in memory and
156
- * publishes it with `publishOwnedObservable`. Guest API 3 serves all consumer calls and
157
- * deliveries through the worker; older packages serve stored values directly from the host.
138
+ * the value. Without it, the worker holds the value in memory and
139
+ * publishes it with `publishOwnedObservable`. All consumer calls and deliveries pass
140
+ * through the worker.
158
141
  */
159
142
  export interface IIsolatedContractObservable extends IContractObservable {
160
143
  persistentVar?: IIsolatedContractPersistentVarBinding;
@@ -224,9 +207,8 @@ export interface IIsolatedPackageManifest {
224
207
  network?: IIsolatedPackageNetwork;
225
208
  appNavs?: IAppNav[];
226
209
  /**
227
- * Guest API version ({@link ISOLATED_GUEST_API_VERSION}). `2` for handlers that take
228
- * `(args, ctx)`; `3` when the package also serves its own contract members; omit for the
229
- * legacy global helpers, which run one call at a time.
210
+ * Guest API version ({@link ISOLATED_GUEST_API_VERSION}). Required by the runtime;
211
+ * optional here so build tools can inspect historical bundles without running them.
230
212
  */
231
213
  guestApi?: IsolatedGuestApi;
232
214
  }
@@ -311,15 +293,20 @@ export declare function looksLikeIsolatedPackageArtifact(bundle: string): boolea
311
293
  */
312
294
  export declare function validateIsolatedPackageArtifact(artifact: IIsolatedPackageArtifact, options?: {
313
295
  strict?: boolean;
296
+ /** Refuse unsupported guest APIs before validating their contents for execution. */
297
+ requireCurrentGuestApi?: boolean;
298
+ /** Display name to include alongside the package ID in a runtime refusal. */
299
+ packageName?: string;
314
300
  }): IIsolatedPackageManifest;
315
301
  /**
316
302
  * Parse a magic-header + JSON isolated package bundle.
317
303
  *
318
304
  * @param bundle - Full package-bundle text.
305
+ * @param options - Runtime loaders require the current guest API; build tools may inspect older bundles.
319
306
  * @returns The validated artifact.
320
307
  * @throws If the magic header is present but the envelope is malformed.
321
308
  */
322
- export declare function parseIsolatedPackageArtifact(bundle: string): IIsolatedPackageArtifact;
309
+ export declare function parseIsolatedPackageArtifact(bundle: string, options?: Parameters<typeof validateIsolatedPackageArtifact>[1]): IIsolatedPackageArtifact;
323
310
  /**
324
311
  * Serialize an isolated artifact into the package-bundle slot format.
325
312
  *
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ISOLATED_MAX_FIELD_DEPTH = exports.ISOLATED_OBSERVABLE_HOST_MEMBER = exports.ISOLATED_EVENT_HOST_MEMBER = exports.ISOLATED_PERSISTENT_VAR_HOST_MEMBER = exports.ISOLATED_TABLE_HOST_MEMBER = exports.ISOLATED_CONSUME_HOST_MEMBER = exports.ISOLATED_TOOL_TIMEOUT_MAX_MS = exports.ISOLATED_GUEST_API_VERSION = exports.ISOLATED_PACKAGE_MAGIC = void 0;
4
- exports.servesContractMembers = servesContractMembers;
5
4
  exports.enumValueFits = enumValueFits;
6
5
  exports.looksLikeIsolatedPackageArtifact = looksLikeIsolatedPackageArtifact;
7
6
  exports.validateIsolatedPackageArtifact = validateIsolatedPackageArtifact;
@@ -30,29 +29,10 @@ const utils_1 = require("../utils");
30
29
  */
31
30
  exports.ISOLATED_PACKAGE_MAGIC = "PEERS_ISOLATED_PACKAGE_V1";
32
31
  /**
33
- * The latest guest API version an isolated manifest targets.
34
- *
35
- * - `3`: as `2`, and the package serves its own contract members. The host sends consumers'
36
- * table calls, observable reads and writes, and subscriptions to the provider's worker
37
- * (`contractMemberHandlerName`), and passes every table change, event, and observable
38
- * value bound for a subscriber through it first (`contractDeliveryHandlerName`).
39
- * - `2`: handlers receive `(args, ctx)` and calls run concurrently; the package orders any
40
- * state it shares between calls itself. The host serves contract tables and stored
41
- * observables from storage, without the worker.
42
- * - Omitted: the legacy global helpers, with one call at a time.
43
- *
44
- * Hosts that predate a version reject it.
32
+ * The guest API version built and run by Peers. Consumers' contract member calls and
33
+ * notifications pass through the provider's worker under the caller's authority.
45
34
  */
46
35
  exports.ISOLATED_GUEST_API_VERSION = 3;
47
- /**
48
- * True when the package serves its contract members itself (guest API 3): every consumer call
49
- * and outgoing message goes through its worker.
50
- *
51
- * @param manifest - The package's manifest.
52
- */
53
- function servesContractMembers(manifest) {
54
- return manifest.guestApi === 3;
55
- }
56
36
  /** Longest per-tool invoke deadline an isolated manifest may declare. */
57
37
  exports.ISOLATED_TOOL_TIMEOUT_MAX_MS = 120_000;
58
38
  /** Host-contract member guests use to request a consumed-contract tool call. */
@@ -340,7 +320,7 @@ function validatePackageTables(manifest, strict) {
340
320
  (0, package_table_identity_1.validateTableIndexes)(table.indexes, new Set(table.fields.map((field) => field.name)), `${tableLabel}.indexes`, table.localOnly);
341
321
  }
342
322
  }
343
- function validateProvidedContract(contract, index, packageTables, guestApi, strict) {
323
+ function validateProvidedContract(contract, index, packageTables, strict) {
344
324
  const label = `provides[${index}]`;
345
325
  if (!contract || typeof contract !== "object" || Array.isArray(contract)) {
346
326
  throw new Error(`${label} must be an object`);
@@ -488,13 +468,8 @@ function validateProvidedContract(contract, index, packageTables, guestApi, stri
488
468
  throw new Error(`${observableLabel}.writable must be a boolean`);
489
469
  }
490
470
  const binding = observable.persistentVar;
491
- if (binding === undefined) {
492
- // Held by the worker, which publishes it: that needs guest API 2 or later.
493
- if (guestApi === undefined) {
494
- throw new Error(`${observableLabel}.persistentVar is required unless the package uses guestApi 2 or later`);
495
- }
471
+ if (binding === undefined)
496
472
  continue;
497
- }
498
473
  if (!binding || typeof binding !== "object" || Array.isArray(binding)) {
499
474
  throw new Error(`${observableLabel}.persistentVar must be an object`);
500
475
  }
@@ -777,6 +752,15 @@ function validateIsolatedPackageArtifact(artifact, options = {}) {
777
752
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
778
753
  throw new Error("Package artifact.manifest is required");
779
754
  }
755
+ if (options.requireCurrentGuestApi && manifest.guestApi !== exports.ISOLATED_GUEST_API_VERSION) {
756
+ const identity = options.packageName
757
+ ? `'${options.packageName}' (${manifest.packageId})`
758
+ : manifest.packageId;
759
+ if (typeof manifest.guestApi === "number" && manifest.guestApi > exports.ISOLATED_GUEST_API_VERSION) {
760
+ throw new Error(`Package ${identity} needs a newer version of Peers; update Peers to use it.`);
761
+ }
762
+ throw new Error(`Package ${identity} has an unsupported guest API. Rebuild it with peers-package; guest API 2 and packages without guestApi are no longer supported.`);
763
+ }
780
764
  assertOnlyKeys(manifest, [
781
765
  "packageId",
782
766
  "version",
@@ -802,7 +786,9 @@ function validateIsolatedPackageArtifact(artifact, options = {}) {
802
786
  manifest.executionMode !== "privileged") {
803
787
  throw new Error('manifest.executionMode must be "sandbox" or "privileged"');
804
788
  }
805
- if (manifest.guestApi !== undefined && manifest.guestApi !== 2 && manifest.guestApi !== 3) {
789
+ const guestApi = manifest.guestApi;
790
+ // Contract import/freezing tools still inspect historical bundles without executing them.
791
+ if (guestApi !== undefined && guestApi !== 2 && guestApi !== 3) {
806
792
  throw new Error("manifest.guestApi must be 2 or 3 when set");
807
793
  }
808
794
  assertPeerId(manifest.packageId, "packageId");
@@ -820,7 +806,7 @@ function validateIsolatedPackageArtifact(artifact, options = {}) {
820
806
  const packageTables = manifest.tables ?? [];
821
807
  const providedKeys = new Set();
822
808
  for (const [index, contract] of manifest.provides.entries()) {
823
- validateProvidedContract(contract, index, packageTables, manifest.guestApi, strict);
809
+ validateProvidedContract(contract, index, packageTables, strict);
824
810
  const key = `${contract.contractId}@${contract.version}`;
825
811
  if (providedKeys.has(key)) {
826
812
  throw new Error(`Duplicate provided contract ${key}`);
@@ -857,10 +843,11 @@ function validateIsolatedPackageArtifact(artifact, options = {}) {
857
843
  * Parse a magic-header + JSON isolated package bundle.
858
844
  *
859
845
  * @param bundle - Full package-bundle text.
846
+ * @param options - Runtime loaders require the current guest API; build tools may inspect older bundles.
860
847
  * @returns The validated artifact.
861
848
  * @throws If the magic header is present but the envelope is malformed.
862
849
  */
863
- function parseIsolatedPackageArtifact(bundle) {
850
+ function parseIsolatedPackageArtifact(bundle, options = {}) {
864
851
  if (!looksLikeIsolatedPackageArtifact(bundle)) {
865
852
  throw new Error("Bundle is not an isolated package artifact");
866
853
  }
@@ -878,7 +865,7 @@ function parseIsolatedPackageArtifact(bundle) {
878
865
  throw new Error(`Isolated package artifact JSON is invalid: ${err instanceof Error ? err.message : String(err)}`);
879
866
  }
880
867
  const artifact = parsed;
881
- validateIsolatedPackageArtifact(artifact);
868
+ validateIsolatedPackageArtifact(artifact, options);
882
869
  return artifact;
883
870
  }
884
871
  /**
@@ -30,7 +30,7 @@ export interface IPackageContractEndpointOptions {
30
30
  * Loads package versions for one {@link DataContext}.
31
31
  *
32
32
  * Isolated artifacts (magic-header JSON) are installed without host
33
- * evaluation. Legacy `package.bundle.js` files still evaluate in-process.
33
+ * evaluation. Host-backed `definePackage()` bundles evaluate in-process.
34
34
  */
35
35
  export declare class PackageLoader {
36
36
  readonly dataContext: DataContext;
@@ -75,7 +75,7 @@ export declare class PackageLoader {
75
75
  get catalogReady(): Promise<void>;
76
76
  /** Optional isolated-provider supervisor created from the host factory. */
77
77
  readonly isolatedRuntime?: IIsolatedPackageRuntime;
78
- /** Optional host `require` used when reading local bundles or evaluating legacy packages. */
78
+ /** Optional host `require` used when reading local bundles or evaluating host-backed packages. */
79
79
  require: (<T>(module: string) => T) | undefined;
80
80
  /**
81
81
  * @param dataContext - Owning personal or group data context. Its
@@ -116,7 +116,7 @@ export declare class PackageLoader {
116
116
  */
117
117
  private recordLoadFailure;
118
118
  private dispatchToolFromProvider;
119
- /** Project a legacy caller's declared tool reach and authority for advisory catalog listings. */
119
+ /** Project a host caller's declared tool reach and authority for advisory catalog listings. */
120
120
  hostToolPrincipal(principal: IIsolatedCallPrincipal, packageId: string, target: {
121
121
  contractId: string;
122
122
  version: number;
@@ -146,7 +146,7 @@ export declare class PackageLoader {
146
146
  resolveContractEndpoint(request: IContractProviderEndpointRequest, opts?: IPackageContractEndpointOptions): Promise<IContractProviderEndpoint>;
147
147
  private guardResolutionTools;
148
148
  /**
149
- * Bind a legacy package's declared consume to its selected provider.
149
+ * Bind a host-backed package's declared consume to its selected provider.
150
150
  *
151
151
  * The returned proxy can target either a resolver-backed or isolated provider.
152
152
  * Its provider choice is captured from {@link ContractRegistry.selectProvider}
@@ -158,7 +158,7 @@ export declare class PackageLoader {
158
158
  */
159
159
  resolveConsumedContract(dependency: IConsumedContract, callerPackageId: string): Promise<IContractConsumer | undefined>;
160
160
  /**
161
- * Bind a legacy package's `remote()` declaration to a per-device consumer factory.
161
+ * Bind a host-backed package's `remote()` declaration to a per-device consumer factory.
162
162
  *
163
163
  * No local provider is selected: every device consumer stamps `targetDeviceId` and
164
164
  * this loader's own router forwards the call through
@@ -46,7 +46,7 @@ function isolatedContractLifecycle(packageVersion) {
46
46
  * Loads package versions for one {@link DataContext}.
47
47
  *
48
48
  * Isolated artifacts (magic-header JSON) are installed without host
49
- * evaluation. Legacy `package.bundle.js` files still evaluate in-process.
49
+ * evaluation. Host-backed `definePackage()` bundles evaluate in-process.
50
50
  */
51
51
  class PackageLoader {
52
52
  dataContext;
@@ -93,7 +93,7 @@ class PackageLoader {
93
93
  }
94
94
  /** Optional isolated-provider supervisor created from the host factory. */
95
95
  isolatedRuntime;
96
- /** Optional host `require` used when reading local bundles or evaluating legacy packages. */
96
+ /** Optional host `require` used when reading local bundles or evaluating host-backed packages. */
97
97
  require = undefined;
98
98
  /**
99
99
  * @param dataContext - Owning personal or group data context. Its
@@ -264,7 +264,7 @@ class PackageLoader {
264
264
  endpoint.dispose();
265
265
  }
266
266
  }
267
- /** Project a legacy caller's declared tool reach and authority for advisory catalog listings. */
267
+ /** Project a host caller's declared tool reach and authority for advisory catalog listings. */
268
268
  hostToolPrincipal(principal, packageId, target) {
269
269
  if (!principal.userId || this.isolatedRuntime?.isInstalled(packageId))
270
270
  return undefined;
@@ -473,7 +473,7 @@ class PackageLoader {
473
473
  };
474
474
  }
475
475
  /**
476
- * Bind a legacy package's declared consume to its selected provider.
476
+ * Bind a host-backed package's declared consume to its selected provider.
477
477
  *
478
478
  * The returned proxy can target either a resolver-backed or isolated provider.
479
479
  * Its provider choice is captured from {@link ContractRegistry.selectProvider}
@@ -514,7 +514,7 @@ class PackageLoader {
514
514
  return consumer;
515
515
  }
516
516
  /**
517
- * Bind a legacy package's `remote()` declaration to a per-device consumer factory.
517
+ * Bind a host-backed package's `remote()` declaration to a per-device consumer factory.
518
518
  *
519
519
  * No local provider is selected: every device consumer stamps `targetDeviceId` and
520
520
  * this loader's own router forwards the call through
@@ -910,7 +910,10 @@ class PackageLoader {
910
910
  assertCurrent();
911
911
  if ((0, isolated_artifact_1.looksLikeIsolatedPackageArtifact)(bundleCode)) {
912
912
  try {
913
- const artifact = (0, isolated_artifact_1.parseIsolatedPackageArtifact)(bundleCode);
913
+ const artifact = (0, isolated_artifact_1.parseIsolatedPackageArtifact)(bundleCode, {
914
+ requireCurrentGuestApi: true,
915
+ packageName: pkg.name,
916
+ });
914
917
  return await this._installIsolated(pkg, artifact, selectedPackageVersion, usingLocalBundle, force, assertCurrent);
915
918
  }
916
919
  catch (err) {
@@ -920,7 +923,7 @@ class PackageLoader {
920
923
  await this.catalogReady;
921
924
  assertCurrent();
922
925
  const replacingIsolated = this.isolatedRuntime?.isInstalled(pkg.packageId) ?? false;
923
- const instance = await this._evaluateBundle(pkg, bundleCode, selectedPackageVersion, force, assertCurrent);
926
+ const instance = await this._evaluateBundle(pkg, bundleCode, selectedPackageVersion, assertCurrent);
924
927
  if (!instance) {
925
928
  return undefined;
926
929
  }
@@ -995,7 +998,7 @@ class PackageLoader {
995
998
  });
996
999
  return instance;
997
1000
  }
998
- async _evaluateBundle(pkg, bundleCode, selectedPackageVersion, force, assertCurrent = this.guardLoad(pkg.packageId)) {
1001
+ async _evaluateBundle(pkg, bundleCode, selectedPackageVersion, assertCurrent = this.guardLoad(pkg.packageId)) {
999
1002
  // Node.js built-in modules that do not exist in React Native (and would cause
1000
1003
  // a fatal native error if passed through to RN's require). We block them here
1001
1004
  // and throw a descriptive JS Error instead, which the surrounding try/catch
@@ -1063,19 +1066,16 @@ class PackageLoader {
1063
1066
  const bundleFunction = new Function("module", "exports", "require", bundleCode);
1064
1067
  const activateResolvers = (0, contract_resolvers_1.stagePackageContractResolvers)(this.dataContext, pkg.packageId, () => bundleFunction(module, moduleExports, customRequire));
1065
1068
  const bundleExports = module.exports;
1066
- // Check for contract-based package definition (new path)
1069
+ // Host bundles must export a definePackage() definition.
1067
1070
  const packageDefinition = (0, contract_package_loader_1.extractPackageDefinition)(bundleExports);
1068
1071
  if (packageDefinition) {
1069
1072
  if (packageDefinition.packageId !== pkg.packageId) {
1070
1073
  console.warn(`[PackageLoader] packageId mismatch: definition has "${packageDefinition.packageId}" but DB record has "${pkg.packageId}"`);
1071
1074
  }
1072
- // Build a proper IPeersPackage from the contract definition so callers
1073
- // (e.g. package-installer) can read appNavs, assistants, etc.
1075
+ // Expose loaded identity and navigation to callers such as package-installer.
1074
1076
  const contractPackageInstance = {
1075
1077
  packageId: packageDefinition.packageId,
1076
1078
  appNavs: packageDefinition.appNavs,
1077
- assistants: packageDefinition.assistants,
1078
- tableDefinitions: packageDefinition.tableDefinitions,
1079
1079
  };
1080
1080
  await (0, contract_package_loader_1.installContractPackage)(this.dataContext, packageDefinition, {
1081
1081
  registry: this.contractRegistry,
@@ -1100,36 +1100,7 @@ class PackageLoader {
1100
1100
  assertCurrent();
1101
1101
  return contractPackageInstance;
1102
1102
  }
1103
- // Legacy path: extract IPeersPackage for backward compat
1104
- const packageInstance = bundleExports?.exports || bundleExports?.package || bundleExports?.default || bundleExports;
1105
- if (packageInstance &&
1106
- typeof packageInstance === "object" &&
1107
- typeof packageInstance.packageId === "string" &&
1108
- packageInstance.packageId.length > 0) {
1109
- const tools = packageInstance.toolInstances;
1110
- if (Array.isArray(tools) && tools.length)
1111
- throw new Error(`Package '${pkg.name}' (${pkg.packageId}) ships legacy executable tools, which this runtime no longer runs; upgrade it to definePackage() contract tools (docs/Packages/contracts.md#migrating-from-toolinstances)`);
1112
- await this.toolCatalog.publish(pkg.packageId, [], async () => assertCurrent(), () => {
1113
- assertCurrent();
1114
- // Final catalog writes must succeed before changing active host tables.
1115
- if (packageInstance.tableDefinitions?.length) {
1116
- this.dataContext.tableContainer.registerTableDefinitions(packageInstance.tableDefinitions, { overwrite: true });
1117
- }
1118
- activateResolvers();
1119
- this.retireHostEndpoints(pkg.packageId);
1120
- if (force && this.contractInstalledPackages.has(pkg.packageId)) {
1121
- this.contractRegistry.unregister(pkg.packageId);
1122
- this.contractInstalledPackages.delete(pkg.packageId);
1123
- }
1124
- this.packageInstances[pkg.packageId] = packageInstance;
1125
- this.recordRunning(pkg.packageId, {
1126
- ...runningVersionFields(selectedPackageVersion),
1127
- kind: "legacy",
1128
- });
1129
- });
1130
- return packageInstance;
1131
- }
1132
- throw new Error("Package bundle exports nothing usable");
1103
+ throw new Error(`Package '${pkg.name}' (${pkg.packageId}) has an unsupported bundle format. Rebuild it with definePackage() and peers-package.`);
1133
1104
  }
1134
1105
  catch (err) {
1135
1106
  this.recordLoadFailure(pkg.packageId, selectedPackageVersion, err, assertCurrent);