@peers-app/peers-sdk 0.27.3 → 0.27.5
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.
- package/dist/contracts/contract-proxy.d.ts +7 -0
- package/dist/contracts/contract-proxy.js +19 -2
- package/dist/contracts/contract-transport.d.ts +2 -1
- package/dist/contracts/contract-transport.js +24 -2
- package/dist/contracts/create-local-consumer.js +16 -1
- package/dist/contracts/index.d.ts +2 -0
- package/dist/contracts/index.js +2 -0
- package/dist/contracts/system/index.d.ts +2 -0
- package/dist/contracts/system/index.js +2 -0
- package/dist/contracts/system/invites-contract.js +2 -1
- package/dist/contracts/system/sync-check-contract.d.ts +97 -0
- package/dist/contracts/system/sync-check-contract.js +115 -0
- package/dist/contracts/tool-arguments.d.ts +15 -0
- package/dist/contracts/tool-arguments.js +41 -0
- package/dist/contracts/tool-catalog-service.js +2 -12
- package/dist/contracts/tool-catalog.js +6 -2
- package/dist/data/change-tracking.d.ts +40 -0
- package/dist/data/change-tracking.js +89 -0
- package/dist/data/orm/types.d.ts +24 -7
- package/dist/data/orm/types.js +97 -24
- package/dist/define/manifest.js +6 -34
- package/dist/device/binary-peer-connection-v2.d.ts +7 -15
- package/dist/device/binary-peer-connection-v2.js +14 -30
- package/dist/device/connection.d.ts +34 -43
- package/dist/device/connection.js +188 -186
- package/dist/device/device.d.ts +1 -8
- package/dist/device/device.js +1 -30
- package/dist/device/socket.type.d.ts +6 -12
- package/dist/package-loader/package-loader.js +27 -22
- package/dist/system-ids.d.ts +5 -0
- package/dist/system-ids.js +6 -1
- package/package.json +4 -2
- package/dist/context/user-context-identity.test.d.ts +0 -1
- package/dist/context/user-context-identity.test.js +0 -220
- package/dist/contracts/__tests__/action-dispatch.test.d.ts +0 -1
- package/dist/contracts/__tests__/action-dispatch.test.js +0 -319
- package/dist/contracts/__tests__/also-implements.test.d.ts +0 -1
- package/dist/contracts/__tests__/also-implements.test.js +0 -110
- package/dist/contracts/__tests__/builder.test.d.ts +0 -1
- package/dist/contracts/__tests__/builder.test.js +0 -669
- package/dist/contracts/__tests__/contract-events.test.d.ts +0 -1
- package/dist/contracts/__tests__/contract-events.test.js +0 -199
- package/dist/contracts/__tests__/contract-observables.test.d.ts +0 -1
- package/dist/contracts/__tests__/contract-observables.test.js +0 -290
- package/dist/contracts/__tests__/contract-provider-router.test.d.ts +0 -1
- package/dist/contracts/__tests__/contract-provider-router.test.js +0 -315
- package/dist/contracts/__tests__/contract-proxy.test.d.ts +0 -1
- package/dist/contracts/__tests__/contract-proxy.test.js +0 -657
- package/dist/contracts/__tests__/contract-resolvers.test.d.ts +0 -1
- package/dist/contracts/__tests__/contract-resolvers.test.js +0 -758
- package/dist/contracts/__tests__/contract-subscription-lifecycle.test.d.ts +0 -1
- package/dist/contracts/__tests__/contract-subscription-lifecycle.test.js +0 -353
- package/dist/contracts/__tests__/contract-table-events.test.d.ts +0 -1
- package/dist/contracts/__tests__/contract-table-events.test.js +0 -209
- package/dist/contracts/__tests__/contract-transport.test.d.ts +0 -1
- package/dist/contracts/__tests__/contract-transport.test.js +0 -163
- package/dist/contracts/__tests__/extract.test.d.ts +0 -1
- package/dist/contracts/__tests__/extract.test.js +0 -145
- package/dist/contracts/__tests__/frozen-contracts.test.d.ts +0 -1
- package/dist/contracts/__tests__/frozen-contracts.test.js +0 -191
- package/dist/contracts/__tests__/generic-tool-dispatch.test.d.ts +0 -1
- package/dist/contracts/__tests__/generic-tool-dispatch.test.js +0 -130
- package/dist/contracts/__tests__/host-contract-client.test.d.ts +0 -1
- package/dist/contracts/__tests__/host-contract-client.test.js +0 -793
- package/dist/contracts/__tests__/host-contract-consumer.test.d.ts +0 -1
- package/dist/contracts/__tests__/host-contract-consumer.test.js +0 -163
- package/dist/contracts/__tests__/host-contract-transport.test.d.ts +0 -1
- package/dist/contracts/__tests__/host-contract-transport.test.js +0 -66
- package/dist/contracts/__tests__/integration.test.d.ts +0 -1
- package/dist/contracts/__tests__/integration.test.js +0 -348
- package/dist/contracts/__tests__/legacy-removal.test.d.ts +0 -1
- package/dist/contracts/__tests__/legacy-removal.test.js +0 -128
- package/dist/contracts/__tests__/persistent-registry.test.d.ts +0 -1
- package/dist/contracts/__tests__/persistent-registry.test.js +0 -109
- package/dist/contracts/__tests__/playground.test.d.ts +0 -1
- package/dist/contracts/__tests__/playground.test.js +0 -216
- package/dist/contracts/__tests__/registry.test.d.ts +0 -1
- package/dist/contracts/__tests__/registry.test.js +0 -238
- package/dist/contracts/__tests__/remote-contract-transport.test.d.ts +0 -1
- package/dist/contracts/__tests__/remote-contract-transport.test.js +0 -119
- package/dist/contracts/__tests__/system-contracts.test.d.ts +0 -1
- package/dist/contracts/__tests__/system-contracts.test.js +0 -247
- package/dist/contracts/__tests__/target-dispatch.d.ts +0 -11
- package/dist/contracts/__tests__/target-dispatch.js +0 -24
- package/dist/contracts/__tests__/tool-catalog-inventory.test.d.ts +0 -1
- package/dist/contracts/__tests__/tool-catalog-inventory.test.js +0 -44
- package/dist/contracts/__tests__/tool-catalog-service.test.d.ts +0 -1
- package/dist/contracts/__tests__/tool-catalog-service.test.js +0 -482
- package/dist/contracts/__tests__/tool-catalog.test.d.ts +0 -1
- package/dist/contracts/__tests__/tool-catalog.test.js +0 -1199
- package/dist/contracts/__tests__/validate.test.d.ts +0 -1
- package/dist/contracts/__tests__/validate.test.js +0 -991
- package/dist/contracts/__tests__/workflow-context.test.d.ts +0 -1
- package/dist/contracts/__tests__/workflow-context.test.js +0 -50
- package/dist/data/access-level.test.d.ts +0 -1
- package/dist/data/access-level.test.js +0 -53
- package/dist/data/change-tracking.test.d.ts +0 -1
- package/dist/data/change-tracking.test.js +0 -62
- package/dist/data/files/files.test.d.ts +0 -1
- package/dist/data/files/files.test.js +0 -770
- package/dist/data/group-members.test.d.ts +0 -1
- package/dist/data/group-members.test.js +0 -336
- package/dist/data/group-share.test.d.ts +0 -1
- package/dist/data/group-share.test.js +0 -110
- package/dist/data/groups.test.d.ts +0 -1
- package/dist/data/groups.test.js +0 -153
- package/dist/data/orm/cursor.test.d.ts +0 -1
- package/dist/data/orm/cursor.test.js +0 -314
- package/dist/data/orm/data-query.mongo.test.d.ts +0 -1
- package/dist/data/orm/data-query.mongo.test.js +0 -367
- package/dist/data/orm/data-query.sqlite.test.d.ts +0 -1
- package/dist/data/orm/data-query.sqlite.test.js +0 -365
- package/dist/data/orm/data-query.test.d.ts +0 -1
- package/dist/data/orm/data-query.test.js +0 -545
- package/dist/data/orm/dependency-injection.test.d.ts +0 -1
- package/dist/data/orm/dependency-injection.test.js +0 -171
- package/dist/data/orm/event-registry.test.d.ts +0 -1
- package/dist/data/orm/event-registry.test.js +0 -44
- package/dist/data/orm/multi-cursors.test.d.ts +0 -1
- package/dist/data/orm/multi-cursors.test.js +0 -463
- package/dist/data/orm/sql.data-source.test.d.ts +0 -1
- package/dist/data/orm/sql.data-source.test.js +0 -731
- package/dist/data/orm/table-container-events.test.d.ts +0 -1
- package/dist/data/orm/table-container-events.test.js +0 -101
- package/dist/data/orm/table-container.test.d.ts +0 -1
- package/dist/data/orm/table-container.test.js +0 -273
- package/dist/data/orm/table.event-source.test.d.ts +0 -1
- package/dist/data/orm/table.event-source.test.js +0 -331
- package/dist/data/orm/types.test.d.ts +0 -1
- package/dist/data/orm/types.test.js +0 -409
- package/dist/data/package-table-identity.test.d.ts +0 -1
- package/dist/data/package-table-identity.test.js +0 -46
- package/dist/data/package-version-permissions.test.d.ts +0 -1
- package/dist/data/package-version-permissions.test.js +0 -107
- package/dist/data/package-version-resolver.test.d.ts +0 -4
- package/dist/data/package-version-resolver.test.js +0 -543
- package/dist/data/package-versions.test.d.ts +0 -1
- package/dist/data/package-versions.test.js +0 -227
- package/dist/data/persistent-vars.conflict.test.d.ts +0 -1
- package/dist/data/persistent-vars.conflict.test.js +0 -126
- package/dist/data/persistent-vars.migration.test.d.ts +0 -1
- package/dist/data/persistent-vars.migration.test.js +0 -133
- package/dist/data/persistent-vars.package-scope.test.d.ts +0 -1
- package/dist/data/persistent-vars.package-scope.test.js +0 -27
- package/dist/data/sync-recovery-queue.test.d.ts +0 -1
- package/dist/data/sync-recovery-queue.test.js +0 -215
- package/dist/data/table-signing.test.d.ts +0 -1
- package/dist/data/table-signing.test.js +0 -118
- package/dist/data/tool-access.test.d.ts +0 -1
- package/dist/data/tool-access.test.js +0 -166
- package/dist/data/user-permissions.test.d.ts +0 -1
- package/dist/data/user-permissions.test.js +0 -305
- package/dist/data/user-trust-levels.test.d.ts +0 -1
- package/dist/data/user-trust-levels.test.js +0 -37
- package/dist/define/also-implements.test.d.ts +0 -1
- package/dist/define/also-implements.test.js +0 -123
- package/dist/define/declare-contract.test.d.ts +0 -1
- package/dist/define/declare-contract.test.js +0 -573
- package/dist/define/define.test.d.ts +0 -1
- package/dist/define/define.test.js +0 -1010
- package/dist/define/empty-fields.test.d.ts +0 -1
- package/dist/define/empty-fields.test.js +0 -55
- package/dist/define/events-observables.test.d.ts +0 -1
- package/dist/define/events-observables.test.js +0 -1013
- package/dist/define/serve-members.test.d.ts +0 -1
- package/dist/define/serve-members.test.js +0 -630
- package/dist/define/vars.test.d.ts +0 -1
- package/dist/define/vars.test.js +0 -39
- package/dist/device/binary-peer-connection-v2.test.d.ts +0 -1
- package/dist/device/binary-peer-connection-v2.test.js +0 -730
- package/dist/device/binary-peer-connection.d.ts +0 -58
- package/dist/device/binary-peer-connection.js +0 -516
- package/dist/device/binary-peer-connection.test.d.ts +0 -1
- package/dist/device/binary-peer-connection.test.js +0 -184
- package/dist/device/connection.test.d.ts +0 -1
- package/dist/device/connection.test.js +0 -613
- package/dist/device/device-election.test.d.ts +0 -1
- package/dist/device/device-election.test.js +0 -516
- package/dist/device/device.test.d.ts +0 -1
- package/dist/device/device.test.js +0 -243
- package/dist/device/frame-cipher.test.d.ts +0 -1
- package/dist/device/frame-cipher.test.js +0 -219
- package/dist/device/get-trust-level-fn.test.d.ts +0 -1
- package/dist/device/get-trust-level-fn.test.js +0 -685
- package/dist/device/streamed-socket.d.ts +0 -23
- package/dist/device/streamed-socket.js +0 -255
- package/dist/device/streamed-socket.test.d.ts +0 -1
- package/dist/device/streamed-socket.test.js +0 -216
- package/dist/device/tx-encoding.test.d.ts +0 -1
- package/dist/device/tx-encoding.test.js +0 -295
- package/dist/device-pairing/cpace.test.d.ts +0 -1
- package/dist/device-pairing/cpace.test.js +0 -116
- package/dist/device-pairing/device-pairing.test.d.ts +0 -1
- package/dist/device-pairing/device-pairing.test.js +0 -211
- package/dist/hlc.test.d.ts +0 -1
- package/dist/hlc.test.js +0 -45
- package/dist/host-credentials.test.d.ts +0 -1
- package/dist/host-credentials.test.js +0 -88
- package/dist/identity/account-email.test.d.ts +0 -1
- package/dist/identity/account-email.test.js +0 -59
- package/dist/identity/anchor-document.test.d.ts +0 -1
- package/dist/identity/anchor-document.test.js +0 -16
- package/dist/identity/anchor-resolution.test.d.ts +0 -1
- package/dist/identity/anchor-resolution.test.js +0 -130
- package/dist/identity/key-change.test.d.ts +0 -1
- package/dist/identity/key-change.test.js +0 -811
- package/dist/invites/group-approval.test.d.ts +0 -1
- package/dist/invites/group-approval.test.js +0 -223
- package/dist/invites/invite-token.test.d.ts +0 -1
- package/dist/invites/invite-token.test.js +0 -123
- package/dist/keys.test.d.ts +0 -1
- package/dist/keys.test.js +0 -336
- package/dist/logging/console-logger.test.d.ts +0 -1
- package/dist/logging/console-logger.test.js +0 -300
- package/dist/logging/console-logs.table.test.d.ts +0 -1
- package/dist/logging/console-logs.table.test.js +0 -156
- package/dist/observable.test.d.ts +0 -1
- package/dist/observable.test.js +0 -309
- package/dist/package-build/build-package.test.d.ts +0 -1
- package/dist/package-build/build-package.test.js +0 -204
- package/dist/package-build/contract-add.test.d.ts +0 -1
- package/dist/package-build/contract-add.test.js +0 -467
- package/dist/package-build/freeze-contracts.test.d.ts +0 -1
- package/dist/package-build/freeze-contracts.test.js +0 -355
- package/dist/package-installer/package-author-signing.test.d.ts +0 -1
- package/dist/package-installer/package-author-signing.test.js +0 -200
- package/dist/package-installer/package-cloner.test.d.ts +0 -1
- package/dist/package-installer/package-cloner.test.js +0 -278
- package/dist/package-installer/package-creator.test.d.ts +0 -1
- package/dist/package-installer/package-creator.test.js +0 -908
- package/dist/package-installer/package-dev-tools.test.d.ts +0 -1
- package/dist/package-installer/package-dev-tools.test.js +0 -64
- package/dist/package-installer/package-installer.test.d.ts +0 -1
- package/dist/package-installer/package-installer.test.js +0 -882
- package/dist/package-installer/package-propagation.test.d.ts +0 -1
- package/dist/package-installer/package-propagation.test.js +0 -1269
- package/dist/package-installer/package-publisher.test.d.ts +0 -1
- package/dist/package-installer/package-publisher.test.js +0 -153
- package/dist/package-installer/package-remote-checker.test.d.ts +0 -1
- package/dist/package-installer/package-remote-checker.test.js +0 -334
- package/dist/package-installer/package-seed-installer.test.d.ts +0 -1
- package/dist/package-installer/package-seed-installer.test.js +0 -123
- package/dist/package-installer/package-tarball.test.d.ts +0 -1
- package/dist/package-installer/package-tarball.test.js +0 -75
- package/dist/package-installer/peers-core-installer.test.d.ts +0 -1
- package/dist/package-installer/peers-core-installer.test.js +0 -49
- package/dist/package-installer/peers-core-trust-anchor.test.d.ts +0 -1
- package/dist/package-installer/peers-core-trust-anchor.test.js +0 -155
- package/dist/package-loader/contract-package-loader.test.d.ts +0 -1
- package/dist/package-loader/contract-package-loader.test.js +0 -274
- package/dist/package-loader/host-authority-loading.test.d.ts +0 -1
- package/dist/package-loader/host-authority-loading.test.js +0 -104
- package/dist/package-loader/host-dispatch-policy.test.d.ts +0 -1
- package/dist/package-loader/host-dispatch-policy.test.js +0 -56
- package/dist/package-loader/isolated-artifact.test.d.ts +0 -1
- package/dist/package-loader/isolated-artifact.test.js +0 -688
- package/dist/package-loader/isolated-persistent-var-gateway.test.d.ts +0 -1
- package/dist/package-loader/isolated-persistent-var-gateway.test.js +0 -56
- package/dist/package-loader/isolated-table-gateway.test.d.ts +0 -1
- package/dist/package-loader/isolated-table-gateway.test.js +0 -633
- package/dist/package-loader/package-loader.test.d.ts +0 -1
- package/dist/package-loader/package-loader.test.js +0 -1764
- package/dist/package-loader/resolve-consumed-contracts.test.d.ts +0 -1
- package/dist/package-loader/resolve-consumed-contracts.test.js +0 -166
- package/dist/serial-json.compat.test.d.ts +0 -1
- package/dist/serial-json.compat.test.js +0 -369
- package/dist/serial-json.test.d.ts +0 -1
- package/dist/serial-json.test.js +0 -401
- package/dist/testing/mock-host-contract.test.d.ts +0 -1
- package/dist/testing/mock-host-contract.test.js +0 -782
- package/dist/utils.test.d.ts +0 -1
- package/dist/utils.test.js +0 -232
|
@@ -1,1764 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const mockBeforeToolSave = jest.fn();
|
|
4
|
-
jest.mock("../data/tools", () => {
|
|
5
|
-
const actual = jest.requireActual("../data/tools");
|
|
6
|
-
const contexts = new WeakMap();
|
|
7
|
-
return {
|
|
8
|
-
...actual,
|
|
9
|
-
Tools: (context) => {
|
|
10
|
-
let rows = contexts.get(context);
|
|
11
|
-
if (!rows) {
|
|
12
|
-
rows = new Map();
|
|
13
|
-
contexts.set(context, rows);
|
|
14
|
-
}
|
|
15
|
-
const records = rows;
|
|
16
|
-
return {
|
|
17
|
-
list: async () => [...records.values()],
|
|
18
|
-
get: async (id) => records.get(id),
|
|
19
|
-
save: async (row, opts) => {
|
|
20
|
-
await mockBeforeToolSave(row);
|
|
21
|
-
if (!opts?.insertOnly || !records.has(row.toolId))
|
|
22
|
-
records.set(row.toolId, row);
|
|
23
|
-
return records.get(row.toolId);
|
|
24
|
-
},
|
|
25
|
-
update: async (patch) => {
|
|
26
|
-
const row = { ...records.get(patch.toolId), ...patch };
|
|
27
|
-
records.set(patch.toolId, row);
|
|
28
|
-
return row;
|
|
29
|
-
},
|
|
30
|
-
dataChanged: { subscribe: () => ({ unsubscribe: () => true }) },
|
|
31
|
-
};
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
});
|
|
35
|
-
const mockGet = jest.fn();
|
|
36
|
-
const mockSignAndSave = jest.fn();
|
|
37
|
-
const mockComputePackageVersionHash = jest.fn((version, _tag, packageBundleFileHash, _routesBundleFileHash, _uiBundleFileHash) => `hash-${version}-${packageBundleFileHash}`);
|
|
38
|
-
jest.mock("../data/package-versions", () => ({
|
|
39
|
-
PackageVersions: () => ({
|
|
40
|
-
get: (...args) => mockGet(...args),
|
|
41
|
-
signAndSave: (...args) => mockSignAndSave(...args),
|
|
42
|
-
}),
|
|
43
|
-
computePackageVersionHash: (version, versionTag, packageBundleFileHash, routesBundleFileHash, uiBundleFileHash) => mockComputePackageVersionHash(version, versionTag, packageBundleFileHash, routesBundleFileHash, uiBundleFileHash),
|
|
44
|
-
}));
|
|
45
|
-
const builder_1 = require("../contracts/builder");
|
|
46
|
-
const contract_provider_router_1 = require("../contracts/contract-provider-router");
|
|
47
|
-
const contract_proxy_1 = require("../contracts/contract-proxy");
|
|
48
|
-
const contract_resolvers_1 = require("../contracts/contract-resolvers");
|
|
49
|
-
const contract_transport_1 = require("../contracts/contract-transport");
|
|
50
|
-
const registry_1 = require("../contracts/registry");
|
|
51
|
-
const remote_contract_transport_1 = require("../contracts/remote-contract-transport");
|
|
52
|
-
const device_operations_contract_1 = require("../contracts/system/device-operations-contract");
|
|
53
|
-
const tool_catalog_identity_1 = require("../contracts/tool-catalog-identity");
|
|
54
|
-
const access_level_1 = require("../data/access-level");
|
|
55
|
-
const groupPermissions = require("../data/group-permissions");
|
|
56
|
-
const tools_1 = require("../data/tools");
|
|
57
|
-
const system_ids_1 = require("../system-ids");
|
|
58
|
-
const utils_1 = require("../utils");
|
|
59
|
-
const isolated_artifact_1 = require("./isolated-artifact");
|
|
60
|
-
const isolated_runtime_1 = require("./isolated-runtime");
|
|
61
|
-
const package_loader_1 = require("./package-loader");
|
|
62
|
-
const consumedContracts = require("./resolve-consumed-contracts");
|
|
63
|
-
test("legacy catalog projection follows the dispatcher's declarations and selected provider", async () => {
|
|
64
|
-
const context = { dataContextId: (0, utils_1.newid)() };
|
|
65
|
-
const loader = new package_loader_1.PackageLoader(context);
|
|
66
|
-
const caller = (0, utils_1.newid)();
|
|
67
|
-
const provider = (0, utils_1.newid)();
|
|
68
|
-
const other = (0, utils_1.newid)();
|
|
69
|
-
const contractId = (0, utils_1.newid)();
|
|
70
|
-
const definition = {
|
|
71
|
-
contractId,
|
|
72
|
-
version: 1,
|
|
73
|
-
name: "Target",
|
|
74
|
-
description: "",
|
|
75
|
-
tools: [],
|
|
76
|
-
tables: [],
|
|
77
|
-
observables: [],
|
|
78
|
-
events: [],
|
|
79
|
-
};
|
|
80
|
-
const principal = {
|
|
81
|
-
userId: (0, utils_1.newid)(),
|
|
82
|
-
userAccessLevel: 40,
|
|
83
|
-
callingToolAccessLevel: 20,
|
|
84
|
-
};
|
|
85
|
-
const target = { contractId, version: 1, providerPackageId: provider };
|
|
86
|
-
try {
|
|
87
|
-
await loader.catalogReady;
|
|
88
|
-
loader.contractRegistry.register(provider, definition);
|
|
89
|
-
loader.contractRegistry.register(other, definition);
|
|
90
|
-
expect(loader.hostToolPrincipal(principal, caller, target)).toBeUndefined();
|
|
91
|
-
loader.hostConsumes.set(caller, [
|
|
92
|
-
{ contractId, version: 1, providerPackageIds: [provider] },
|
|
93
|
-
]);
|
|
94
|
-
expect(loader.hostToolPrincipal(principal, caller, target)).toMatchObject({
|
|
95
|
-
assistantAccessLevel: 20,
|
|
96
|
-
strictActionScope: true,
|
|
97
|
-
});
|
|
98
|
-
expect(loader.hostToolPrincipal(principal, caller, { ...target, providerPackageId: other })).toBeUndefined();
|
|
99
|
-
expect(loader.hostToolPrincipal({ ...principal, userId: undefined }, caller, target)).toBeUndefined();
|
|
100
|
-
// A provider can reach its own contract without declaring a consume.
|
|
101
|
-
expect(loader.hostToolPrincipal(principal, provider, target)).toMatchObject({
|
|
102
|
-
assistantAccessLevel: 20,
|
|
103
|
-
});
|
|
104
|
-
expect(loader.hostToolPrincipal(principal, provider, { ...target, providerPackageId: other })).toBeUndefined();
|
|
105
|
-
}
|
|
106
|
-
finally {
|
|
107
|
-
await loader.dispose();
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
describe("host load invalidation", () => {
|
|
111
|
-
it("retains legacy package identity and refuses system calls before forwarding", async () => {
|
|
112
|
-
const packageId = (0, utils_1.newid)();
|
|
113
|
-
const context = { dataContextId: (0, utils_1.newid)() };
|
|
114
|
-
const loader = new package_loader_1.PackageLoader(context);
|
|
115
|
-
Object.assign(context, { packageLoader: loader });
|
|
116
|
-
const emit = jest.fn(async () => "must not execute");
|
|
117
|
-
(0, remote_contract_transport_1.setRemoteContractTransportFactory)(() => ({ emit, on: () => () => { } }));
|
|
118
|
-
const definition = (0, builder_1.definePackage)((pkg) => {
|
|
119
|
-
pkg.packageId = packageId;
|
|
120
|
-
pkg.version = "1.0.0";
|
|
121
|
-
pkg.remote(system_ids_1.deviceOperationsContractId, 1);
|
|
122
|
-
});
|
|
123
|
-
try {
|
|
124
|
-
await loader.catalogReady;
|
|
125
|
-
loader.contractRegistry.register(system_ids_1.peersSystemPackageId, (0, device_operations_contract_1.getDeviceOperationsContractDefinition)());
|
|
126
|
-
await consumedContracts.resolveConsumedContracts(context, loader.contractRegistry, definition);
|
|
127
|
-
const remote = (await definition.remotes?.[0].handle
|
|
128
|
-
?.consumer);
|
|
129
|
-
await expect(remote.device((0, utils_1.newid)()).tools.getDeviceStatus()).rejects.toThrow("packages cannot call system contract");
|
|
130
|
-
expect(emit).not.toHaveBeenCalled();
|
|
131
|
-
remote.dispose();
|
|
132
|
-
}
|
|
133
|
-
finally {
|
|
134
|
-
(0, remote_contract_transport_1.setRemoteContractTransportFactory)(undefined);
|
|
135
|
-
await loader.dispose();
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
it.each([
|
|
139
|
-
["contract", "quarantine"],
|
|
140
|
-
["contract", "unload"],
|
|
141
|
-
["legacy", "quarantine"],
|
|
142
|
-
["legacy", "unload"],
|
|
143
|
-
])("rejects %s tool invocation when %s completes during authorization", async (kind, action) => {
|
|
144
|
-
const packageId = (0, utils_1.newid)();
|
|
145
|
-
const contractId = (0, utils_1.newid)();
|
|
146
|
-
const context = { dataContextId: (0, utils_1.newid)() };
|
|
147
|
-
const loader = new package_loader_1.PackageLoader(context);
|
|
148
|
-
const toolFn = jest.fn(() => "must not execute");
|
|
149
|
-
loader.require = (() => {
|
|
150
|
-
const instance = {
|
|
151
|
-
tool: { name: "read", usageDescription: "Read", accessLevel: access_level_1.AccessLevel.Self },
|
|
152
|
-
toolFn,
|
|
153
|
-
};
|
|
154
|
-
(0, contract_resolvers_1.registerContractResolver)(contractId, 1, () => ({
|
|
155
|
-
contractId,
|
|
156
|
-
version: 1,
|
|
157
|
-
tools: { read: instance },
|
|
158
|
-
}));
|
|
159
|
-
return kind === "contract"
|
|
160
|
-
? (0, builder_1.definePackage)((pkg) => {
|
|
161
|
-
pkg.packageId = packageId;
|
|
162
|
-
pkg.version = "1.0.0";
|
|
163
|
-
pkg.contract(contractId, 1, "Authorization race").tools = [instance];
|
|
164
|
-
})
|
|
165
|
-
: { packageId };
|
|
166
|
-
});
|
|
167
|
-
let entered;
|
|
168
|
-
const waiting = new Promise((resolve) => {
|
|
169
|
-
entered = resolve;
|
|
170
|
-
});
|
|
171
|
-
let release;
|
|
172
|
-
const permission = jest
|
|
173
|
-
.spyOn(groupPermissions, "getUserAccessLevel")
|
|
174
|
-
.mockImplementationOnce(() => {
|
|
175
|
-
entered();
|
|
176
|
-
return new Promise((resolve) => {
|
|
177
|
-
release = resolve;
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
const warn = jest.spyOn(console, "warn").mockImplementation(() => { });
|
|
181
|
-
let endpoint;
|
|
182
|
-
try {
|
|
183
|
-
await loader._loadBundle({ packageId, name: "Authorization race" }, kind === "contract"
|
|
184
|
-
? 'module.exports.packageDefinition = require("fixture");'
|
|
185
|
-
: 'module.exports = require("fixture");', undefined, false, false);
|
|
186
|
-
// Legacy resolver-only bundles have no manifest to index; model an existing row.
|
|
187
|
-
const toolId = (0, tool_catalog_identity_1.deriveToolCatalogId)({ contractId, version: 1, toolName: "read" });
|
|
188
|
-
if (kind === "legacy")
|
|
189
|
-
await (0, tools_1.Tools)(context).save({
|
|
190
|
-
toolId,
|
|
191
|
-
name: "read",
|
|
192
|
-
code: "",
|
|
193
|
-
usageDescription: "Read",
|
|
194
|
-
inputSchema: { type: tools_1.IOSchemaType.none, fields: [] },
|
|
195
|
-
outputSchema: { type: tools_1.IOSchemaType.none, fields: [] },
|
|
196
|
-
contractId,
|
|
197
|
-
contractVersion: 1,
|
|
198
|
-
toolName: "read",
|
|
199
|
-
catalogIdentity: "deterministic",
|
|
200
|
-
catalogState: "resolved",
|
|
201
|
-
});
|
|
202
|
-
endpoint = await loader.resolveContractEndpoint({
|
|
203
|
-
contractId,
|
|
204
|
-
version: 1,
|
|
205
|
-
context: { dataContext: context, callerUserId: "self", localUserId: "self" },
|
|
206
|
-
notify: async () => undefined,
|
|
207
|
-
});
|
|
208
|
-
const call = endpoint.handleCall({
|
|
209
|
-
contractId,
|
|
210
|
-
version: 1,
|
|
211
|
-
member: "tool",
|
|
212
|
-
name: "read",
|
|
213
|
-
operation: "run",
|
|
214
|
-
args: [],
|
|
215
|
-
});
|
|
216
|
-
const rejection = expect(call).rejects.toThrow(action === "quarantine" ? /quarantined/i : /retired/);
|
|
217
|
-
await waiting;
|
|
218
|
-
if (action === "quarantine")
|
|
219
|
-
await (0, tools_1.Tools)(context).update({ toolId, catalogState: "quarantined" });
|
|
220
|
-
else
|
|
221
|
-
await loader.unloadPackage(packageId);
|
|
222
|
-
release(access_level_1.AccessLevel.Self);
|
|
223
|
-
await rejection;
|
|
224
|
-
expect(toolFn).not.toHaveBeenCalled();
|
|
225
|
-
}
|
|
226
|
-
finally {
|
|
227
|
-
release?.(access_level_1.AccessLevel.Self);
|
|
228
|
-
endpoint?.dispose();
|
|
229
|
-
permission.mockRestore();
|
|
230
|
-
warn.mockRestore();
|
|
231
|
-
await loader.dispose();
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
it("settles a fresh package's remote and consume handles after its own contract activates", async () => {
|
|
235
|
-
const packageId = (0, utils_1.newid)();
|
|
236
|
-
const contractId = (0, utils_1.newid)();
|
|
237
|
-
const context = { dataContextId: (0, utils_1.newid)() };
|
|
238
|
-
const loader = new package_loader_1.PackageLoader(context);
|
|
239
|
-
Object.assign(context, { packageLoader: loader });
|
|
240
|
-
let definition;
|
|
241
|
-
loader.require = (() => {
|
|
242
|
-
const instance = {
|
|
243
|
-
tool: { name: "read", usageDescription: "Read", accessLevel: access_level_1.AccessLevel.None },
|
|
244
|
-
toolFn: () => "own provider",
|
|
245
|
-
};
|
|
246
|
-
(0, contract_resolvers_1.registerContractResolver)(contractId, 1, () => ({
|
|
247
|
-
contractId,
|
|
248
|
-
version: 1,
|
|
249
|
-
tools: { read: instance },
|
|
250
|
-
}));
|
|
251
|
-
definition = (0, builder_1.definePackage)((pkg) => {
|
|
252
|
-
pkg.packageId = packageId;
|
|
253
|
-
pkg.version = "1.0.0";
|
|
254
|
-
pkg.contract(contractId, 1, "Own contract").tools = [instance];
|
|
255
|
-
pkg.remote(contractId, 1);
|
|
256
|
-
pkg.consumes(contractId, 1);
|
|
257
|
-
});
|
|
258
|
-
return definition;
|
|
259
|
-
});
|
|
260
|
-
const warn = jest.spyOn(console, "warn").mockImplementation(() => { });
|
|
261
|
-
try {
|
|
262
|
-
expect(loader.contractRegistry.getDefinition(contractId, 1)).toBeUndefined();
|
|
263
|
-
await expect(loader._loadBundle({ packageId, name: "Own contract" }, 'module.exports.packageDefinition = require("fixture");', undefined, false, false)).resolves.toMatchObject({ packageId });
|
|
264
|
-
const remoteHandle = definition.remotes?.[0].handle;
|
|
265
|
-
await expect(remoteHandle?.consumer).resolves.toMatchObject({ contractId, version: 1 });
|
|
266
|
-
const remote = (await remoteHandle?.consumer);
|
|
267
|
-
expect(typeof remote?.device((0, utils_1.newid)()).tools.read).toBe("function");
|
|
268
|
-
remote?.dispose();
|
|
269
|
-
const consumeHandle = definition.consumes[0].handle;
|
|
270
|
-
await expect(consumeHandle?.consumer).resolves.toMatchObject({ contractId, version: 1 });
|
|
271
|
-
const consumer = (await consumeHandle?.consumer);
|
|
272
|
-
await expect(consumer?.tools.read()).resolves.toBe("own provider");
|
|
273
|
-
consumer?.dispose();
|
|
274
|
-
}
|
|
275
|
-
finally {
|
|
276
|
-
warn.mockRestore();
|
|
277
|
-
await loader.dispose();
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
it.each([
|
|
281
|
-
"persistence",
|
|
282
|
-
"evaluation",
|
|
283
|
-
"invalidation",
|
|
284
|
-
])("stages host resolvers until activation and retains the old implementation on %s failure", async (failure) => {
|
|
285
|
-
const packageId = (0, utils_1.newid)();
|
|
286
|
-
const contractId = (0, utils_1.newid)();
|
|
287
|
-
const definition = {
|
|
288
|
-
packageId,
|
|
289
|
-
version: "1.0.0",
|
|
290
|
-
contracts: [
|
|
291
|
-
{
|
|
292
|
-
contractId,
|
|
293
|
-
version: 1,
|
|
294
|
-
name: "Host",
|
|
295
|
-
description: "",
|
|
296
|
-
devTag: "dev",
|
|
297
|
-
tables: [],
|
|
298
|
-
observables: [],
|
|
299
|
-
tools: [{ name: "read", usageDescription: "", inputFields: [], outputFields: [] }],
|
|
300
|
-
},
|
|
301
|
-
],
|
|
302
|
-
consumes: [],
|
|
303
|
-
alsoImplements: new Map(),
|
|
304
|
-
assistants: [],
|
|
305
|
-
appNavs: [],
|
|
306
|
-
tableDefinitions: [],
|
|
307
|
-
};
|
|
308
|
-
const context = { dataContextId: (0, utils_1.newid)() };
|
|
309
|
-
const otherContext = { dataContextId: (0, utils_1.newid)() };
|
|
310
|
-
const loader = new package_loader_1.PackageLoader(context);
|
|
311
|
-
const otherLoader = new package_loader_1.PackageLoader(otherContext);
|
|
312
|
-
let value = "OLD";
|
|
313
|
-
let failEvaluation = false;
|
|
314
|
-
let current = true;
|
|
315
|
-
const fixture = () => {
|
|
316
|
-
const captured = value;
|
|
317
|
-
(0, contract_resolvers_1.registerContractResolver)(contractId, 1, () => ({
|
|
318
|
-
contractId,
|
|
319
|
-
version: 1,
|
|
320
|
-
tools: {
|
|
321
|
-
read: {
|
|
322
|
-
tool: { name: "read", accessLevel: access_level_1.AccessLevel.None },
|
|
323
|
-
toolFn: () => captured,
|
|
324
|
-
},
|
|
325
|
-
},
|
|
326
|
-
}), { providerPackageId: packageId });
|
|
327
|
-
if (failEvaluation)
|
|
328
|
-
throw new Error("evaluation failed");
|
|
329
|
-
return definition;
|
|
330
|
-
};
|
|
331
|
-
loader.require = fixture;
|
|
332
|
-
otherLoader.require = fixture;
|
|
333
|
-
const load = (target = loader) => target._loadBundle({ packageId, name: "Host" }, 'module.exports.packageDefinition = require("fixture");', undefined, false, true, () => {
|
|
334
|
-
if (!current)
|
|
335
|
-
throw new Error("load invalidated");
|
|
336
|
-
});
|
|
337
|
-
const call = async (target = loader) => {
|
|
338
|
-
const endpoint = await target.resolveContractEndpoint({
|
|
339
|
-
contractId,
|
|
340
|
-
version: 1,
|
|
341
|
-
context: { callerUserId: "self", localUserId: "self" },
|
|
342
|
-
notify: async () => undefined,
|
|
343
|
-
});
|
|
344
|
-
try {
|
|
345
|
-
return await endpoint.handleCall({
|
|
346
|
-
contractId,
|
|
347
|
-
version: 1,
|
|
348
|
-
member: "tool",
|
|
349
|
-
name: "read",
|
|
350
|
-
operation: "run",
|
|
351
|
-
args: [],
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
finally {
|
|
355
|
-
endpoint.dispose();
|
|
356
|
-
}
|
|
357
|
-
};
|
|
358
|
-
const warn = jest.spyOn(console, "warn").mockImplementation(() => { });
|
|
359
|
-
let release = () => { };
|
|
360
|
-
let consumer;
|
|
361
|
-
let endpoint;
|
|
362
|
-
// External host transports own their routers, so loader-side invalidation
|
|
363
|
-
// cannot evict their cached endpoints directly.
|
|
364
|
-
const [clientTransport, serverTransport] = (0, contract_transport_1.inProcessTransportPair)();
|
|
365
|
-
const externalRouter = (0, contract_provider_router_1.createContractProviderRouter)(serverTransport, {
|
|
366
|
-
authorizeCall: () => ({ context: { dataContext: context } }),
|
|
367
|
-
resolveProviderPackageId: () => packageId,
|
|
368
|
-
resolveEndpoint: (request) => loader.resolveContractEndpoint(request),
|
|
369
|
-
});
|
|
370
|
-
const externalConsumer = (0, contract_proxy_1.createContractConsumer)(definition.contracts[0], clientTransport);
|
|
371
|
-
try {
|
|
372
|
-
await load();
|
|
373
|
-
consumer = await loader.resolveConsumedContract({ contractId, version: 1 }, (0, utils_1.newid)());
|
|
374
|
-
expect(await consumer?.tools.read()).toBe("OLD");
|
|
375
|
-
expect(await externalConsumer.tools.read()).toBe("OLD");
|
|
376
|
-
endpoint = await loader.resolveContractEndpoint({
|
|
377
|
-
contractId,
|
|
378
|
-
version: 1,
|
|
379
|
-
context: {},
|
|
380
|
-
notify: async () => undefined,
|
|
381
|
-
});
|
|
382
|
-
expect(await call()).toBe("OLD");
|
|
383
|
-
value = "OTHER CONTEXT";
|
|
384
|
-
await load(otherLoader);
|
|
385
|
-
expect(await call()).toBe("OLD");
|
|
386
|
-
expect(await call(otherLoader)).toBe("OTHER CONTEXT");
|
|
387
|
-
value = "NEW";
|
|
388
|
-
let entered;
|
|
389
|
-
const waiting = new Promise((resolve) => {
|
|
390
|
-
entered = resolve;
|
|
391
|
-
});
|
|
392
|
-
const gate = new Promise((resolve) => {
|
|
393
|
-
release = resolve;
|
|
394
|
-
});
|
|
395
|
-
if (failure === "evaluation")
|
|
396
|
-
failEvaluation = true;
|
|
397
|
-
else
|
|
398
|
-
mockBeforeToolSave.mockImplementationOnce(async () => {
|
|
399
|
-
entered();
|
|
400
|
-
await gate;
|
|
401
|
-
if (failure === "persistence")
|
|
402
|
-
throw new Error("catalog disk failure");
|
|
403
|
-
current = false;
|
|
404
|
-
});
|
|
405
|
-
const loading = load();
|
|
406
|
-
if (failure !== "evaluation") {
|
|
407
|
-
await waiting;
|
|
408
|
-
// Candidate resolver registrations are invisible even while persistence waits.
|
|
409
|
-
expect(await call()).toBe("OLD");
|
|
410
|
-
release();
|
|
411
|
-
}
|
|
412
|
-
expect(await loading).toBeUndefined();
|
|
413
|
-
expect(await consumer?.tools.read()).toBe("OLD");
|
|
414
|
-
expect(await call()).toBe("OLD");
|
|
415
|
-
expect(await call(otherLoader)).toBe("OTHER CONTEXT");
|
|
416
|
-
expect((0, contract_resolvers_1.getContractResolver)(contractId, 1, packageId)).toBeUndefined();
|
|
417
|
-
current = true;
|
|
418
|
-
failEvaluation = false;
|
|
419
|
-
mockBeforeToolSave.mockReset();
|
|
420
|
-
await load();
|
|
421
|
-
expect(await call()).toBe("NEW");
|
|
422
|
-
expect(await consumer?.tools.read()).toBe("NEW");
|
|
423
|
-
expect(await externalConsumer.tools.read()).toBe("NEW");
|
|
424
|
-
await expect(endpoint.handleCall({
|
|
425
|
-
contractId,
|
|
426
|
-
version: 1,
|
|
427
|
-
member: "tool",
|
|
428
|
-
name: "read",
|
|
429
|
-
operation: "run",
|
|
430
|
-
args: [],
|
|
431
|
-
})).rejects.toThrow(/retired/);
|
|
432
|
-
await loader.unloadPackage(packageId);
|
|
433
|
-
await expect(consumer?.tools.read()).rejects.toThrow(/not a registered provider/);
|
|
434
|
-
await expect(externalConsumer.tools.read()).rejects.toThrow(/not a registered provider/);
|
|
435
|
-
expect((0, contract_resolvers_1.getContractResolver)(contractId, 1, packageId, context)).toBeUndefined();
|
|
436
|
-
expect(await call(otherLoader)).toBe("OTHER CONTEXT");
|
|
437
|
-
}
|
|
438
|
-
finally {
|
|
439
|
-
release();
|
|
440
|
-
consumer?.dispose();
|
|
441
|
-
externalConsumer.dispose();
|
|
442
|
-
externalRouter.dispose();
|
|
443
|
-
endpoint?.dispose();
|
|
444
|
-
mockBeforeToolSave.mockReset();
|
|
445
|
-
warn.mockRestore();
|
|
446
|
-
await loader.dispose();
|
|
447
|
-
await otherLoader.dispose();
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
it("retires cached legacy endpoints even when a bundle reuses its resolver function", async () => {
|
|
451
|
-
const packageId = (0, utils_1.newid)();
|
|
452
|
-
const contractId = (0, utils_1.newid)();
|
|
453
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: (0, utils_1.newid)() });
|
|
454
|
-
let value = "OLD";
|
|
455
|
-
const resolver = () => {
|
|
456
|
-
const captured = value;
|
|
457
|
-
return {
|
|
458
|
-
contractId,
|
|
459
|
-
version: 1,
|
|
460
|
-
tools: {
|
|
461
|
-
read: {
|
|
462
|
-
tool: { name: "read", accessLevel: access_level_1.AccessLevel.None },
|
|
463
|
-
toolFn: () => captured,
|
|
464
|
-
},
|
|
465
|
-
},
|
|
466
|
-
};
|
|
467
|
-
};
|
|
468
|
-
loader.require = (() => {
|
|
469
|
-
(0, contract_resolvers_1.registerContractResolver)(contractId, 1, resolver);
|
|
470
|
-
return { packageId };
|
|
471
|
-
});
|
|
472
|
-
const load = () => loader._loadBundle({ packageId, name: "legacy" }, 'module.exports = require("fixture");', undefined, false, true);
|
|
473
|
-
const resolve = () => loader.resolveContractEndpoint({
|
|
474
|
-
contractId,
|
|
475
|
-
version: 1,
|
|
476
|
-
context: {},
|
|
477
|
-
notify: async () => undefined,
|
|
478
|
-
});
|
|
479
|
-
const call = (endpoint) => endpoint.handleCall({
|
|
480
|
-
contractId,
|
|
481
|
-
version: 1,
|
|
482
|
-
member: "tool",
|
|
483
|
-
name: "read",
|
|
484
|
-
operation: "run",
|
|
485
|
-
args: [],
|
|
486
|
-
});
|
|
487
|
-
const endpoints = [];
|
|
488
|
-
const warn = jest.spyOn(console, "warn").mockImplementation(() => { });
|
|
489
|
-
let release = () => { };
|
|
490
|
-
try {
|
|
491
|
-
await load();
|
|
492
|
-
const old = await resolve();
|
|
493
|
-
endpoints.push(old);
|
|
494
|
-
expect(await call(old)).toBe("OLD");
|
|
495
|
-
value = "NEW";
|
|
496
|
-
await load();
|
|
497
|
-
await expect(call(old)).rejects.toThrow(/retired/);
|
|
498
|
-
const current = await resolve();
|
|
499
|
-
endpoints.push(current);
|
|
500
|
-
expect(await call(current)).toBe("NEW");
|
|
501
|
-
let entered;
|
|
502
|
-
const waiting = new Promise((resolve) => {
|
|
503
|
-
entered = resolve;
|
|
504
|
-
});
|
|
505
|
-
const gate = new Promise((resolve) => {
|
|
506
|
-
release = resolve;
|
|
507
|
-
});
|
|
508
|
-
const assertTarget = loader.toolCatalog.assertTargetUsable.bind(loader.toolCatalog);
|
|
509
|
-
jest
|
|
510
|
-
.spyOn(loader.toolCatalog, "assertTargetUsable")
|
|
511
|
-
.mockImplementationOnce(async (target) => {
|
|
512
|
-
await assertTarget(target);
|
|
513
|
-
entered();
|
|
514
|
-
await gate;
|
|
515
|
-
});
|
|
516
|
-
const pending = call(current);
|
|
517
|
-
const rejected = expect(pending).rejects.toThrow(/retired/);
|
|
518
|
-
await waiting;
|
|
519
|
-
await loader.unloadPackage(packageId);
|
|
520
|
-
release();
|
|
521
|
-
await rejected;
|
|
522
|
-
await expect(call(current)).rejects.toThrow(/retired/);
|
|
523
|
-
}
|
|
524
|
-
finally {
|
|
525
|
-
release();
|
|
526
|
-
for (const endpoint of endpoints)
|
|
527
|
-
endpoint.dispose();
|
|
528
|
-
await loader.dispose();
|
|
529
|
-
warn.mockRestore();
|
|
530
|
-
}
|
|
531
|
-
});
|
|
532
|
-
it("loads a tool-free legacy bundle and rejects executable replacement without side effects", async () => {
|
|
533
|
-
const packageId = (0, utils_1.newid)();
|
|
534
|
-
const contractId = (0, utils_1.newid)();
|
|
535
|
-
const context = {
|
|
536
|
-
dataContextId: (0, utils_1.newid)(),
|
|
537
|
-
tableContainer: { registerTableDefinitions: jest.fn() },
|
|
538
|
-
};
|
|
539
|
-
const loader = new package_loader_1.PackageLoader(context);
|
|
540
|
-
const original = { packageId, appNavs: [] };
|
|
541
|
-
let bundle = original;
|
|
542
|
-
loader.require = ((id) => id === "@peers-app/peers-sdk"
|
|
543
|
-
? { registerContractResolver: contract_resolvers_1.registerContractResolver }
|
|
544
|
-
: bundle);
|
|
545
|
-
const load = (code = 'module.exports = require("fixture");') => loader._loadBundle({ packageId, name: "legacy" }, code, undefined, false, true);
|
|
546
|
-
const warn = jest.spyOn(console, "warn").mockImplementation(() => { });
|
|
547
|
-
try {
|
|
548
|
-
expect(await load()).toBe(original);
|
|
549
|
-
const before = await (0, tools_1.Tools)(context).list();
|
|
550
|
-
bundle = {
|
|
551
|
-
packageId,
|
|
552
|
-
tableDefinitions: [{}],
|
|
553
|
-
toolInstances: [{ tool: { toolId: (0, utils_1.newid)() }, toolFn: jest.fn() }],
|
|
554
|
-
};
|
|
555
|
-
expect(await load(`require("@peers-app/peers-sdk").registerContractResolver("${contractId}", 1, () => ({})); module.exports = require("fixture");`)).toBeUndefined();
|
|
556
|
-
expect(warn).toHaveBeenCalledWith(expect.stringContaining("Failed to evaluate"), expect.stringContaining("ships legacy executable tools"));
|
|
557
|
-
expect(await (0, tools_1.Tools)(context).list()).toEqual(before);
|
|
558
|
-
expect(context.tableContainer.registerTableDefinitions).not.toHaveBeenCalled();
|
|
559
|
-
expect((0, contract_resolvers_1.getContractResolver)(contractId, 1, packageId, context)).toBeUndefined();
|
|
560
|
-
expect(loader.packageInstances[packageId]).toBe(original);
|
|
561
|
-
}
|
|
562
|
-
finally {
|
|
563
|
-
warn.mockRestore();
|
|
564
|
-
await loader.dispose();
|
|
565
|
-
}
|
|
566
|
-
});
|
|
567
|
-
it.each([
|
|
568
|
-
"dependencies",
|
|
569
|
-
"preparation",
|
|
570
|
-
])("unload invalidates a host load waiting on %s", async (boundary) => {
|
|
571
|
-
const packageId = (0, utils_1.newid)();
|
|
572
|
-
const contractId = (0, utils_1.newid)();
|
|
573
|
-
const context = {
|
|
574
|
-
dataContextId: (0, utils_1.newid)(),
|
|
575
|
-
tableContainer: { registerTableDefinition: jest.fn() },
|
|
576
|
-
};
|
|
577
|
-
const loader = new package_loader_1.PackageLoader(context);
|
|
578
|
-
const definition = {
|
|
579
|
-
packageId,
|
|
580
|
-
version: "1.0.0",
|
|
581
|
-
contracts: [
|
|
582
|
-
{
|
|
583
|
-
contractId,
|
|
584
|
-
version: 1,
|
|
585
|
-
name: "Pending",
|
|
586
|
-
description: "",
|
|
587
|
-
tables: [],
|
|
588
|
-
observables: [],
|
|
589
|
-
tools: [],
|
|
590
|
-
},
|
|
591
|
-
],
|
|
592
|
-
consumes: [],
|
|
593
|
-
alsoImplements: new Map(),
|
|
594
|
-
assistants: [],
|
|
595
|
-
appNavs: [],
|
|
596
|
-
tableDefinitions: [],
|
|
597
|
-
};
|
|
598
|
-
loader.require = (() => definition);
|
|
599
|
-
let release;
|
|
600
|
-
let entered;
|
|
601
|
-
const waiting = new Promise((resolve) => {
|
|
602
|
-
entered = resolve;
|
|
603
|
-
});
|
|
604
|
-
const gate = new Promise((resolve) => {
|
|
605
|
-
release = resolve;
|
|
606
|
-
});
|
|
607
|
-
const hold = async () => {
|
|
608
|
-
entered();
|
|
609
|
-
await gate;
|
|
610
|
-
};
|
|
611
|
-
const dependencySpy = jest
|
|
612
|
-
.spyOn(consumedContracts, "resolveConsumedContracts")
|
|
613
|
-
.mockResolvedValue(undefined);
|
|
614
|
-
const preparationSpy = jest
|
|
615
|
-
.spyOn(loader, "correctPackageVersion")
|
|
616
|
-
.mockResolvedValue(undefined);
|
|
617
|
-
if (boundary === "dependencies")
|
|
618
|
-
dependencySpy.mockImplementationOnce(hold);
|
|
619
|
-
else
|
|
620
|
-
preparationSpy.mockImplementationOnce(hold);
|
|
621
|
-
const warn = jest.spyOn(console, "warn").mockImplementation(() => { });
|
|
622
|
-
const load = () => loader._loadBundle({ packageId, name: "Pending" }, 'module.exports.packageDefinition = require("fixture");', undefined, false, false);
|
|
623
|
-
try {
|
|
624
|
-
const installing = load();
|
|
625
|
-
await waiting;
|
|
626
|
-
const removing = loader.unloadPackage(packageId);
|
|
627
|
-
if (boundary === "dependencies")
|
|
628
|
-
await removing;
|
|
629
|
-
release();
|
|
630
|
-
await Promise.all([installing, removing]);
|
|
631
|
-
expect(loader.contractRegistry.getProviderPackageId(contractId, 1)).toBeUndefined();
|
|
632
|
-
expect(loader.isContractInstalled(packageId)).toBe(false);
|
|
633
|
-
await load();
|
|
634
|
-
expect(loader.contractRegistry.getProviderPackageId(contractId, 1)).toBe(packageId);
|
|
635
|
-
}
|
|
636
|
-
finally {
|
|
637
|
-
release();
|
|
638
|
-
dependencySpy.mockRestore();
|
|
639
|
-
preparationSpy.mockRestore();
|
|
640
|
-
warn.mockRestore();
|
|
641
|
-
await loader.dispose();
|
|
642
|
-
}
|
|
643
|
-
});
|
|
644
|
-
});
|
|
645
|
-
describe("PackageLoader.correctPackageVersion", () => {
|
|
646
|
-
const activePvId = "pv-active";
|
|
647
|
-
const pkg = {
|
|
648
|
-
packageId: "pkg-1",
|
|
649
|
-
name: "Test Package",
|
|
650
|
-
activePackageVersionId: activePvId,
|
|
651
|
-
};
|
|
652
|
-
const def = { version: "0.3.6" };
|
|
653
|
-
let loader;
|
|
654
|
-
beforeEach(() => {
|
|
655
|
-
jest.clearAllMocks();
|
|
656
|
-
mockSignAndSave.mockResolvedValue(undefined);
|
|
657
|
-
loader = new package_loader_1.PackageLoader({ dataContextId: "ctx" });
|
|
658
|
-
});
|
|
659
|
-
async function correctPackageVersion(selectedPackageVersion) {
|
|
660
|
-
await loader.correctPackageVersion(pkg, selectedPackageVersion, def.version);
|
|
661
|
-
}
|
|
662
|
-
it("updates the active PV hash and clears a stale author signature", async () => {
|
|
663
|
-
await correctPackageVersion({
|
|
664
|
-
packageVersionId: activePvId,
|
|
665
|
-
version: "0.3.1",
|
|
666
|
-
versionTag: "dev",
|
|
667
|
-
packageBundleFileHash: "bundle-hash",
|
|
668
|
-
routesBundleFileHash: "routes-hash",
|
|
669
|
-
uiBundleFileHash: "ui-hash",
|
|
670
|
-
packageAuthorSignature: "stale-sig",
|
|
671
|
-
packageVersionHash: "old-hash",
|
|
672
|
-
});
|
|
673
|
-
expect(mockComputePackageVersionHash).toHaveBeenCalledWith("0.3.6", "dev", "bundle-hash", "routes-hash", "ui-hash");
|
|
674
|
-
expect(mockSignAndSave).toHaveBeenCalledWith(expect.objectContaining({
|
|
675
|
-
packageVersionId: activePvId,
|
|
676
|
-
version: "0.3.6",
|
|
677
|
-
versionTag: "dev",
|
|
678
|
-
packageVersionHash: "hash-0.3.6-bundle-hash",
|
|
679
|
-
}));
|
|
680
|
-
expect(mockSignAndSave.mock.calls[0][0].packageAuthorSignature).toBeUndefined();
|
|
681
|
-
});
|
|
682
|
-
it("does not update the active PV when it is stable", async () => {
|
|
683
|
-
await correctPackageVersion({
|
|
684
|
-
packageVersionId: activePvId,
|
|
685
|
-
version: "0.3.5",
|
|
686
|
-
versionTag: "stable",
|
|
687
|
-
});
|
|
688
|
-
expect(mockSignAndSave).not.toHaveBeenCalled();
|
|
689
|
-
});
|
|
690
|
-
it("does not update the active PV when it is beta", async () => {
|
|
691
|
-
await correctPackageVersion({
|
|
692
|
-
packageVersionId: activePvId,
|
|
693
|
-
version: "0.3.5",
|
|
694
|
-
versionTag: "beta",
|
|
695
|
-
});
|
|
696
|
-
expect(mockSignAndSave).not.toHaveBeenCalled();
|
|
697
|
-
});
|
|
698
|
-
it("does nothing when the active PV version already matches", async () => {
|
|
699
|
-
await correctPackageVersion({
|
|
700
|
-
packageVersionId: activePvId,
|
|
701
|
-
version: "0.3.6",
|
|
702
|
-
versionTag: "dev",
|
|
703
|
-
});
|
|
704
|
-
expect(mockSignAndSave).not.toHaveBeenCalled();
|
|
705
|
-
});
|
|
706
|
-
});
|
|
707
|
-
describe("PackageLoader.contractRegistry", () => {
|
|
708
|
-
it("owns an in-memory ContractRegistry per loader instance", () => {
|
|
709
|
-
const loaderA = new package_loader_1.PackageLoader({ dataContextId: "ctx-a" });
|
|
710
|
-
const loaderB = new package_loader_1.PackageLoader({ dataContextId: "ctx-b" });
|
|
711
|
-
expect(loaderA.contractRegistry).toBeInstanceOf(registry_1.ContractRegistry);
|
|
712
|
-
expect(loaderB.contractRegistry).toBeInstanceOf(registry_1.ContractRegistry);
|
|
713
|
-
expect(loaderA.contractRegistry).not.toBe(loaderB.contractRegistry);
|
|
714
|
-
});
|
|
715
|
-
it("installs system contracts after explicit readiness", async () => {
|
|
716
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-system" });
|
|
717
|
-
expect(loader.contractRegistry.resolve(system_ids_1.logsContractId, 1)).toBeUndefined();
|
|
718
|
-
await loader.catalogReady;
|
|
719
|
-
expect(loader.contractRegistry.getProviderPackageId(system_ids_1.logsContractId, 1)).toBe(system_ids_1.peersSystemPackageId);
|
|
720
|
-
expect(loader.contractRegistry.resolve(system_ids_1.logsContractId, 1)?.name).toBe("System Logs");
|
|
721
|
-
expect(loader.contractRegistry.getProviderPackageId(system_ids_1.deviceOperationsContractId, 1)).toBe(system_ids_1.peersSystemPackageId);
|
|
722
|
-
});
|
|
723
|
-
it("resolves the registry-selected provider through its scoped resolver", async () => {
|
|
724
|
-
const dataContext = { dataContextId: "ctx-selected-provider" };
|
|
725
|
-
const loader = new package_loader_1.PackageLoader(dataContext);
|
|
726
|
-
const contractId = (0, utils_1.newid)();
|
|
727
|
-
const providerA = (0, utils_1.newid)();
|
|
728
|
-
const providerB = (0, utils_1.newid)();
|
|
729
|
-
const definition = {
|
|
730
|
-
contractId,
|
|
731
|
-
version: 1,
|
|
732
|
-
devTag: "dev",
|
|
733
|
-
name: "Selected Provider",
|
|
734
|
-
description: "test",
|
|
735
|
-
tables: [],
|
|
736
|
-
tools: [],
|
|
737
|
-
observables: [],
|
|
738
|
-
events: [],
|
|
739
|
-
};
|
|
740
|
-
loader.contractRegistry.register(providerA, definition);
|
|
741
|
-
loader.contractRegistry.register(providerB, definition);
|
|
742
|
-
loader.contractRegistry.setActiveProvider(contractId, 1, providerB);
|
|
743
|
-
const resolverA = jest.fn(() => ({ contractId, version: 1 }));
|
|
744
|
-
const resolverB = jest.fn(() => ({ contractId, version: 1 }));
|
|
745
|
-
(0, contract_resolvers_1.registerContractResolver)(contractId, 1, resolverA, { providerPackageId: providerA });
|
|
746
|
-
(0, contract_resolvers_1.registerContractResolver)(contractId, 1, resolverB, { providerPackageId: providerB });
|
|
747
|
-
const endpoint = await loader.resolveContractEndpoint({
|
|
748
|
-
contractId,
|
|
749
|
-
version: 1,
|
|
750
|
-
context: {},
|
|
751
|
-
notify: async () => undefined,
|
|
752
|
-
});
|
|
753
|
-
expect(resolverA).not.toHaveBeenCalled();
|
|
754
|
-
expect(resolverB).toHaveBeenCalledWith({ dataContext });
|
|
755
|
-
endpoint.dispose();
|
|
756
|
-
});
|
|
757
|
-
it("preserves legacy unscoped resolvers without a registry provider", async () => {
|
|
758
|
-
const dataContext = { dataContextId: "ctx-legacy-resolver" };
|
|
759
|
-
const loader = new package_loader_1.PackageLoader(dataContext);
|
|
760
|
-
const contractId = (0, utils_1.newid)();
|
|
761
|
-
const resolver = jest.fn(() => ({ contractId, version: 1 }));
|
|
762
|
-
(0, contract_resolvers_1.registerContractResolver)(contractId, 1, resolver);
|
|
763
|
-
const endpoint = await loader.resolveContractEndpoint({
|
|
764
|
-
contractId,
|
|
765
|
-
version: 1,
|
|
766
|
-
context: {},
|
|
767
|
-
notify: async () => undefined,
|
|
768
|
-
});
|
|
769
|
-
expect(resolver).toHaveBeenCalledWith({ dataContext });
|
|
770
|
-
endpoint.dispose();
|
|
771
|
-
});
|
|
772
|
-
it("checks retained identity evidence even for an unscoped legacy resolver", async () => {
|
|
773
|
-
const dataContext = { dataContextId: "ctx-legacy-quarantine" };
|
|
774
|
-
const loader = new package_loader_1.PackageLoader(dataContext);
|
|
775
|
-
const contractId = (0, utils_1.newid)();
|
|
776
|
-
const toolFn = jest.fn();
|
|
777
|
-
(0, contract_resolvers_1.registerContractResolver)(contractId, 1, () => ({
|
|
778
|
-
contractId,
|
|
779
|
-
version: 1,
|
|
780
|
-
tools: { read: { toolFn, tool: {} } },
|
|
781
|
-
}));
|
|
782
|
-
const endpoint = await loader.resolveContractEndpoint({
|
|
783
|
-
contractId,
|
|
784
|
-
version: 1,
|
|
785
|
-
context: {},
|
|
786
|
-
notify: async () => undefined,
|
|
787
|
-
});
|
|
788
|
-
const toolId = (0, tool_catalog_identity_1.deriveToolCatalogId)({ contractId, version: 1, toolName: "read" });
|
|
789
|
-
await (0, tools_1.Tools)(dataContext).save({
|
|
790
|
-
toolId,
|
|
791
|
-
contractId,
|
|
792
|
-
contractVersion: 1,
|
|
793
|
-
toolName: "read",
|
|
794
|
-
name: "read",
|
|
795
|
-
usageDescription: "",
|
|
796
|
-
code: "",
|
|
797
|
-
inputSchema: { type: tools_1.IOSchemaType.none, fields: [] },
|
|
798
|
-
outputSchema: { type: tools_1.IOSchemaType.none, fields: [] },
|
|
799
|
-
catalogState: "quarantined",
|
|
800
|
-
});
|
|
801
|
-
try {
|
|
802
|
-
await expect(endpoint.handleCall({
|
|
803
|
-
contractId,
|
|
804
|
-
version: 1,
|
|
805
|
-
member: "tool",
|
|
806
|
-
name: "read",
|
|
807
|
-
operation: "run",
|
|
808
|
-
args: [],
|
|
809
|
-
})).rejects.toThrow(/Quarantined/);
|
|
810
|
-
expect(toolFn).not.toHaveBeenCalled();
|
|
811
|
-
}
|
|
812
|
-
finally {
|
|
813
|
-
endpoint.dispose();
|
|
814
|
-
await loader.dispose();
|
|
815
|
-
}
|
|
816
|
-
});
|
|
817
|
-
it("does not supply local identity to ordinary callers that omit it", async () => {
|
|
818
|
-
const userId = (0, utils_1.newid)();
|
|
819
|
-
const dataContext = {
|
|
820
|
-
dataContextId: userId,
|
|
821
|
-
userContext: { userId, deviceId: () => "local-device" },
|
|
822
|
-
};
|
|
823
|
-
const loader = new package_loader_1.PackageLoader(dataContext);
|
|
824
|
-
const contractId = (0, utils_1.newid)();
|
|
825
|
-
const write = jest.fn();
|
|
826
|
-
(0, contract_resolvers_1.registerContractResolver)(contractId, 1, () => ({
|
|
827
|
-
contractId,
|
|
828
|
-
version: 1,
|
|
829
|
-
tools: {
|
|
830
|
-
invoke: {
|
|
831
|
-
tool: { toolId: (0, utils_1.newid)(), name: "invoke", accessLevel: access_level_1.AccessLevel.Writer },
|
|
832
|
-
toolFn: write,
|
|
833
|
-
},
|
|
834
|
-
},
|
|
835
|
-
}));
|
|
836
|
-
const endpoint = await loader.resolveContractEndpoint({
|
|
837
|
-
contractId,
|
|
838
|
-
version: 1,
|
|
839
|
-
context: {},
|
|
840
|
-
notify: async () => undefined,
|
|
841
|
-
});
|
|
842
|
-
try {
|
|
843
|
-
await expect(endpoint.handleCall({
|
|
844
|
-
contractId,
|
|
845
|
-
version: 1,
|
|
846
|
-
member: "tool",
|
|
847
|
-
name: "invoke",
|
|
848
|
-
operation: "run",
|
|
849
|
-
args: [],
|
|
850
|
-
})).rejects.toThrow("missing caller identity or trusted context for tool 'invoke'");
|
|
851
|
-
expect(write).not.toHaveBeenCalled();
|
|
852
|
-
}
|
|
853
|
-
finally {
|
|
854
|
-
endpoint.dispose();
|
|
855
|
-
}
|
|
856
|
-
});
|
|
857
|
-
});
|
|
858
|
-
const mockGetFileContents = jest.fn();
|
|
859
|
-
jest.mock("../data/files", () => ({
|
|
860
|
-
Files: () => ({
|
|
861
|
-
getFileContents: (...args) => mockGetFileContents(...args),
|
|
862
|
-
}),
|
|
863
|
-
}));
|
|
864
|
-
describe("PackageLoader isolated artifacts", () => {
|
|
865
|
-
const packageId = (0, utils_1.newid)();
|
|
866
|
-
const contractId = (0, utils_1.newid)();
|
|
867
|
-
const artifact = {
|
|
868
|
-
manifest: {
|
|
869
|
-
packageId,
|
|
870
|
-
version: "1.0.0",
|
|
871
|
-
provides: [
|
|
872
|
-
{
|
|
873
|
-
contractId,
|
|
874
|
-
version: 1,
|
|
875
|
-
name: "Greeter",
|
|
876
|
-
tools: [{ name: "greet", usageDescription: "Hello", inputFields: [], outputFields: [] }],
|
|
877
|
-
},
|
|
878
|
-
],
|
|
879
|
-
consumes: [],
|
|
880
|
-
},
|
|
881
|
-
providerSource: "globalThis.__shouldNotEvalOnHost = true;",
|
|
882
|
-
};
|
|
883
|
-
const pkg = {
|
|
884
|
-
packageId,
|
|
885
|
-
name: "Isolated Greeter",
|
|
886
|
-
activePackageVersionId: (0, utils_1.newid)(),
|
|
887
|
-
};
|
|
888
|
-
const selectedVersion = (overrides = {}) => ({
|
|
889
|
-
packageVersionId: pkg.activePackageVersionId,
|
|
890
|
-
packageId,
|
|
891
|
-
version: artifact.manifest.version,
|
|
892
|
-
versionTag: "stable",
|
|
893
|
-
packageBundleFileId: (0, utils_1.newid)(),
|
|
894
|
-
packageBundleFileHash: "bundle-hash",
|
|
895
|
-
...overrides,
|
|
896
|
-
});
|
|
897
|
-
afterEach(() => {
|
|
898
|
-
(0, isolated_runtime_1.setIsolatedPackageRuntimeFactory)(undefined);
|
|
899
|
-
mockGetFileContents.mockReset();
|
|
900
|
-
delete globalThis.__shouldNotEvalOnHost;
|
|
901
|
-
});
|
|
902
|
-
function makeRuntime() {
|
|
903
|
-
const installed = new Set();
|
|
904
|
-
const runtime = {
|
|
905
|
-
install: jest.fn(async (next) => {
|
|
906
|
-
installed.add(next.manifest.packageId);
|
|
907
|
-
return { packageId: next.manifest.packageId, provided: [], consumes: [] };
|
|
908
|
-
}),
|
|
909
|
-
replace: jest.fn(async (next) => {
|
|
910
|
-
installed.add(next.manifest.packageId);
|
|
911
|
-
return { packageId: next.manifest.packageId, provided: [], consumes: [] };
|
|
912
|
-
}),
|
|
913
|
-
updateContractLifecycle: jest.fn(async () => { }),
|
|
914
|
-
uninstall: jest.fn(async (id) => {
|
|
915
|
-
installed.delete(id);
|
|
916
|
-
}),
|
|
917
|
-
resolveEndpoint: jest.fn(async () => undefined),
|
|
918
|
-
provides: jest.fn(() => false),
|
|
919
|
-
isInstalled: jest.fn((id) => installed.has(id)),
|
|
920
|
-
dispose: jest.fn(async () => {
|
|
921
|
-
installed.clear();
|
|
922
|
-
}),
|
|
923
|
-
};
|
|
924
|
-
(0, isolated_runtime_1.setIsolatedPackageRuntimeFactory)(() => runtime);
|
|
925
|
-
return runtime;
|
|
926
|
-
}
|
|
927
|
-
it.each([
|
|
928
|
-
"unload",
|
|
929
|
-
"dispose",
|
|
930
|
-
])("%s invalidates isolated loads awaiting dev version correction", async (action) => {
|
|
931
|
-
const runtime = makeRuntime();
|
|
932
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: (0, utils_1.newid)() });
|
|
933
|
-
let release;
|
|
934
|
-
let entered;
|
|
935
|
-
const gate = new Promise((resolve) => {
|
|
936
|
-
release = resolve;
|
|
937
|
-
});
|
|
938
|
-
const waiting = new Promise((resolve) => {
|
|
939
|
-
entered = resolve;
|
|
940
|
-
});
|
|
941
|
-
mockSignAndSave.mockImplementationOnce(async () => {
|
|
942
|
-
entered();
|
|
943
|
-
await gate;
|
|
944
|
-
});
|
|
945
|
-
const load = () => loader._loadBundle(pkg, (0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact), selectedVersion({ version: "0.0.1", versionTag: "dev" }), true, true);
|
|
946
|
-
try {
|
|
947
|
-
const installing = load();
|
|
948
|
-
await waiting;
|
|
949
|
-
if (action === "unload")
|
|
950
|
-
await loader.unloadPackage(packageId);
|
|
951
|
-
else
|
|
952
|
-
await loader.dispose();
|
|
953
|
-
release();
|
|
954
|
-
await expect(installing).rejects.toThrow(/invalidated|disposed/);
|
|
955
|
-
expect(runtime.install).not.toHaveBeenCalled();
|
|
956
|
-
expect(runtime.replace).not.toHaveBeenCalled();
|
|
957
|
-
expect(runtime.isInstalled(packageId)).toBe(false);
|
|
958
|
-
expect(loader.isIsolatedInstalled(packageId)).toBe(false);
|
|
959
|
-
expect(loader.isContractInstalled(packageId)).toBe(false);
|
|
960
|
-
if (action === "unload") {
|
|
961
|
-
await load();
|
|
962
|
-
expect(runtime.isInstalled(packageId)).toBe(true);
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
finally {
|
|
966
|
-
release();
|
|
967
|
-
await loader.dispose();
|
|
968
|
-
}
|
|
969
|
-
});
|
|
970
|
-
it("does not restore activation bookkeeping when unload overlaps an isolated install", async () => {
|
|
971
|
-
const runtime = makeRuntime();
|
|
972
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: (0, utils_1.newid)() });
|
|
973
|
-
let release;
|
|
974
|
-
let entered;
|
|
975
|
-
const gate = new Promise((resolve) => {
|
|
976
|
-
release = resolve;
|
|
977
|
-
});
|
|
978
|
-
const waiting = new Promise((resolve) => {
|
|
979
|
-
entered = resolve;
|
|
980
|
-
});
|
|
981
|
-
const install = runtime.install.getMockImplementation();
|
|
982
|
-
if (!install)
|
|
983
|
-
throw new Error("Missing runtime install fixture");
|
|
984
|
-
runtime.install.mockImplementationOnce(async (next) => {
|
|
985
|
-
const result = await install(next);
|
|
986
|
-
entered();
|
|
987
|
-
await gate;
|
|
988
|
-
return result;
|
|
989
|
-
});
|
|
990
|
-
const installing = loader._loadBundle(pkg, (0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact), selectedVersion(), false, true);
|
|
991
|
-
try {
|
|
992
|
-
await waiting;
|
|
993
|
-
await loader.unloadPackage(packageId);
|
|
994
|
-
release();
|
|
995
|
-
await expect(installing).rejects.toThrow(/invalidated/);
|
|
996
|
-
expect(loader.isIsolatedInstalled(packageId)).toBe(false);
|
|
997
|
-
expect(loader.isContractInstalled(packageId)).toBe(false);
|
|
998
|
-
expect(runtime.isInstalled(packageId)).toBe(false);
|
|
999
|
-
}
|
|
1000
|
-
finally {
|
|
1001
|
-
release();
|
|
1002
|
-
await loader.dispose();
|
|
1003
|
-
}
|
|
1004
|
-
});
|
|
1005
|
-
it.each([
|
|
1006
|
-
undefined,
|
|
1007
|
-
"sandbox",
|
|
1008
|
-
"privileged",
|
|
1009
|
-
])("installs executionMode=%s without host evaluation", async (executionMode) => {
|
|
1010
|
-
artifact.manifest.executionMode = executionMode;
|
|
1011
|
-
const runtime = makeRuntime();
|
|
1012
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1013
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1014
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-iso" });
|
|
1015
|
-
const loaded = await loader.loadPackage(pkg);
|
|
1016
|
-
expect(loaded).toEqual({ packageId, appNavs: undefined });
|
|
1017
|
-
expect(runtime.install).toHaveBeenCalledWith(artifact, expect.objectContaining({
|
|
1018
|
-
contractLifecycle: "stable",
|
|
1019
|
-
packageVersionId: pkg.activePackageVersionId,
|
|
1020
|
-
packageVersion: "1.0.0",
|
|
1021
|
-
versionTag: "stable",
|
|
1022
|
-
}));
|
|
1023
|
-
expect(loader.isIsolatedInstalled(packageId)).toBe(true);
|
|
1024
|
-
expect(loader.isContractInstalled(packageId)).toBe(true);
|
|
1025
|
-
expect(globalThis.__shouldNotEvalOnHost).toBeUndefined();
|
|
1026
|
-
});
|
|
1027
|
-
test.each([
|
|
1028
|
-
["dev", "mutable"],
|
|
1029
|
-
["beta", "mutable"],
|
|
1030
|
-
["stable", "stable"],
|
|
1031
|
-
])("maps trusted %s package versions to %s contracts", async (versionTag, lifecycle) => {
|
|
1032
|
-
const runtime = makeRuntime();
|
|
1033
|
-
mockGet.mockResolvedValue(selectedVersion({ versionTag }));
|
|
1034
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1035
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: `ctx-${versionTag}` });
|
|
1036
|
-
await loader.loadPackage(pkg);
|
|
1037
|
-
expect(runtime.install).toHaveBeenCalledWith(artifact, expect.objectContaining({ contractLifecycle: lifecycle, versionTag }));
|
|
1038
|
-
});
|
|
1039
|
-
it("rejects an artifact whose package ID does not match the package record", async () => {
|
|
1040
|
-
const runtime = makeRuntime();
|
|
1041
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1042
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1043
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-mismatch" });
|
|
1044
|
-
await expect(loader.loadPackage({ ...pkg, packageId: (0, utils_1.newid)(), activePackageVersionId: (0, utils_1.newid)() })).rejects.toThrow(/packageId mismatch/);
|
|
1045
|
-
expect(runtime.install).not.toHaveBeenCalled();
|
|
1046
|
-
expect(loader.isIsolatedInstalled(packageId)).toBe(false);
|
|
1047
|
-
});
|
|
1048
|
-
it("skips a valid isolated artifact when no runtime factory is registered", async () => {
|
|
1049
|
-
const warn = jest.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
1050
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1051
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1052
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-nofactory" });
|
|
1053
|
-
try {
|
|
1054
|
-
await expect(loader.loadPackage(pkg)).resolves.toBeUndefined();
|
|
1055
|
-
expect(loader.isIsolatedInstalled(packageId)).toBe(false);
|
|
1056
|
-
expect(loader.isContractInstalled(packageId)).toBe(false);
|
|
1057
|
-
expect(warn).toHaveBeenCalledWith(expect.stringMatching(/does not provide an isolated runtime/));
|
|
1058
|
-
expect(globalThis.__shouldNotEvalOnHost).toBeUndefined();
|
|
1059
|
-
}
|
|
1060
|
-
finally {
|
|
1061
|
-
warn.mockRestore();
|
|
1062
|
-
}
|
|
1063
|
-
});
|
|
1064
|
-
it("clears a previously loaded legacy provider when isolation is unsupported", async () => {
|
|
1065
|
-
const warn = jest.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
1066
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1067
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1068
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-no-runtime-transition" });
|
|
1069
|
-
const legacyDefinition = {
|
|
1070
|
-
contractId,
|
|
1071
|
-
version: 1,
|
|
1072
|
-
devTag: "dev",
|
|
1073
|
-
name: "Legacy provider",
|
|
1074
|
-
description: "",
|
|
1075
|
-
tables: [],
|
|
1076
|
-
tools: [],
|
|
1077
|
-
observables: [],
|
|
1078
|
-
events: [],
|
|
1079
|
-
};
|
|
1080
|
-
loader.contractRegistry.register(packageId, legacyDefinition);
|
|
1081
|
-
loader.packageInstances[packageId] = { packageId, appNavs: [] };
|
|
1082
|
-
loader.contractInstalledPackages.add(packageId);
|
|
1083
|
-
try {
|
|
1084
|
-
await expect(loader.loadPackage(pkg, { force: true })).resolves.toBeUndefined();
|
|
1085
|
-
expect(loader.contractRegistry.getProviderPackageId(contractId, 1)).toBeUndefined();
|
|
1086
|
-
expect(loader.packageInstances[packageId]).toBeUndefined();
|
|
1087
|
-
}
|
|
1088
|
-
finally {
|
|
1089
|
-
warn.mockRestore();
|
|
1090
|
-
}
|
|
1091
|
-
});
|
|
1092
|
-
it("preserves a working legacy provider when isolated installation fails", async () => {
|
|
1093
|
-
const runtime = makeRuntime();
|
|
1094
|
-
runtime.install.mockRejectedValueOnce(new Error("migration failed"));
|
|
1095
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1096
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1097
|
-
const loader = new package_loader_1.PackageLoader({
|
|
1098
|
-
dataContextId: "ctx-failed-isolated-transition",
|
|
1099
|
-
});
|
|
1100
|
-
const legacyDefinition = {
|
|
1101
|
-
contractId,
|
|
1102
|
-
version: 1,
|
|
1103
|
-
devTag: "dev",
|
|
1104
|
-
name: "Legacy provider",
|
|
1105
|
-
description: "",
|
|
1106
|
-
tables: [],
|
|
1107
|
-
tools: [],
|
|
1108
|
-
observables: [],
|
|
1109
|
-
events: [],
|
|
1110
|
-
};
|
|
1111
|
-
loader.contractRegistry.register(packageId, legacyDefinition);
|
|
1112
|
-
const previous = { packageId, appNavs: [] };
|
|
1113
|
-
loader.packageInstances[packageId] = previous;
|
|
1114
|
-
loader.contractInstalledPackages.add(packageId);
|
|
1115
|
-
await expect(loader.loadPackage(pkg, { force: true })).rejects.toThrow(/migration failed/);
|
|
1116
|
-
expect(loader.contractRegistry.getProviderPackageId(contractId, 1)).toBe(packageId);
|
|
1117
|
-
expect(loader.packageInstances[packageId]).toBe(previous);
|
|
1118
|
-
});
|
|
1119
|
-
it("rejects a package ID mismatch even when the host has no isolated runtime", async () => {
|
|
1120
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1121
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1122
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-nofactory-mismatch" });
|
|
1123
|
-
await expect(loader.loadPackage({ ...pkg, packageId: (0, utils_1.newid)(), activePackageVersionId: (0, utils_1.newid)() })).rejects.toThrow(/packageId mismatch/);
|
|
1124
|
-
});
|
|
1125
|
-
it("continues loading legacy packages after skipping an unsupported isolated package", async () => {
|
|
1126
|
-
const warn = jest.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
1127
|
-
const legacyPackageId = (0, utils_1.newid)();
|
|
1128
|
-
const legacyPkg = {
|
|
1129
|
-
packageId: legacyPackageId,
|
|
1130
|
-
name: "Legacy Package",
|
|
1131
|
-
activePackageVersionId: (0, utils_1.newid)(),
|
|
1132
|
-
};
|
|
1133
|
-
mockGet.mockImplementation(async (packageVersionId) => ({
|
|
1134
|
-
packageVersionId,
|
|
1135
|
-
packageId: packageVersionId === pkg.activePackageVersionId ? packageId : legacyPackageId,
|
|
1136
|
-
version: "1.0.0",
|
|
1137
|
-
packageBundleFileId: (0, utils_1.newid)(),
|
|
1138
|
-
packageBundleFileHash: "bundle-hash",
|
|
1139
|
-
versionTag: "dev",
|
|
1140
|
-
}));
|
|
1141
|
-
mockGetFileContents
|
|
1142
|
-
.mockResolvedValueOnce(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)))
|
|
1143
|
-
.mockResolvedValueOnce(Buffer.from(`module.exports = { packageId: "${legacyPackageId}", appNavs: [] };`));
|
|
1144
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-mixed-host" });
|
|
1145
|
-
try {
|
|
1146
|
-
await expect(loader.loadPackage(pkg)).resolves.toBeUndefined();
|
|
1147
|
-
await expect(loader.loadPackage(legacyPkg)).resolves.toEqual({
|
|
1148
|
-
packageId: legacyPackageId,
|
|
1149
|
-
appNavs: [],
|
|
1150
|
-
});
|
|
1151
|
-
}
|
|
1152
|
-
finally {
|
|
1153
|
-
warn.mockRestore();
|
|
1154
|
-
}
|
|
1155
|
-
});
|
|
1156
|
-
it("fails closed on a malformed isolated envelope and never evaluates it", async () => {
|
|
1157
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1158
|
-
mockGetFileContents.mockResolvedValue(Buffer.from(`${"PEERS_ISOLATED_PACKAGE_V1"}\n{nope`));
|
|
1159
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-bad" });
|
|
1160
|
-
await expect(loader.loadPackage(pkg)).rejects.toThrow(/JSON is invalid/);
|
|
1161
|
-
expect(globalThis.__shouldNotEvalOnHost).toBeUndefined();
|
|
1162
|
-
});
|
|
1163
|
-
it.each([
|
|
1164
|
-
null,
|
|
1165
|
-
true,
|
|
1166
|
-
"unknown",
|
|
1167
|
-
])("fails closed on malformed executionMode=%p", async (executionMode) => {
|
|
1168
|
-
const runtime = makeRuntime();
|
|
1169
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1170
|
-
const malformed = structuredClone(artifact);
|
|
1171
|
-
Object.assign(malformed.manifest, { executionMode });
|
|
1172
|
-
mockGetFileContents.mockResolvedValue(Buffer.from(`PEERS_ISOLATED_PACKAGE_V1\n${JSON.stringify(malformed)}`));
|
|
1173
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-bad-mode" });
|
|
1174
|
-
await expect(loader.loadPackage(pkg)).rejects.toThrow(/executionMode/);
|
|
1175
|
-
expect(runtime.install).not.toHaveBeenCalled();
|
|
1176
|
-
expect(globalThis.__shouldNotEvalOnHost).toBeUndefined();
|
|
1177
|
-
});
|
|
1178
|
-
it("passes gateways and a trusted local identity resolver from its data context", () => {
|
|
1179
|
-
const factory = jest.fn((_options) => ({
|
|
1180
|
-
install: jest.fn(async () => ({ packageId, provided: [], consumes: [] })),
|
|
1181
|
-
replace: jest.fn(async () => ({ packageId, provided: [], consumes: [] })),
|
|
1182
|
-
uninstall: jest.fn(async () => undefined),
|
|
1183
|
-
resolveEndpoint: jest.fn(async () => undefined),
|
|
1184
|
-
provides: jest.fn(() => false),
|
|
1185
|
-
isInstalled: jest.fn(() => false),
|
|
1186
|
-
dispose: jest.fn(async () => undefined),
|
|
1187
|
-
}));
|
|
1188
|
-
(0, isolated_runtime_1.setIsolatedPackageRuntimeFactory)(factory);
|
|
1189
|
-
const tableContainer = { registerTableDefinition: jest.fn(), getTable: jest.fn() };
|
|
1190
|
-
const dataContext = {
|
|
1191
|
-
dataContextId: "ctx-tables",
|
|
1192
|
-
tableContainer,
|
|
1193
|
-
};
|
|
1194
|
-
dataContext.userContext = {
|
|
1195
|
-
userId: (0, utils_1.newid)(),
|
|
1196
|
-
userDataContext: dataContext,
|
|
1197
|
-
};
|
|
1198
|
-
new package_loader_1.PackageLoader(dataContext);
|
|
1199
|
-
expect(factory).toHaveBeenCalledWith(expect.objectContaining({
|
|
1200
|
-
dataContextId: "ctx-tables",
|
|
1201
|
-
getLocalUserContext: expect.any(Function),
|
|
1202
|
-
providerGateway: expect.objectContaining({
|
|
1203
|
-
resolveEndpoint: expect.any(Function),
|
|
1204
|
-
}),
|
|
1205
|
-
persistentVarGateway: expect.objectContaining({
|
|
1206
|
-
get: expect.any(Function),
|
|
1207
|
-
set: expect.any(Function),
|
|
1208
|
-
}),
|
|
1209
|
-
tableGateway: expect.objectContaining({
|
|
1210
|
-
register: expect.any(Function),
|
|
1211
|
-
get: expect.any(Function),
|
|
1212
|
-
save: expect.any(Function),
|
|
1213
|
-
}),
|
|
1214
|
-
}));
|
|
1215
|
-
const resolveLocalUser = factory.mock.calls[0][0].getLocalUserContext;
|
|
1216
|
-
expect(resolveLocalUser?.()).toEqual({ userId: dataContext.userContext.userId, dataContext });
|
|
1217
|
-
expect(resolveLocalUser?.()?.dataContext).toBe(dataContext);
|
|
1218
|
-
dataContext.userContext = undefined;
|
|
1219
|
-
expect(resolveLocalUser?.()).toBeUndefined();
|
|
1220
|
-
});
|
|
1221
|
-
it("unloads an isolated package on force reload and disable", async () => {
|
|
1222
|
-
const runtime = makeRuntime();
|
|
1223
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1224
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1225
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-unload" });
|
|
1226
|
-
await loader.loadPackage(pkg);
|
|
1227
|
-
await loader.loadPackage(pkg, { force: true });
|
|
1228
|
-
expect(runtime.uninstall).not.toHaveBeenCalled();
|
|
1229
|
-
expect(runtime.install).toHaveBeenCalledTimes(1);
|
|
1230
|
-
expect(runtime.replace).toHaveBeenCalledTimes(1);
|
|
1231
|
-
await loader.loadPackage({ ...pkg, disabled: true });
|
|
1232
|
-
expect(runtime.uninstall).toHaveBeenCalledWith(packageId);
|
|
1233
|
-
expect(loader.isIsolatedInstalled(packageId)).toBe(false);
|
|
1234
|
-
});
|
|
1235
|
-
it("prefers a local isolated bundle over a stored isolated Files bundle", async () => {
|
|
1236
|
-
const runtime = makeRuntime();
|
|
1237
|
-
mockGet.mockResolvedValue(selectedVersion({ versionTag: "dev" }));
|
|
1238
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)({
|
|
1239
|
-
...artifact,
|
|
1240
|
-
manifest: { ...artifact.manifest, version: "1.0.0" },
|
|
1241
|
-
})));
|
|
1242
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-local-iso" });
|
|
1243
|
-
loader.require = ((moduleId) => {
|
|
1244
|
-
if (moduleId === "fs") {
|
|
1245
|
-
return {
|
|
1246
|
-
readFileSync: () => (0, isolated_artifact_1.serializeIsolatedPackageArtifact)({
|
|
1247
|
-
...artifact,
|
|
1248
|
-
manifest: { ...artifact.manifest, version: "2.0.0" },
|
|
1249
|
-
}),
|
|
1250
|
-
};
|
|
1251
|
-
}
|
|
1252
|
-
if (moduleId === "path") {
|
|
1253
|
-
return { join: (...parts) => parts.join("/") };
|
|
1254
|
-
}
|
|
1255
|
-
throw new Error(`unexpected require ${moduleId}`);
|
|
1256
|
-
});
|
|
1257
|
-
await loader.loadPackage(pkg, { localPath: "/tmp/isolation-local" });
|
|
1258
|
-
expect(runtime.install).toHaveBeenCalledWith(expect.objectContaining({
|
|
1259
|
-
manifest: expect.objectContaining({ version: "2.0.0" }),
|
|
1260
|
-
}), expect.objectContaining({
|
|
1261
|
-
contractLifecycle: "mutable",
|
|
1262
|
-
packageVersion: "2.0.0",
|
|
1263
|
-
versionTag: "dev",
|
|
1264
|
-
}));
|
|
1265
|
-
});
|
|
1266
|
-
it("does not override a stable selected version from a local checkout", async () => {
|
|
1267
|
-
const runtime = makeRuntime();
|
|
1268
|
-
mockGet.mockResolvedValue(selectedVersion({ versionTag: "stable" }));
|
|
1269
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)({
|
|
1270
|
-
...artifact,
|
|
1271
|
-
manifest: { ...artifact.manifest, version: "1.0.0" },
|
|
1272
|
-
})));
|
|
1273
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-stable-local" });
|
|
1274
|
-
loader.require = ((moduleId) => {
|
|
1275
|
-
if (moduleId === "fs") {
|
|
1276
|
-
return {
|
|
1277
|
-
readFileSync: () => (0, isolated_artifact_1.serializeIsolatedPackageArtifact)({
|
|
1278
|
-
...artifact,
|
|
1279
|
-
manifest: { ...artifact.manifest, version: "2.0.0" },
|
|
1280
|
-
}),
|
|
1281
|
-
};
|
|
1282
|
-
}
|
|
1283
|
-
if (moduleId === "path") {
|
|
1284
|
-
return { join: (...parts) => parts.join("/") };
|
|
1285
|
-
}
|
|
1286
|
-
throw new Error(`unexpected require ${moduleId}`);
|
|
1287
|
-
});
|
|
1288
|
-
await loader.loadPackage(pkg, { localPath: "/tmp/stable-checkout" });
|
|
1289
|
-
expect(runtime.install).toHaveBeenCalledWith(expect.objectContaining({
|
|
1290
|
-
manifest: expect.objectContaining({ version: "1.0.0" }),
|
|
1291
|
-
}), expect.objectContaining({ contractLifecycle: "stable", packageVersion: "1.0.0" }));
|
|
1292
|
-
});
|
|
1293
|
-
it("rejects a beta or stable manifest version mismatch", async () => {
|
|
1294
|
-
const runtime = makeRuntime();
|
|
1295
|
-
mockGet.mockResolvedValue(selectedVersion({ version: "1.0.0", versionTag: "beta" }));
|
|
1296
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)({
|
|
1297
|
-
...artifact,
|
|
1298
|
-
manifest: { ...artifact.manifest, version: "2.0.0" },
|
|
1299
|
-
})));
|
|
1300
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-version-mismatch" });
|
|
1301
|
-
await expect(loader.loadPackage(pkg)).rejects.toThrow(/version mismatch/);
|
|
1302
|
-
expect(runtime.install).not.toHaveBeenCalled();
|
|
1303
|
-
});
|
|
1304
|
-
it("uses the instance require callback for legacy package dependencies", async () => {
|
|
1305
|
-
const warn = jest.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
1306
|
-
const runtime = makeRuntime();
|
|
1307
|
-
const requiredValue = { source: "injected-require" };
|
|
1308
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1309
|
-
mockGetFileContents.mockResolvedValue(Buffer.from(`module.exports = { packageId: "${packageId}", appNavs: [require("custom-module")] };`));
|
|
1310
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-custom-require" });
|
|
1311
|
-
loader.require = ((moduleId) => {
|
|
1312
|
-
if (moduleId === "custom-module") {
|
|
1313
|
-
return requiredValue;
|
|
1314
|
-
}
|
|
1315
|
-
throw new Error(`unexpected require ${moduleId}`);
|
|
1316
|
-
});
|
|
1317
|
-
try {
|
|
1318
|
-
await expect(loader.loadPackage(pkg)).resolves.toEqual({
|
|
1319
|
-
packageId,
|
|
1320
|
-
appNavs: [requiredValue],
|
|
1321
|
-
});
|
|
1322
|
-
expect(runtime.install).not.toHaveBeenCalled();
|
|
1323
|
-
}
|
|
1324
|
-
finally {
|
|
1325
|
-
warn.mockRestore();
|
|
1326
|
-
}
|
|
1327
|
-
});
|
|
1328
|
-
it("resolves the virtual PeersSDK import through the canonical package name", async () => {
|
|
1329
|
-
const runtime = makeRuntime();
|
|
1330
|
-
const sdkModule = { marker: "canonical-sdk" };
|
|
1331
|
-
const previousPeersSDK = package_loader_1.PackageLoader.PeersSDK;
|
|
1332
|
-
package_loader_1.PackageLoader.PeersSDK = undefined;
|
|
1333
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1334
|
-
mockGetFileContents.mockResolvedValue(Buffer.from(`module.exports = { packageId: "${packageId}", appNavs: [require("PeersSDK").marker] };`));
|
|
1335
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-canonical-sdk" });
|
|
1336
|
-
loader.require = ((moduleId) => {
|
|
1337
|
-
if (moduleId === "@peers-app/peers-sdk") {
|
|
1338
|
-
return sdkModule;
|
|
1339
|
-
}
|
|
1340
|
-
throw new Error(`unexpected require ${moduleId}`);
|
|
1341
|
-
});
|
|
1342
|
-
try {
|
|
1343
|
-
await expect(loader.loadPackage(pkg)).resolves.toEqual({
|
|
1344
|
-
packageId,
|
|
1345
|
-
appNavs: ["canonical-sdk"],
|
|
1346
|
-
});
|
|
1347
|
-
expect(runtime.install).not.toHaveBeenCalled();
|
|
1348
|
-
}
|
|
1349
|
-
finally {
|
|
1350
|
-
package_loader_1.PackageLoader.PeersSDK = previousPeersSDK;
|
|
1351
|
-
}
|
|
1352
|
-
});
|
|
1353
|
-
it("keeps an isolated provider active when legacy evaluation fails", async () => {
|
|
1354
|
-
const runtime = makeRuntime();
|
|
1355
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1356
|
-
mockGetFileContents.mockResolvedValueOnce(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1357
|
-
const loader = new package_loader_1.PackageLoader({
|
|
1358
|
-
dataContextId: "ctx-failed-legacy-transition",
|
|
1359
|
-
});
|
|
1360
|
-
await loader.loadPackage(pkg);
|
|
1361
|
-
mockGetFileContents.mockResolvedValueOnce(Buffer.from("throw new Error('legacy failed');"));
|
|
1362
|
-
await expect(loader.loadPackage(pkg, { force: true })).resolves.toBeUndefined();
|
|
1363
|
-
expect(runtime.uninstall).not.toHaveBeenCalled();
|
|
1364
|
-
expect(runtime.isInstalled(packageId)).toBe(true);
|
|
1365
|
-
expect(loader.isIsolatedInstalled(packageId)).toBe(true);
|
|
1366
|
-
});
|
|
1367
|
-
it("retires isolated state only after a legacy bundle evaluates successfully", async () => {
|
|
1368
|
-
const runtime = makeRuntime();
|
|
1369
|
-
Object.assign(runtime, {
|
|
1370
|
-
getPackageHealth: () => ({
|
|
1371
|
-
workerRunning: false,
|
|
1372
|
-
lastWorkerError: { message: "old worker", at: 1 },
|
|
1373
|
-
}),
|
|
1374
|
-
});
|
|
1375
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1376
|
-
mockGetFileContents
|
|
1377
|
-
.mockResolvedValueOnce(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)))
|
|
1378
|
-
.mockResolvedValueOnce(Buffer.from(`module.exports = { packageId: "${packageId}" };`));
|
|
1379
|
-
const loader = new package_loader_1.PackageLoader({
|
|
1380
|
-
dataContextId: "ctx-successful-legacy-transition",
|
|
1381
|
-
});
|
|
1382
|
-
await loader.loadPackage(pkg);
|
|
1383
|
-
await expect(loader.loadPackage(pkg, { force: true })).resolves.toEqual({ packageId });
|
|
1384
|
-
expect(runtime.uninstall).toHaveBeenCalledWith(packageId, { preserveRegistry: false });
|
|
1385
|
-
expect(runtime.isInstalled(packageId)).toBe(false);
|
|
1386
|
-
expect(loader.isIsolatedInstalled(packageId)).toBe(false);
|
|
1387
|
-
expect(loader.runtimeStates()[packageId].worker).toBeUndefined();
|
|
1388
|
-
expect(loader.getPackageRuntimeState(packageId)?.worker).toBeUndefined();
|
|
1389
|
-
});
|
|
1390
|
-
describe("runtime state", () => {
|
|
1391
|
-
beforeEach(() => {
|
|
1392
|
-
// An earlier executionMode case leaves the shared artifact privileged.
|
|
1393
|
-
delete artifact.manifest.executionMode;
|
|
1394
|
-
});
|
|
1395
|
-
it("records running state with the runtime's provides and consumes", async () => {
|
|
1396
|
-
const runtime = makeRuntime();
|
|
1397
|
-
const provided = [{ contractId, version: 1 }];
|
|
1398
|
-
const consumes = [{ contractId: (0, utils_1.newid)(), version: 2, optional: true, satisfied: false }];
|
|
1399
|
-
runtime.install.mockImplementationOnce((async (next) => ({
|
|
1400
|
-
packageId: next.manifest.packageId,
|
|
1401
|
-
provided,
|
|
1402
|
-
consumes,
|
|
1403
|
-
})));
|
|
1404
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1405
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1406
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-state-running" });
|
|
1407
|
-
const seen = [];
|
|
1408
|
-
loader.runtimeStates.subscribe((states) => seen.push(states[packageId]?.status));
|
|
1409
|
-
await loader.loadPackage(pkg);
|
|
1410
|
-
expect(loader.getPackageRuntimeState(packageId)).toEqual({
|
|
1411
|
-
packageId,
|
|
1412
|
-
status: "running",
|
|
1413
|
-
running: {
|
|
1414
|
-
packageVersionId: pkg.activePackageVersionId,
|
|
1415
|
-
version: "1.0.0",
|
|
1416
|
-
versionTag: "stable",
|
|
1417
|
-
kind: "isolated",
|
|
1418
|
-
executionMode: "sandbox",
|
|
1419
|
-
provides: provided,
|
|
1420
|
-
consumes,
|
|
1421
|
-
},
|
|
1422
|
-
updatedAt: expect.any(Number),
|
|
1423
|
-
});
|
|
1424
|
-
expect(seen).toContain("running");
|
|
1425
|
-
await loader.dispose();
|
|
1426
|
-
expect(loader.getPackageRuntimeStates()).toEqual({});
|
|
1427
|
-
});
|
|
1428
|
-
it("keeps the running build on an onlyIfChanged load and reloads a changed one", async () => {
|
|
1429
|
-
const runtime = makeRuntime();
|
|
1430
|
-
mockGet.mockResolvedValue(selectedVersion({ versionTag: "dev" }));
|
|
1431
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1432
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: (0, utils_1.newid)() });
|
|
1433
|
-
try {
|
|
1434
|
-
const first = await loader.loadPackage(pkg);
|
|
1435
|
-
expect(runtime.install).toHaveBeenCalledTimes(1);
|
|
1436
|
-
await expect(loader.loadPackage(pkg, { force: true, onlyIfChanged: true })).resolves.toBe(first);
|
|
1437
|
-
expect(runtime.replace).not.toHaveBeenCalled();
|
|
1438
|
-
// A promotion changes only the tag, which is not part of the bundle.
|
|
1439
|
-
mockGet.mockResolvedValue(selectedVersion({ versionTag: "beta" }));
|
|
1440
|
-
await loader.loadPackage(pkg, { force: true, onlyIfChanged: true });
|
|
1441
|
-
expect(runtime.replace).not.toHaveBeenCalled();
|
|
1442
|
-
expect(loader.getPackageRuntimeState(packageId)?.running?.versionTag).toBe("beta");
|
|
1443
|
-
mockGet.mockResolvedValue(selectedVersion({ versionTag: "stable" }));
|
|
1444
|
-
await loader.loadPackage(pkg, { force: true, onlyIfChanged: true });
|
|
1445
|
-
expect(runtime.updateContractLifecycle).toHaveBeenLastCalledWith(packageId, expect.objectContaining({ contractLifecycle: "stable", versionTag: "stable" }));
|
|
1446
|
-
expect(runtime.replace).not.toHaveBeenCalled();
|
|
1447
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)({
|
|
1448
|
-
...artifact,
|
|
1449
|
-
providerSource: `${artifact.providerSource}\n// rebuilt`,
|
|
1450
|
-
})));
|
|
1451
|
-
await loader.loadPackage(pkg, { force: true, onlyIfChanged: true });
|
|
1452
|
-
expect(runtime.replace).toHaveBeenCalledTimes(1);
|
|
1453
|
-
await loader.loadPackage(pkg, { force: true });
|
|
1454
|
-
expect(runtime.replace).toHaveBeenCalledTimes(2);
|
|
1455
|
-
}
|
|
1456
|
-
finally {
|
|
1457
|
-
await loader.dispose();
|
|
1458
|
-
}
|
|
1459
|
-
});
|
|
1460
|
-
it.each([
|
|
1461
|
-
"unload",
|
|
1462
|
-
"dispose",
|
|
1463
|
-
"replace",
|
|
1464
|
-
])("does not overwrite %s while an unchanged-build comparison is pending", async (action) => {
|
|
1465
|
-
const runtime = makeRuntime();
|
|
1466
|
-
mockGet.mockResolvedValue(selectedVersion({ versionTag: "dev" }));
|
|
1467
|
-
const bundle = Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact));
|
|
1468
|
-
mockGetFileContents.mockResolvedValue(bundle);
|
|
1469
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: (0, utils_1.newid)() });
|
|
1470
|
-
let release;
|
|
1471
|
-
let entered;
|
|
1472
|
-
const waiting = new Promise((resolve) => {
|
|
1473
|
-
entered = resolve;
|
|
1474
|
-
});
|
|
1475
|
-
const pendingRead = new Promise((resolve) => {
|
|
1476
|
-
release = resolve;
|
|
1477
|
-
});
|
|
1478
|
-
try {
|
|
1479
|
-
await loader.loadPackage(pkg);
|
|
1480
|
-
mockGet.mockResolvedValue(selectedVersion({ versionTag: "beta" }));
|
|
1481
|
-
mockGetFileContents.mockImplementationOnce(() => {
|
|
1482
|
-
entered();
|
|
1483
|
-
return pendingRead;
|
|
1484
|
-
});
|
|
1485
|
-
const pending = loader.loadPackage(pkg, { force: true, onlyIfChanged: true });
|
|
1486
|
-
await waiting;
|
|
1487
|
-
if (action === "replace") {
|
|
1488
|
-
mockGet.mockResolvedValue(selectedVersion({ versionTag: "stable" }));
|
|
1489
|
-
await loader.loadPackage(pkg, { force: true });
|
|
1490
|
-
}
|
|
1491
|
-
else if (action === "unload") {
|
|
1492
|
-
await loader.unloadPackage(packageId);
|
|
1493
|
-
}
|
|
1494
|
-
else {
|
|
1495
|
-
await loader.dispose();
|
|
1496
|
-
}
|
|
1497
|
-
const latest = loader.getPackageRuntimeState(packageId);
|
|
1498
|
-
release(bundle);
|
|
1499
|
-
await expect(pending).resolves.toBeUndefined();
|
|
1500
|
-
expect(loader.getPackageRuntimeState(packageId)).toEqual(latest);
|
|
1501
|
-
expect(runtime.updateContractLifecycle).not.toHaveBeenCalled();
|
|
1502
|
-
expect(runtime.install).toHaveBeenCalledTimes(1);
|
|
1503
|
-
expect(runtime.replace).toHaveBeenCalledTimes(action === "replace" ? 1 : 0);
|
|
1504
|
-
}
|
|
1505
|
-
finally {
|
|
1506
|
-
release(bundle);
|
|
1507
|
-
await loader.dispose();
|
|
1508
|
-
}
|
|
1509
|
-
});
|
|
1510
|
-
it("validates a changed version label even when its bundle is unchanged", async () => {
|
|
1511
|
-
makeRuntime();
|
|
1512
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1513
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1514
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: (0, utils_1.newid)() });
|
|
1515
|
-
try {
|
|
1516
|
-
await loader.loadPackage(pkg);
|
|
1517
|
-
mockGet.mockResolvedValue(selectedVersion({ version: "2.0.0" }));
|
|
1518
|
-
await expect(loader.loadPackage(pkg, { force: true, onlyIfChanged: true })).rejects.toThrow(/version mismatch/);
|
|
1519
|
-
expect(loader.getPackageRuntimeState(packageId)?.running?.version).toBe("1.0.0");
|
|
1520
|
-
}
|
|
1521
|
-
finally {
|
|
1522
|
-
await loader.dispose();
|
|
1523
|
-
}
|
|
1524
|
-
});
|
|
1525
|
-
it("keeps the previous running version when a replacement fails", async () => {
|
|
1526
|
-
const runtime = makeRuntime();
|
|
1527
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1528
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1529
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-state-replace" });
|
|
1530
|
-
await loader.loadPackage(pkg);
|
|
1531
|
-
runtime.replace.mockRejectedValueOnce(new Error("replacement rejected"));
|
|
1532
|
-
await expect(loader.loadPackage(pkg, { force: true })).rejects.toThrow(/replacement rejected/);
|
|
1533
|
-
const state = loader.getPackageRuntimeState(packageId);
|
|
1534
|
-
expect(state?.status).toBe("failed");
|
|
1535
|
-
expect(state?.running?.version).toBe("1.0.0");
|
|
1536
|
-
expect(state?.lastError).toEqual({
|
|
1537
|
-
phase: "load",
|
|
1538
|
-
message: "replacement rejected",
|
|
1539
|
-
packageVersionId: pkg.activePackageVersionId,
|
|
1540
|
-
version: "1.0.0",
|
|
1541
|
-
at: expect.any(Number),
|
|
1542
|
-
});
|
|
1543
|
-
await loader.loadPackage(pkg, { force: true });
|
|
1544
|
-
expect(loader.getPackageRuntimeState(packageId)?.status).toBe("running");
|
|
1545
|
-
expect(loader.getPackageRuntimeState(packageId)?.lastError).toBeUndefined();
|
|
1546
|
-
});
|
|
1547
|
-
it("records a malformed isolated envelope as a failed load", async () => {
|
|
1548
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1549
|
-
mockGetFileContents.mockResolvedValue(Buffer.from("PEERS_ISOLATED_PACKAGE_V1\n{nope"));
|
|
1550
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-state-malformed" });
|
|
1551
|
-
await expect(loader.loadPackage(pkg)).rejects.toThrow(/JSON is invalid/);
|
|
1552
|
-
const state = loader.getPackageRuntimeState(packageId);
|
|
1553
|
-
expect(state?.status).toBe("failed");
|
|
1554
|
-
expect(state?.running).toBeUndefined();
|
|
1555
|
-
expect(state?.lastError?.message).toMatch(/JSON is invalid/);
|
|
1556
|
-
});
|
|
1557
|
-
it("records a failed legacy evaluation", async () => {
|
|
1558
|
-
const warn = jest.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
1559
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1560
|
-
mockGetFileContents.mockResolvedValue(Buffer.from("throw new Error('legacy failed');"));
|
|
1561
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-state-legacy" });
|
|
1562
|
-
try {
|
|
1563
|
-
await expect(loader.loadPackage(pkg)).resolves.toBeUndefined();
|
|
1564
|
-
expect(loader.getPackageRuntimeState(packageId)).toMatchObject({
|
|
1565
|
-
status: "failed",
|
|
1566
|
-
lastError: { phase: "load", message: "legacy failed" },
|
|
1567
|
-
});
|
|
1568
|
-
}
|
|
1569
|
-
finally {
|
|
1570
|
-
warn.mockRestore();
|
|
1571
|
-
}
|
|
1572
|
-
});
|
|
1573
|
-
it.each([
|
|
1574
|
-
"module.exports = 42;",
|
|
1575
|
-
"// No exports",
|
|
1576
|
-
"module.exports = {};",
|
|
1577
|
-
"module.exports = { default: {} };",
|
|
1578
|
-
])("records unusable legacy exports as a failed load: %s", async (bundle) => {
|
|
1579
|
-
const warn = jest.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
1580
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1581
|
-
mockGetFileContents.mockResolvedValue(Buffer.from(bundle));
|
|
1582
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-state-empty" });
|
|
1583
|
-
try {
|
|
1584
|
-
await expect(loader.loadPackage(pkg)).resolves.toBeUndefined();
|
|
1585
|
-
expect(loader.getPackageRuntimeState(packageId)).toMatchObject({
|
|
1586
|
-
status: "failed",
|
|
1587
|
-
lastError: { phase: "load", message: "Package bundle exports nothing usable" },
|
|
1588
|
-
});
|
|
1589
|
-
}
|
|
1590
|
-
finally {
|
|
1591
|
-
warn.mockRestore();
|
|
1592
|
-
}
|
|
1593
|
-
});
|
|
1594
|
-
it("keeps the previous package when a replacement exports nothing", async () => {
|
|
1595
|
-
const warn = jest.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
1596
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1597
|
-
mockGetFileContents.mockResolvedValue(Buffer.from(`module.exports = { packageId: "${packageId}" };`));
|
|
1598
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: (0, utils_1.newid)() });
|
|
1599
|
-
try {
|
|
1600
|
-
const previous = await loader.loadPackage(pkg);
|
|
1601
|
-
const running = loader.getPackageRuntimeState(packageId)?.running;
|
|
1602
|
-
mockGetFileContents.mockResolvedValue(Buffer.from("// No exports"));
|
|
1603
|
-
await expect(loader.loadPackage(pkg, { force: true })).resolves.toBeUndefined();
|
|
1604
|
-
expect(loader.getPackageRuntimeState(packageId)).toMatchObject({
|
|
1605
|
-
status: "failed",
|
|
1606
|
-
running,
|
|
1607
|
-
lastError: { message: "Package bundle exports nothing usable" },
|
|
1608
|
-
});
|
|
1609
|
-
await expect(loader.loadPackage(pkg)).resolves.toBe(previous);
|
|
1610
|
-
}
|
|
1611
|
-
finally {
|
|
1612
|
-
warn.mockRestore();
|
|
1613
|
-
await loader.dispose();
|
|
1614
|
-
}
|
|
1615
|
-
});
|
|
1616
|
-
it("records unsupported when the host has no isolated runtime", async () => {
|
|
1617
|
-
const warn = jest.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
1618
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1619
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1620
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-state-unsupported" });
|
|
1621
|
-
try {
|
|
1622
|
-
await loader.loadPackage(pkg);
|
|
1623
|
-
expect(loader.getPackageRuntimeState(packageId)).toMatchObject({
|
|
1624
|
-
status: "unsupported",
|
|
1625
|
-
reason: expect.stringMatching(/no isolated package runtime/),
|
|
1626
|
-
});
|
|
1627
|
-
}
|
|
1628
|
-
finally {
|
|
1629
|
-
warn.mockRestore();
|
|
1630
|
-
}
|
|
1631
|
-
});
|
|
1632
|
-
it("records unavailable when the selected bundle is not on this device", async () => {
|
|
1633
|
-
const warn = jest.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
1634
|
-
makeRuntime();
|
|
1635
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1636
|
-
mockGetFileContents.mockResolvedValue(undefined);
|
|
1637
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-state-unavailable" });
|
|
1638
|
-
try {
|
|
1639
|
-
await expect(loader.loadPackage(pkg)).resolves.toBeUndefined();
|
|
1640
|
-
expect(loader.getPackageRuntimeState(packageId)).toMatchObject({
|
|
1641
|
-
status: "unavailable",
|
|
1642
|
-
reason: expect.stringMatching(/not on this device/),
|
|
1643
|
-
});
|
|
1644
|
-
}
|
|
1645
|
-
finally {
|
|
1646
|
-
warn.mockRestore();
|
|
1647
|
-
}
|
|
1648
|
-
});
|
|
1649
|
-
it("records disabled and unloaded packages without a running version", async () => {
|
|
1650
|
-
makeRuntime();
|
|
1651
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1652
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1653
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-state-disabled" });
|
|
1654
|
-
await loader.loadPackage(pkg);
|
|
1655
|
-
await loader.loadPackage({ ...pkg, disabled: true });
|
|
1656
|
-
expect(loader.getPackageRuntimeState(packageId)).toEqual({
|
|
1657
|
-
packageId,
|
|
1658
|
-
status: "disabled",
|
|
1659
|
-
reason: expect.any(String),
|
|
1660
|
-
updatedAt: expect.any(Number),
|
|
1661
|
-
});
|
|
1662
|
-
await loader.unloadPackage(packageId, { reason: "uninstalled" });
|
|
1663
|
-
expect(loader.getPackageRuntimeState(packageId)).toEqual({
|
|
1664
|
-
packageId,
|
|
1665
|
-
status: "unloaded",
|
|
1666
|
-
reason: "uninstalled",
|
|
1667
|
-
updatedAt: expect.any(Number),
|
|
1668
|
-
});
|
|
1669
|
-
});
|
|
1670
|
-
it("does not let a superseded load overwrite newer state", async () => {
|
|
1671
|
-
const runtime = makeRuntime();
|
|
1672
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-state-superseded" });
|
|
1673
|
-
let release;
|
|
1674
|
-
let entered;
|
|
1675
|
-
const gate = new Promise((resolve) => {
|
|
1676
|
-
release = resolve;
|
|
1677
|
-
});
|
|
1678
|
-
const waiting = new Promise((resolve) => {
|
|
1679
|
-
entered = resolve;
|
|
1680
|
-
});
|
|
1681
|
-
mockSignAndSave.mockImplementationOnce(async () => {
|
|
1682
|
-
entered();
|
|
1683
|
-
await gate;
|
|
1684
|
-
});
|
|
1685
|
-
mockGet.mockResolvedValue(selectedVersion({ version: "0.0.1", versionTag: "dev" }));
|
|
1686
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1687
|
-
const reading = jest.fn(() => (0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact));
|
|
1688
|
-
loader._readLocalBundle = reading;
|
|
1689
|
-
try {
|
|
1690
|
-
const installing = loader.loadPackage(pkg, { force: true, localPath: "/pkg" });
|
|
1691
|
-
await waiting;
|
|
1692
|
-
await loader.unloadPackage(packageId);
|
|
1693
|
-
release();
|
|
1694
|
-
await expect(installing).rejects.toThrow(/invalidated/);
|
|
1695
|
-
expect(runtime.install).not.toHaveBeenCalled();
|
|
1696
|
-
expect(loader.getPackageRuntimeState(packageId)?.status).toBe("unloaded");
|
|
1697
|
-
expect(loader.getPackageRuntimeState(packageId)?.lastError).toBeUndefined();
|
|
1698
|
-
}
|
|
1699
|
-
finally {
|
|
1700
|
-
release();
|
|
1701
|
-
await loader.dispose();
|
|
1702
|
-
}
|
|
1703
|
-
});
|
|
1704
|
-
it("publishes isolated worker health and detaches on disposal", async () => {
|
|
1705
|
-
const runtime = makeRuntime();
|
|
1706
|
-
let health = {
|
|
1707
|
-
workerRunning: false,
|
|
1708
|
-
};
|
|
1709
|
-
let notify;
|
|
1710
|
-
const unsubscribe = jest.fn();
|
|
1711
|
-
Object.assign(runtime, {
|
|
1712
|
-
getPackageHealth: jest.fn(() => health),
|
|
1713
|
-
subscribePackageHealth: (listener) => {
|
|
1714
|
-
notify = listener;
|
|
1715
|
-
return unsubscribe;
|
|
1716
|
-
},
|
|
1717
|
-
});
|
|
1718
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1719
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1720
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: "ctx-state-health" });
|
|
1721
|
-
await loader.loadPackage(pkg);
|
|
1722
|
-
const seen = [];
|
|
1723
|
-
loader.runtimeStates.subscribe((states) => seen.push(states[packageId]?.worker));
|
|
1724
|
-
health = { workerRunning: false, lastWorkerError: { message: "boom", at: 1 } };
|
|
1725
|
-
notify(packageId);
|
|
1726
|
-
expect(loader.getPackageRuntimeState(packageId)?.worker).toEqual(health);
|
|
1727
|
-
expect(loader.getPackageRuntimeStates()[packageId]?.worker).toEqual(health);
|
|
1728
|
-
expect(loader.runtimeStates()[packageId]?.worker).toEqual(health);
|
|
1729
|
-
expect(seen).toContainEqual(health);
|
|
1730
|
-
await loader.dispose();
|
|
1731
|
-
expect(unsubscribe).toHaveBeenCalledTimes(1);
|
|
1732
|
-
notify(packageId);
|
|
1733
|
-
expect(loader.runtimeStates()).toEqual({});
|
|
1734
|
-
});
|
|
1735
|
-
it.each([
|
|
1736
|
-
"failure",
|
|
1737
|
-
"unavailable",
|
|
1738
|
-
])("ignores an older load's late %s after a newer load", async (outcome) => {
|
|
1739
|
-
makeRuntime();
|
|
1740
|
-
mockGet.mockResolvedValue(selectedVersion());
|
|
1741
|
-
let finish;
|
|
1742
|
-
let fail;
|
|
1743
|
-
const pending = new Promise((resolve, reject) => {
|
|
1744
|
-
finish = resolve;
|
|
1745
|
-
fail = reject;
|
|
1746
|
-
});
|
|
1747
|
-
mockGetFileContents.mockReturnValueOnce(pending);
|
|
1748
|
-
const loader = new package_loader_1.PackageLoader({ dataContextId: (0, utils_1.newid)() });
|
|
1749
|
-
const older = loader.loadPackage(pkg, { force: true });
|
|
1750
|
-
// Let the older load start its file read before the newer attempt.
|
|
1751
|
-
await Promise.resolve();
|
|
1752
|
-
mockGetFileContents.mockResolvedValue(Buffer.from((0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact)));
|
|
1753
|
-
await loader.loadPackage(pkg, { force: true });
|
|
1754
|
-
if (outcome === "failure")
|
|
1755
|
-
fail(new Error("old failure"));
|
|
1756
|
-
else
|
|
1757
|
-
finish(undefined);
|
|
1758
|
-
await older;
|
|
1759
|
-
expect(loader.getPackageRuntimeState(packageId)?.status).toBe("running");
|
|
1760
|
-
expect(loader.getPackageRuntimeState(packageId)?.lastError).toBeUndefined();
|
|
1761
|
-
await loader.dispose();
|
|
1762
|
-
});
|
|
1763
|
-
});
|
|
1764
|
-
});
|