@peers-app/peers-sdk 0.23.4 → 0.23.6

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 (75) hide show
  1. package/dist/context/data-context.d.ts +36 -5
  2. package/dist/context/data-context.js +44 -9
  3. package/dist/context/user-context.d.ts +44 -0
  4. package/dist/context/user-context.js +91 -12
  5. package/dist/contracts/__tests__/contract-provider-router.test.js +26 -0
  6. package/dist/contracts/__tests__/contract-resolvers.test.js +44 -0
  7. package/dist/contracts/__tests__/registry.test.js +104 -0
  8. package/dist/contracts/contract-provider-router.d.ts +21 -0
  9. package/dist/contracts/contract-provider-router.js +31 -5
  10. package/dist/contracts/contract-proxy.d.ts +3 -0
  11. package/dist/contracts/contract-proxy.js +3 -0
  12. package/dist/contracts/contract-resolvers.d.ts +21 -3
  13. package/dist/contracts/contract-resolvers.js +105 -32
  14. package/dist/contracts/index.d.ts +3 -3
  15. package/dist/contracts/registry.d.ts +59 -0
  16. package/dist/contracts/registry.js +155 -25
  17. package/dist/contracts/system-contracts.js +3 -1
  18. package/dist/data/index.d.ts +1 -0
  19. package/dist/data/index.js +1 -0
  20. package/dist/data/orm/types.d.ts +19 -0
  21. package/dist/data/orm/types.js +11 -0
  22. package/dist/data/package-table-identity.d.ts +46 -0
  23. package/dist/data/package-table-identity.js +71 -0
  24. package/dist/data/package-table-identity.test.d.ts +1 -0
  25. package/dist/data/package-table-identity.test.js +46 -0
  26. package/dist/data/package-version-resolver.d.ts +13 -0
  27. package/dist/data/package-version-resolver.js +142 -6
  28. package/dist/data/package-version-resolver.test.js +42 -0
  29. package/dist/data/persistent-var-identity.d.ts +37 -0
  30. package/dist/data/persistent-var-identity.js +69 -0
  31. package/dist/data/persistent-vars.d.ts +3 -5
  32. package/dist/data/persistent-vars.js +25 -13
  33. package/dist/data/persistent-vars.package-scope.test.d.ts +1 -0
  34. package/dist/data/persistent-vars.package-scope.test.js +27 -0
  35. package/dist/data/tools.d.ts +3 -3
  36. package/dist/package-installer/index.d.ts +1 -0
  37. package/dist/package-installer/index.js +1 -0
  38. package/dist/package-installer/package-cloner.d.ts +1 -1
  39. package/dist/package-installer/package-cloner.js +11 -16
  40. package/dist/package-installer/package-cloner.test.js +5 -3
  41. package/dist/package-installer/package-creator.d.ts +4 -3
  42. package/dist/package-installer/package-creator.js +13 -5
  43. package/dist/package-installer/package-creator.test.js +19 -0
  44. package/dist/package-installer/package-dev-tools.d.ts +30 -0
  45. package/dist/package-installer/package-dev-tools.js +63 -0
  46. package/dist/package-installer/package-dev-tools.test.d.ts +1 -0
  47. package/dist/package-installer/package-dev-tools.test.js +64 -0
  48. package/dist/package-loader/contract-package-loader.js +20 -8
  49. package/dist/package-loader/contract-package-loader.test.js +16 -0
  50. package/dist/package-loader/index.d.ts +4 -0
  51. package/dist/package-loader/index.js +4 -0
  52. package/dist/package-loader/isolated-artifact.d.ts +233 -0
  53. package/dist/package-loader/isolated-artifact.js +643 -0
  54. package/dist/package-loader/isolated-artifact.test.d.ts +1 -0
  55. package/dist/package-loader/isolated-artifact.test.js +270 -0
  56. package/dist/package-loader/isolated-persistent-var-gateway.d.ts +20 -0
  57. package/dist/package-loader/isolated-persistent-var-gateway.js +106 -0
  58. package/dist/package-loader/isolated-persistent-var-gateway.test.d.ts +1 -0
  59. package/dist/package-loader/isolated-persistent-var-gateway.test.js +56 -0
  60. package/dist/package-loader/isolated-runtime.d.ts +229 -0
  61. package/dist/package-loader/isolated-runtime.js +37 -0
  62. package/dist/package-loader/isolated-table-gateway.d.ts +16 -0
  63. package/dist/package-loader/isolated-table-gateway.js +375 -0
  64. package/dist/package-loader/isolated-table-gateway.test.d.ts +1 -0
  65. package/dist/package-loader/isolated-table-gateway.test.js +206 -0
  66. package/dist/package-loader/isolated-value.d.ts +14 -0
  67. package/dist/package-loader/isolated-value.js +63 -0
  68. package/dist/package-loader/package-loader.d.ts +123 -4
  69. package/dist/package-loader/package-loader.js +417 -46
  70. package/dist/package-loader/package-loader.test.js +478 -12
  71. package/dist/package-loader/resolve-consumed-contracts.d.ts +3 -3
  72. package/dist/package-loader/resolve-consumed-contracts.js +18 -10
  73. package/dist/utils.d.ts +21 -2
  74. package/dist/utils.js +46 -7
  75. package/package.json +1 -1
@@ -6,6 +6,7 @@ const packages_1 = require("../data/packages");
6
6
  const persistent_vars_1 = require("../data/persistent-vars");
7
7
  const system_ids_1 = require("../system-ids");
8
8
  const utils_1 = require("../utils");
9
+ const package_dev_tools_1 = require("./package-dev-tools");
9
10
  const package_installer_1 = require("./package-installer");
10
11
  /**
11
12
  * Derive a filesystem-safe slug from a git remote URL.
@@ -26,24 +27,13 @@ function autoDetectDisabled(remoteRepo) {
26
27
  * Clone a remote git repository containing a Peers package, install dependencies,
27
28
  * build it, and register the dev bundle in the database.
28
29
  *
29
- * Prerequisites (git, npm) are validated up front with clear error messages.
30
+ * Prerequisites (git, and Node.js/npm unless `skipBuild`) are validated up front.
30
31
  * If the target directory already exists and contains a valid package, delegates
31
32
  * to {@link installPackageFromBundles} instead of re-cloning.
32
33
  */
33
34
  async function clonePackage(dataContext, deps, opts) {
34
- // 1. Validate prerequisites
35
- try {
36
- await deps.shell.exec("git --version");
37
- }
38
- catch {
39
- throw new Error("git is not installed. Please install git to add remote packages.");
40
- }
41
- try {
42
- await deps.shell.exec("npm --version");
43
- }
44
- catch {
45
- throw new Error("npm is not installed. Please install Node.js (which includes npm) to add remote packages.");
46
- }
35
+ // 1. Validate prerequisites before cloning so a missing toolchain does not leave a partial repo.
36
+ await (0, package_dev_tools_1.assertPackageDevTools)(deps, { requireNode: opts.skipBuild !== true });
47
37
  // 2. Resolve location
48
38
  const location = opts.location ?? deps.resolvePath(deps.packagesRootDir, deriveRepoSlug(opts.remoteRepo));
49
39
  // 3. Check if directory already exists
@@ -66,8 +56,13 @@ async function clonePackage(dataContext, deps, opts) {
66
56
  await deps.shell.exec(`git clone ${opts.remoteRepo} ${location}`);
67
57
  // 5. Install + build (unless skipBuild)
68
58
  if (!opts.skipBuild) {
69
- await deps.shell.exec("npm install", { cwd: location });
70
- await deps.shell.exec("npm run build", { cwd: location });
59
+ try {
60
+ await deps.shell.exec("npm install", { cwd: location });
61
+ await deps.shell.exec("npm run build", { cwd: location });
62
+ }
63
+ catch (error) {
64
+ throw (0, package_dev_tools_1.rewriteMissingDevToolError)(error);
65
+ }
71
66
  }
72
67
  // 6. Read package info and resolve packageId
73
68
  const info = await (0, package_installer_1.getPackageInfo)(deps, location);
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const package_cloner_1 = require("./package-cloner");
4
+ const package_dev_tools_1 = require("./package-dev-tools");
4
5
  // --- Mocks for SDK internals ---
5
6
  const mockSignAndSavePkg = jest.fn();
6
7
  jest.mock("../data/packages", () => ({
@@ -68,7 +69,7 @@ describe("clonePackage", () => {
68
69
  });
69
70
  const deps = makeMockDeps(shellExec);
70
71
  const dc = mockDataContext();
71
- await expect((0, package_cloner_1.clonePackage)(dc, deps, { remoteRepo: "https://github.com/peers-app/test.git" })).rejects.toThrow("git is not installed");
72
+ await expect((0, package_cloner_1.clonePackage)(dc, deps, { remoteRepo: "https://github.com/peers-app/test.git" })).rejects.toThrow(package_dev_tools_1.MISSING_GIT_MESSAGE);
72
73
  });
73
74
  it("throws when npm is not installed", async () => {
74
75
  const shellExec = jest.fn(async (cmd) => {
@@ -78,7 +79,7 @@ describe("clonePackage", () => {
78
79
  });
79
80
  const deps = makeMockDeps(shellExec);
80
81
  const dc = mockDataContext();
81
- await expect((0, package_cloner_1.clonePackage)(dc, deps, { remoteRepo: "https://github.com/peers-app/test.git" })).rejects.toThrow("npm is not installed");
82
+ await expect((0, package_cloner_1.clonePackage)(dc, deps, { remoteRepo: "https://github.com/peers-app/test.git" })).rejects.toThrow(package_dev_tools_1.MISSING_NODE_MESSAGE);
82
83
  });
83
84
  it("clones, installs, builds, and registers a package with valid packageId", async () => {
84
85
  const deps = makeMockDeps();
@@ -184,7 +185,8 @@ describe("clonePackage", () => {
184
185
  });
185
186
  const execCalls = deps.shell.exec.mock.calls.map((c) => c[0]);
186
187
  expect(execCalls).toContain("git --version");
187
- expect(execCalls).toContain("npm --version");
188
+ expect(execCalls).not.toContain("node --version");
189
+ expect(execCalls).not.toContain("npm --version");
188
190
  expect(execCalls).toContain("git clone https://github.com/peers-app/prebuilt.git /packages/prebuilt");
189
191
  expect(execCalls).not.toContain("npm install");
190
192
  expect(execCalls).not.toContain("npm run build");
@@ -43,9 +43,10 @@ export declare function importExistingPackage(dataContext: DataContext, deps: IP
43
43
  /**
44
44
  * Create a new Peers package from the package template.
45
45
  *
46
- * Clones the template repo, patches IDs and metadata, runs npm install + build,
47
- * initializes a fresh git repo, then delegates to {@link installPackageFromBundles}
48
- * to register the dev bundle in the database.
46
+ * Validates git/Node.js/npm first so a missing toolchain does not leave a
47
+ * half-copied package directory. Then clones the template repo, patches IDs
48
+ * and metadata, runs npm install + build, initializes a fresh git repo, and
49
+ * delegates to {@link installPackageFromBundles} to register the dev bundle.
49
50
  *
50
51
  * When `location` already exists and contains a valid Peers package manifest,
51
52
  * imports that directory instead (see {@link importExistingPackage}).
@@ -11,6 +11,7 @@ const keys_1 = require("../keys");
11
11
  const system_ids_1 = require("../system-ids");
12
12
  const utils_1 = require("../utils");
13
13
  const package_author_signing_1 = require("./package-author-signing");
14
+ const package_dev_tools_1 = require("./package-dev-tools");
14
15
  const package_installer_1 = require("./package-installer");
15
16
  const DEFAULT_TEMPLATE_REPO = "https://github.com/peers-app/peers-package-template.git";
16
17
  const TEMPLATE_CACHE_DIR = ".peers-package-template";
@@ -170,9 +171,10 @@ async function importExistingPackage(dataContext, deps, opts) {
170
171
  /**
171
172
  * Create a new Peers package from the package template.
172
173
  *
173
- * Clones the template repo, patches IDs and metadata, runs npm install + build,
174
- * initializes a fresh git repo, then delegates to {@link installPackageFromBundles}
175
- * to register the dev bundle in the database.
174
+ * Validates git/Node.js/npm first so a missing toolchain does not leave a
175
+ * half-copied package directory. Then clones the template repo, patches IDs
176
+ * and metadata, runs npm install + build, initializes a fresh git repo, and
177
+ * delegates to {@link installPackageFromBundles} to register the dev bundle.
176
178
  *
177
179
  * When `location` already exists and contains a valid Peers package manifest,
178
180
  * imports that directory instead (see {@link importExistingPackage}).
@@ -187,6 +189,7 @@ async function createPackage(dataContext, deps, opts) {
187
189
  displayName: opts.name,
188
190
  });
189
191
  }
192
+ await (0, package_dev_tools_1.assertPackageDevTools)(deps);
190
193
  // Generate fresh IDs
191
194
  const packageId = (0, utils_1.newid)();
192
195
  const contractId = (0, utils_1.newid)();
@@ -226,8 +229,13 @@ async function createPackage(dataContext, deps, opts) {
226
229
  }
227
230
  // Initialize as a new project: remove template git history, install, build, fresh git
228
231
  await deps.shell.exec("rm -rf .git", { cwd: location });
229
- await deps.shell.exec("npm install", { cwd: location });
230
- await deps.shell.exec("npm run build", { cwd: location });
232
+ try {
233
+ await deps.shell.exec("npm install", { cwd: location });
234
+ await deps.shell.exec("npm run build", { cwd: location });
235
+ }
236
+ catch (error) {
237
+ throw (0, package_dev_tools_1.rewriteMissingDevToolError)(error);
238
+ }
231
239
  await deps.shell.exec("git init", { cwd: location });
232
240
  await deps.shell.exec("git add .", { cwd: location });
233
241
  await deps.shell.exec('git commit -m "Initial commit"', { cwd: location });
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const package_creator_1 = require("./package-creator");
4
+ const package_dev_tools_1 = require("./package-dev-tools");
4
5
  // --- Mocks for SDK internals ---
5
6
  const mockSignAndSavePkg = jest.fn();
6
7
  const mockGetPkg = jest.fn();
@@ -97,6 +98,23 @@ describe("nameToFolderSlug", () => {
97
98
  });
98
99
  });
99
100
  describe("createPackage", () => {
101
+ it("fails before scaffolding when Node.js or npm is missing", async () => {
102
+ const shellExec = jest.fn(async (cmd) => {
103
+ if (cmd === "npm --version") {
104
+ throw new Error("/bin/sh: npm: command not found");
105
+ }
106
+ return { stdout: "", stderr: "" };
107
+ });
108
+ const deps = makeMockDeps({}, shellExec);
109
+ deps.fs.exists.mockResolvedValue(false);
110
+ await expect((0, package_creator_1.createPackage)(mockDataContext(), deps, { name: "KidsTube" })).rejects.toThrow(package_dev_tools_1.MISSING_NODE_MESSAGE);
111
+ expect(deps.fs.writeFile).not.toHaveBeenCalled();
112
+ expect(shellExec.mock.calls.map((call) => call[0])).toEqual([
113
+ "git --version",
114
+ "node --version",
115
+ "npm --version",
116
+ ]);
117
+ });
100
118
  it("creates a package using local template cache with correct steps", async () => {
101
119
  const templatePkgJson = JSON.stringify({
102
120
  name: "peers-package-template",
@@ -135,6 +153,7 @@ describe("createPackage", () => {
135
153
  author: "Test User",
136
154
  });
137
155
  const execCalls = deps.shell.exec.mock.calls.map((c) => c[0]);
156
+ expect(execCalls.slice(0, 3)).toEqual(["git --version", "node --version", "npm --version"]);
138
157
  // Verify template was cloned to cache (not directly to location)
139
158
  expect(execCalls).toContain("git clone https://github.com/peers-app/peers-package-template.git /packages/.peers-package-template");
140
159
  // Verify cp -r from cache to location
@@ -0,0 +1,30 @@
1
+ import type { IPackageInstallerDeps } from "./types";
2
+ /** User-facing error when `git` is missing from the host PATH. */
3
+ export declare const MISSING_GIT_MESSAGE = "git is required to create and add packages. Install git, then quit and reopen Peers.";
4
+ /**
5
+ * User-facing error when Node.js or npm cannot be found.
6
+ *
7
+ * Keep this specific enough that a packaged-app PATH miss is distinguishable
8
+ * from an install/build failure, and actionable when Node is truly absent.
9
+ */
10
+ export declare const MISSING_NODE_MESSAGE: string;
11
+ /** Options for {@link assertPackageDevTools}. */
12
+ export interface IAssertPackageDevToolsOpts {
13
+ /** When false, skip Node.js/npm checks (for clone-only/import-only flows). Defaults to true. */
14
+ requireNode?: boolean;
15
+ }
16
+ /**
17
+ * Verify that package create/clone prerequisites are on the host PATH.
18
+ *
19
+ * @param deps - Injected filesystem/shell host.
20
+ * @param opts - Optional Node.js requirement override.
21
+ * @throws When git, Node.js, or npm cannot be executed.
22
+ */
23
+ export declare function assertPackageDevTools(deps: IPackageInstallerDeps, opts?: IAssertPackageDevToolsOpts): Promise<void>;
24
+ /**
25
+ * Rewrite a late "command not found" failure into the same user-facing message.
26
+ *
27
+ * @param error - Error thrown by `shell.exec`.
28
+ * @returns The original error, or a clearer missing-tool error.
29
+ */
30
+ export declare function rewriteMissingDevToolError(error: unknown): Error;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MISSING_NODE_MESSAGE = exports.MISSING_GIT_MESSAGE = void 0;
4
+ exports.assertPackageDevTools = assertPackageDevTools;
5
+ exports.rewriteMissingDevToolError = rewriteMissingDevToolError;
6
+ /** User-facing error when `git` is missing from the host PATH. */
7
+ exports.MISSING_GIT_MESSAGE = "git is required to create and add packages. Install git, then quit and reopen Peers.";
8
+ /**
9
+ * User-facing error when Node.js or npm cannot be found.
10
+ *
11
+ * Keep this specific enough that a packaged-app PATH miss is distinguishable
12
+ * from an install/build failure, and actionable when Node is truly absent.
13
+ */
14
+ exports.MISSING_NODE_MESSAGE = "Node.js and npm are required to create and build packages. Peers couldn't find them on this computer.\n\n" +
15
+ "Install Node.js from https://nodejs.org (this includes npm), then quit and reopen Peers.\n\n" +
16
+ "If Node is already installed through nvm, Homebrew, or fnm, those tools must be available in your login shell.";
17
+ /**
18
+ * Verify that package create/clone prerequisites are on the host PATH.
19
+ *
20
+ * @param deps - Injected filesystem/shell host.
21
+ * @param opts - Optional Node.js requirement override.
22
+ * @throws When git, Node.js, or npm cannot be executed.
23
+ */
24
+ async function assertPackageDevTools(deps, opts) {
25
+ try {
26
+ await deps.shell.exec("git --version");
27
+ }
28
+ catch {
29
+ throw new Error(exports.MISSING_GIT_MESSAGE);
30
+ }
31
+ if (opts?.requireNode === false) {
32
+ return;
33
+ }
34
+ try {
35
+ await deps.shell.exec("node --version");
36
+ await deps.shell.exec("npm --version");
37
+ }
38
+ catch {
39
+ throw new Error(exports.MISSING_NODE_MESSAGE);
40
+ }
41
+ }
42
+ /**
43
+ * Rewrite a late "command not found" failure into the same user-facing message.
44
+ *
45
+ * @param error - Error thrown by `shell.exec`.
46
+ * @returns The original error, or a clearer missing-tool error.
47
+ */
48
+ function rewriteMissingDevToolError(error) {
49
+ const message = error instanceof Error ? error.message : String(error);
50
+ if (isCommandNotFound(message, "git")) {
51
+ return new Error(exports.MISSING_GIT_MESSAGE);
52
+ }
53
+ if (isCommandNotFound(message, "node") || isCommandNotFound(message, "npm")) {
54
+ return new Error(exports.MISSING_NODE_MESSAGE);
55
+ }
56
+ return error instanceof Error ? error : new Error(message);
57
+ }
58
+ function isCommandNotFound(message, command) {
59
+ const lower = message.toLowerCase();
60
+ return (lower.includes(`${command}: command not found`) ||
61
+ lower.includes(`'${command}' is not recognized`) ||
62
+ (lower.includes("enoent") && lower.includes(command)));
63
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const package_dev_tools_1 = require("./package-dev-tools");
4
+ function makeDeps(exec) {
5
+ return {
6
+ fs: {
7
+ readFile: jest.fn(),
8
+ writeFile: jest.fn(),
9
+ exists: jest.fn(),
10
+ readJson: jest.fn(),
11
+ },
12
+ shell: { exec },
13
+ resolvePath: (...segments) => segments.join("/"),
14
+ packagesRootDir: "/packages",
15
+ };
16
+ }
17
+ describe("assertPackageDevTools", () => {
18
+ it("requires git, node, and npm by default", async () => {
19
+ const exec = jest.fn(async () => ({ stdout: "ok", stderr: "" }));
20
+ await (0, package_dev_tools_1.assertPackageDevTools)(makeDeps(exec));
21
+ expect(exec.mock.calls.map((call) => call[0])).toEqual([
22
+ "git --version",
23
+ "node --version",
24
+ "npm --version",
25
+ ]);
26
+ });
27
+ it("skips Node.js checks when requireNode is false", async () => {
28
+ const exec = jest.fn(async () => ({ stdout: "ok", stderr: "" }));
29
+ await (0, package_dev_tools_1.assertPackageDevTools)(makeDeps(exec), { requireNode: false });
30
+ expect(exec.mock.calls.map((call) => call[0])).toEqual(["git --version"]);
31
+ });
32
+ it("throws a clear git error", async () => {
33
+ const exec = jest.fn(async (cmd) => {
34
+ if (cmd === "git --version") {
35
+ throw new Error("git: command not found");
36
+ }
37
+ return { stdout: "ok", stderr: "" };
38
+ });
39
+ await expect((0, package_dev_tools_1.assertPackageDevTools)(makeDeps(exec))).rejects.toThrow(package_dev_tools_1.MISSING_GIT_MESSAGE);
40
+ });
41
+ it("throws a clear Node.js/npm error", async () => {
42
+ const exec = jest.fn(async (cmd) => {
43
+ if (cmd === "npm --version") {
44
+ throw new Error("/bin/sh: npm: command not found");
45
+ }
46
+ return { stdout: "ok", stderr: "" };
47
+ });
48
+ await expect((0, package_dev_tools_1.assertPackageDevTools)(makeDeps(exec))).rejects.toThrow(package_dev_tools_1.MISSING_NODE_MESSAGE);
49
+ });
50
+ });
51
+ describe("rewriteMissingDevToolError", () => {
52
+ it("rewrites npm command-not-found failures", () => {
53
+ const rewritten = (0, package_dev_tools_1.rewriteMissingDevToolError)(new Error("Command failed: npm install\n/bin/sh: npm: command not found"));
54
+ expect(rewritten.message).toBe(package_dev_tools_1.MISSING_NODE_MESSAGE);
55
+ });
56
+ it("rewrites Windows npm-not-recognized failures", () => {
57
+ const rewritten = (0, package_dev_tools_1.rewriteMissingDevToolError)(new Error("'npm' is not recognized as an internal or external command"));
58
+ expect(rewritten.message).toBe(package_dev_tools_1.MISSING_NODE_MESSAGE);
59
+ });
60
+ it("leaves unrelated install failures unchanged", () => {
61
+ const original = new Error("npm ERR! network timeout");
62
+ expect((0, package_dev_tools_1.rewriteMissingDevToolError)(original)).toBe(original);
63
+ });
64
+ });
@@ -19,9 +19,8 @@ const tools_2 = require("../tools");
19
19
  async function installContractPackage(dataContext, packageDefinition, opts) {
20
20
  const { packageId } = packageDefinition;
21
21
  const registry = opts?.registry;
22
- // Register contracts in the in-memory registry
23
- if (registry) {
24
- for (const contract of packageDefinition.contracts) {
22
+ const registrations = registry
23
+ ? packageDefinition.contracts.map((contract) => {
25
24
  const key = `${contract.contractId}@${contract.version}`;
26
25
  const alsoImpl = packageDefinition.alsoImplements.get(key) ?? [];
27
26
  // If the contract is already frozen in the registry, re-register as
@@ -31,11 +30,14 @@ async function installContractPackage(dataContext, packageDefinition, opts) {
31
30
  if (existing && !existing.devTag && contract.devTag === "dev") {
32
31
  effectiveContract = { ...contract, devTag: undefined };
33
32
  }
34
- const result = registry.register(packageId, effectiveContract, alsoImpl);
35
- if (!result.valid) {
36
- console.error(`[ContractPackageLoader] Contract registration failed for ${contract.name}:`, result.errors);
37
- }
38
- }
33
+ return { definition: effectiveContract, alsoImplements: alsoImpl };
34
+ })
35
+ : undefined;
36
+ const validation = registrations ? registry?.validateBatch(packageId, registrations) : undefined;
37
+ if (validation && !validation.valid) {
38
+ throw new Error(`Atomic contract registration failed for package ${packageId}: ${validation.errors
39
+ .map((error) => error.message)
40
+ .join("; ")}`);
39
41
  }
40
42
  // Install tool instances with packageId
41
43
  for (const toolInstance of packageDefinition.toolInstances) {
@@ -55,6 +57,16 @@ async function installContractPackage(dataContext, packageDefinition, opts) {
55
57
  const assistantWithPackageId = { ...assistant, packageId };
56
58
  await Assistants(dataContext).save(assistantWithPackageId);
57
59
  }
60
+ // Activate the new provider only after all fallible package-owned persistence
61
+ // has succeeded, so a failed update leaves the previous provider routable.
62
+ if (registry && registrations) {
63
+ const result = registry.replaceBatch(packageId, registrations);
64
+ if (!result.valid) {
65
+ throw new Error(`Atomic contract registration failed for package ${packageId}: ${result.errors
66
+ .map((error) => error.message)
67
+ .join("; ")}`);
68
+ }
69
+ }
58
70
  }
59
71
  /**
60
72
  * Attempts to extract an `IPackageDefinitionResult` from a bundle's module exports.
@@ -66,6 +66,22 @@ describe("installContractPackage registry wiring", () => {
66
66
  const dataContext = mockDataContext();
67
67
  await expect((0, contract_package_loader_1.installContractPackage)(dataContext, makeDefinition())).resolves.toBeUndefined();
68
68
  });
69
+ it("keeps the previous provider active when package persistence fails", async () => {
70
+ const registry = new registry_1.ContractRegistry();
71
+ const previous = makeDefinition();
72
+ previous.contracts[0] = { ...previous.contracts[0], name: "Previous Contract" };
73
+ await (0, contract_package_loader_1.installContractPackage)(mockDataContext(), previous, { registry });
74
+ const dataContext = mockDataContext();
75
+ dataContext.tableContainer.registerTableDefinition.mockImplementation(() => {
76
+ throw new Error("table registration failed");
77
+ });
78
+ const replacement = makeDefinition({
79
+ tableDefinitions: [{}],
80
+ });
81
+ await expect((0, contract_package_loader_1.installContractPackage)(dataContext, replacement, { registry })).rejects.toThrow(/table registration failed/);
82
+ expect(registry.resolve(contractId, 1)?.name).toBe("Previous Contract");
83
+ expect(registry.getProviderPackageId(contractId, 1)).toBe(packageId);
84
+ });
69
85
  });
70
86
  describe("extractPackageDefinition", () => {
71
87
  it("returns a package definition when packageId and contracts are present", () => {
@@ -1,2 +1,6 @@
1
1
  export * from "./contract-package-loader";
2
+ export * from "./isolated-artifact";
3
+ export * from "./isolated-persistent-var-gateway";
4
+ export * from "./isolated-runtime";
5
+ export * from "./isolated-table-gateway";
2
6
  export * from "./package-loader";
@@ -15,4 +15,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./contract-package-loader"), exports);
18
+ __exportStar(require("./isolated-artifact"), exports);
19
+ __exportStar(require("./isolated-persistent-var-gateway"), exports);
20
+ __exportStar(require("./isolated-runtime"), exports);
21
+ __exportStar(require("./isolated-table-gateway"), exports);
18
22
  __exportStar(require("./package-loader"), exports);