@peers-app/peers-sdk 0.26.4 → 0.26.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/__tests__/contract-resolvers.test.js +24 -0
- package/dist/contracts/__tests__/legacy-removal.test.js +38 -0
- package/dist/contracts/__tests__/remote-contract-transport.test.js +23 -32
- package/dist/contracts/__tests__/system-contracts.test.js +80 -0
- package/dist/contracts/contract-resolvers.d.ts +1 -1
- package/dist/contracts/contract-resolvers.js +3 -2
- package/dist/contracts/create-local-consumer.d.ts +2 -1
- package/dist/contracts/create-local-consumer.js +1 -1
- package/dist/contracts/index.d.ts +2 -0
- package/dist/contracts/index.js +2 -0
- package/dist/contracts/remote-contract-transport.d.ts +3 -3
- package/dist/contracts/remote-contract-transport.js +19 -17
- package/dist/contracts/system/host-shell-contract.d.ts +191 -0
- package/dist/contracts/system/host-shell-contract.js +190 -0
- package/dist/contracts/system/index.d.ts +4 -0
- package/dist/contracts/system/index.js +4 -0
- package/dist/contracts/system/packages-contract.d.ts +257 -0
- package/dist/contracts/system/packages-contract.js +213 -0
- package/dist/data/create-group.d.ts +1 -1
- package/dist/data/create-group.js +1 -1
- package/dist/data/package-version-resolver.d.ts +8 -2
- package/dist/data/package-version-resolver.js +28 -2
- package/dist/data/package-version-resolver.test.js +42 -0
- package/dist/package-installer/package-author-signer.d.ts +19 -0
- package/dist/package-installer/package-author-signer.js +24 -0
- package/dist/package-installer/package-creator.d.ts +1 -7
- package/dist/package-installer/package-creator.js +0 -12
- package/dist/package-installer/package-creator.test.js +0 -16
- package/dist/package-installer/package-installer.d.ts +5 -0
- package/dist/package-installer/package-installer.js +60 -37
- package/dist/package-installer/package-installer.test.js +129 -0
- package/dist/package-installer/types.d.ts +5 -11
- package/dist/package-loader/index.d.ts +1 -0
- package/dist/package-loader/index.js +1 -0
- package/dist/package-loader/isolated-runtime.d.ts +18 -0
- package/dist/package-loader/package-loader.d.ts +67 -4
- package/dist/package-loader/package-loader.js +300 -52
- package/dist/package-loader/package-loader.test.js +413 -1
- package/dist/package-loader/package-runtime-state.d.ts +112 -0
- package/dist/package-loader/package-runtime-state.js +18 -0
- package/dist/package-loader/resolve-consumed-contracts.js +5 -4
- package/dist/package-loader/resolve-consumed-contracts.test.js +1 -1
- package/dist/rpc-types.d.ts +0 -121
- package/dist/rpc-types.js +0 -39
- package/dist/system-ids.d.ts +13 -0
- package/dist/system-ids.js +14 -1
- package/package.json +1 -1
|
@@ -13,14 +13,22 @@ const files_1 = require("../data/files");
|
|
|
13
13
|
const package_version_resolver_1 = require("../data/package-version-resolver");
|
|
14
14
|
const package_versions_1 = require("../data/package-versions");
|
|
15
15
|
const packages_1 = require("../data/packages");
|
|
16
|
+
const keys_1 = require("../keys");
|
|
17
|
+
const observable_1 = require("../observable");
|
|
16
18
|
const contract_package_loader_1 = require("./contract-package-loader");
|
|
17
19
|
const isolated_artifact_1 = require("./isolated-artifact");
|
|
18
20
|
const isolated_persistent_var_gateway_1 = require("./isolated-persistent-var-gateway");
|
|
19
21
|
const isolated_runtime_1 = require("./isolated-runtime");
|
|
20
22
|
const isolated_table_gateway_1 = require("./isolated-table-gateway");
|
|
23
|
+
const package_runtime_state_1 = require("./package-runtime-state");
|
|
21
24
|
const resolve_consumed_contracts_1 = require("./resolve-consumed-contracts");
|
|
22
25
|
// Ensure built-in system contracts are cataloged before any PackageLoader installs them.
|
|
23
26
|
require("../contracts/system");
|
|
27
|
+
/**
|
|
28
|
+
* Build the trusted install options for an isolated artifact from its selected version.
|
|
29
|
+
*
|
|
30
|
+
* @param packageVersion - Selected PackageVersion; `dev` and `beta` publish mutable contracts.
|
|
31
|
+
*/
|
|
24
32
|
function isolatedContractLifecycle(packageVersion) {
|
|
25
33
|
return {
|
|
26
34
|
contractLifecycle: packageVersion?.versionTag === "dev" || packageVersion?.versionTag === "beta"
|
|
@@ -53,6 +61,17 @@ class PackageLoader {
|
|
|
53
61
|
contractInstalledPackages = new Set();
|
|
54
62
|
/** Packages installed through the isolated runtime (no host eval). */
|
|
55
63
|
isolatedPackageIds = new Set();
|
|
64
|
+
/** Per-package counter bumped on every runtime-state change; lets slow unloads yield. */
|
|
65
|
+
runtimeStateSequences = new Map();
|
|
66
|
+
/** Hash of the bundle each loaded package was built from, for `onlyIfChanged` loads. */
|
|
67
|
+
loadedBundleHashes = new Map();
|
|
68
|
+
unsubscribePackageHealth;
|
|
69
|
+
/**
|
|
70
|
+
* Observed runtime state of each package this loader has attempted, keyed by package id.
|
|
71
|
+
* In memory only; read merged with isolated worker health through
|
|
72
|
+
* {@link getPackageRuntimeState} and {@link getPackageRuntimeStates}.
|
|
73
|
+
*/
|
|
74
|
+
runtimeStates = (0, observable_1.observable)({});
|
|
56
75
|
/**
|
|
57
76
|
* In-memory contract providers for this data context. Seeded with system
|
|
58
77
|
* contracts after catalog readiness; package providers are added via
|
|
@@ -100,6 +119,20 @@ class PackageLoader {
|
|
|
100
119
|
}),
|
|
101
120
|
},
|
|
102
121
|
});
|
|
122
|
+
this.unsubscribePackageHealth = this.isolatedRuntime.subscribePackageHealth?.((packageId) => {
|
|
123
|
+
const previous = this.runtimeStates()[packageId];
|
|
124
|
+
if (!previous || this.disposed)
|
|
125
|
+
return;
|
|
126
|
+
const { worker: _worker, ...state } = previous;
|
|
127
|
+
const worker = state.running?.kind === "isolated"
|
|
128
|
+
? this.isolatedRuntime?.getPackageHealth?.(packageId)
|
|
129
|
+
: undefined;
|
|
130
|
+
// Health does not supersede a load/unload; keep its sequence unchanged.
|
|
131
|
+
this.runtimeStates({
|
|
132
|
+
...this.runtimeStates(),
|
|
133
|
+
[packageId]: { ...state, ...(worker ? { worker } : {}), updatedAt: Date.now() },
|
|
134
|
+
});
|
|
135
|
+
});
|
|
103
136
|
}
|
|
104
137
|
}
|
|
105
138
|
/**
|
|
@@ -123,6 +156,68 @@ class PackageLoader {
|
|
|
123
156
|
supportsIsolatedPackages() {
|
|
124
157
|
return this.isolatedRuntime !== undefined;
|
|
125
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Return the observed runtime state of one package, merged with isolated worker health.
|
|
161
|
+
*
|
|
162
|
+
* @param packageId - Package to query.
|
|
163
|
+
* @returns The state, or `undefined` when this loader has never attempted the package.
|
|
164
|
+
*/
|
|
165
|
+
getPackageRuntimeState(packageId) {
|
|
166
|
+
const state = this.runtimeStates()[packageId];
|
|
167
|
+
if (!state)
|
|
168
|
+
return undefined;
|
|
169
|
+
const worker = state.running?.kind === "isolated"
|
|
170
|
+
? this.isolatedRuntime?.getPackageHealth?.(packageId)
|
|
171
|
+
: undefined;
|
|
172
|
+
return worker ? { ...state, worker } : state;
|
|
173
|
+
}
|
|
174
|
+
/** Return every recorded package runtime state, merged with isolated worker health. */
|
|
175
|
+
getPackageRuntimeStates() {
|
|
176
|
+
return Object.fromEntries(Object.keys(this.runtimeStates()).map((packageId) => [
|
|
177
|
+
packageId,
|
|
178
|
+
this.getPackageRuntimeState(packageId),
|
|
179
|
+
]));
|
|
180
|
+
}
|
|
181
|
+
setRuntimeState(state) {
|
|
182
|
+
this.runtimeStateSequences.set(state.packageId, (this.runtimeStateSequences.get(state.packageId) ?? 0) + 1);
|
|
183
|
+
const worker = state.running?.kind === "isolated"
|
|
184
|
+
? this.isolatedRuntime?.getPackageHealth?.(state.packageId)
|
|
185
|
+
: undefined;
|
|
186
|
+
this.runtimeStates({
|
|
187
|
+
...this.runtimeStates(),
|
|
188
|
+
[state.packageId]: { ...state, ...(worker ? { worker } : {}) },
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
recordRunning(packageId, running) {
|
|
192
|
+
this.setRuntimeState({ packageId, status: "running", running, updatedAt: Date.now() });
|
|
193
|
+
}
|
|
194
|
+
recordStatus(packageId, status, reason) {
|
|
195
|
+
const previous = this.runtimeStates()[packageId];
|
|
196
|
+
this.setRuntimeState({ ...previous, packageId, status, reason, updatedAt: Date.now() });
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Record a failed load attempt unless a newer load or unload superseded it. The previous
|
|
200
|
+
* `running` version is kept: a failed replacement leaves the old version serving.
|
|
201
|
+
*/
|
|
202
|
+
recordLoadFailure(packageId, packageVersion, error, assertCurrent) {
|
|
203
|
+
if (!isCurrent(assertCurrent))
|
|
204
|
+
return;
|
|
205
|
+
const now = Date.now();
|
|
206
|
+
const previous = this.runtimeStates()[packageId];
|
|
207
|
+
this.setRuntimeState({
|
|
208
|
+
packageId,
|
|
209
|
+
status: "failed",
|
|
210
|
+
running: previous?.running,
|
|
211
|
+
lastError: {
|
|
212
|
+
phase: "load",
|
|
213
|
+
message: (0, package_runtime_state_1.runtimeErrorMessage)(error),
|
|
214
|
+
packageVersionId: packageVersion?.packageVersionId,
|
|
215
|
+
version: packageVersion?.version,
|
|
216
|
+
at: now,
|
|
217
|
+
},
|
|
218
|
+
updatedAt: now,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
126
221
|
async dispatchToolFromProvider(packageId, target, args, context) {
|
|
127
222
|
if (!context.authority?.userId ||
|
|
128
223
|
!context.dataContext ||
|
|
@@ -354,9 +449,12 @@ class PackageLoader {
|
|
|
354
449
|
* before any consumer call payload is processed.
|
|
355
450
|
*
|
|
356
451
|
* @param dependency - Validated package consume declaration.
|
|
452
|
+
* @param callerPackageId - Owning package, supplied by the loader rather than call input.
|
|
357
453
|
* @returns A live consumer proxy, or `undefined` when no provider matches.
|
|
358
454
|
*/
|
|
359
|
-
async resolveConsumedContract(dependency) {
|
|
455
|
+
async resolveConsumedContract(dependency, callerPackageId) {
|
|
456
|
+
if (!callerPackageId)
|
|
457
|
+
throw new Error("A package caller identity is required");
|
|
360
458
|
await this.catalogReady;
|
|
361
459
|
const selected = this.contractRegistry.selectProvider(dependency);
|
|
362
460
|
if (!selected) {
|
|
@@ -367,7 +465,7 @@ class PackageLoader {
|
|
|
367
465
|
const router = (0, contract_provider_router_1.createContractProviderRouter)(providerTransport, {
|
|
368
466
|
authorizeCall: () => ({
|
|
369
467
|
dataContextId: this.dataContext.dataContextId,
|
|
370
|
-
context: { dataContext: this.dataContext },
|
|
468
|
+
context: { dataContext: this.dataContext, callerPackageId },
|
|
371
469
|
}),
|
|
372
470
|
resolveProviderPackageId: () => providerPackageId,
|
|
373
471
|
resolveEndpoint: (request) => this.resolveContractEndpoint(request, { providerPackageId }),
|
|
@@ -395,9 +493,12 @@ class PackageLoader {
|
|
|
395
493
|
* installed package) so the proxy knows the tool names.
|
|
396
494
|
*
|
|
397
495
|
* @param declaration - Validated package remote declaration.
|
|
496
|
+
* @param callerPackageId - Owning package, retained for local and remote authorization.
|
|
398
497
|
* @returns A per-device consumer factory, or `undefined` when the contract is unknown.
|
|
399
498
|
*/
|
|
400
|
-
async resolveRemoteContract(declaration) {
|
|
499
|
+
async resolveRemoteContract(declaration, callerPackageId) {
|
|
500
|
+
if (!callerPackageId)
|
|
501
|
+
throw new Error("A package caller identity is required");
|
|
401
502
|
await this.catalogReady;
|
|
402
503
|
const definition = this.contractRegistry.getDefinition(declaration.contractId, declaration.version);
|
|
403
504
|
if (!definition) {
|
|
@@ -407,7 +508,7 @@ class PackageLoader {
|
|
|
407
508
|
const router = (0, contract_provider_router_1.createContractProviderRouter)(hostTransport, {
|
|
408
509
|
authorizeCall: () => ({
|
|
409
510
|
dataContextId: this.dataContext.dataContextId,
|
|
410
|
-
context: { dataContext: this.dataContext },
|
|
511
|
+
context: { dataContext: this.dataContext, callerPackageId },
|
|
411
512
|
}),
|
|
412
513
|
resolveProviderPackageId: (request) => this.contractRegistry.getProviderPackageId(request.contractId, request.version),
|
|
413
514
|
resolveEndpoint: (request) => this.resolveContractEndpoint(request),
|
|
@@ -441,12 +542,17 @@ class PackageLoader {
|
|
|
441
542
|
/**
|
|
442
543
|
* Stop an isolated worker (if any) and drop the in-memory package instance.
|
|
443
544
|
*
|
|
545
|
+
* Records the given runtime status unless a newer load recorded state while the unload
|
|
546
|
+
* was in flight.
|
|
547
|
+
*
|
|
444
548
|
* @param packageId - Package to unload.
|
|
549
|
+
* @param opts - Runtime status and reason to record.
|
|
445
550
|
*/
|
|
446
|
-
async unloadPackage(packageId) {
|
|
551
|
+
async unloadPackage(packageId, opts = {}) {
|
|
447
552
|
// Invalidate loads before the first await, including those still resolving
|
|
448
553
|
// bundles or consumed contracts outside the catalog publication queue.
|
|
449
554
|
this.loadGenerations.set(packageId, (this.loadGenerations.get(packageId) ?? 0) + 1);
|
|
555
|
+
const stateSequence = this.runtimeStateSequences.get(packageId);
|
|
450
556
|
// Uninstall also queues behind an install that has not published yet.
|
|
451
557
|
await this.isolatedRuntime?.uninstall(packageId);
|
|
452
558
|
await this.toolCatalog.removeProvider(packageId, () => {
|
|
@@ -456,13 +562,23 @@ class PackageLoader {
|
|
|
456
562
|
this.isolatedPackageIds.delete(packageId);
|
|
457
563
|
this.contractInstalledPackages.delete(packageId);
|
|
458
564
|
delete this.packageInstances[packageId];
|
|
565
|
+
this.loadedBundleHashes.delete(packageId);
|
|
459
566
|
});
|
|
567
|
+
if (!this.disposed && this.runtimeStateSequences.get(packageId) === stateSequence) {
|
|
568
|
+
this.setRuntimeState({
|
|
569
|
+
packageId,
|
|
570
|
+
status: opts.status ?? "unloaded",
|
|
571
|
+
reason: opts.reason,
|
|
572
|
+
updatedAt: Date.now(),
|
|
573
|
+
});
|
|
574
|
+
}
|
|
460
575
|
}
|
|
461
576
|
/**
|
|
462
577
|
* Dispose the isolated runtime for this data context.
|
|
463
578
|
*/
|
|
464
579
|
async dispose() {
|
|
465
580
|
this.disposed = true;
|
|
581
|
+
this.unsubscribePackageHealth?.();
|
|
466
582
|
for (const router of this.consumedContractRouters)
|
|
467
583
|
router.dispose();
|
|
468
584
|
this.consumedContractRouters.clear();
|
|
@@ -482,6 +598,9 @@ class PackageLoader {
|
|
|
482
598
|
this.isolatedPackageIds.clear();
|
|
483
599
|
this.contractInstalledPackages.clear();
|
|
484
600
|
this.packageInstances = {};
|
|
601
|
+
this.loadedBundleHashes.clear();
|
|
602
|
+
this.runtimeStateSequences.clear();
|
|
603
|
+
this.runtimeStates({});
|
|
485
604
|
}
|
|
486
605
|
retireHostEndpoints(packageId) {
|
|
487
606
|
this.hostActivationGenerations.set(packageId, (this.hostActivationGenerations.get(packageId) ?? 0) + 1);
|
|
@@ -499,7 +618,7 @@ class PackageLoader {
|
|
|
499
618
|
const packages = await (0, packages_1.Packages)(this.dataContext).list();
|
|
500
619
|
await Promise.all(packages.map(async (pkg) => {
|
|
501
620
|
if (pkg.disabled) {
|
|
502
|
-
await this.unloadPackage(pkg.packageId);
|
|
621
|
+
await this.unloadPackage(pkg.packageId, DISABLED_UNLOAD);
|
|
503
622
|
return;
|
|
504
623
|
}
|
|
505
624
|
try {
|
|
@@ -519,63 +638,50 @@ class PackageLoader {
|
|
|
519
638
|
* isolated `dist/package.bundle.js`, that disk artifact wins over a stale
|
|
520
639
|
* stored Files bundle so local-dev packages pick up new contract versions.
|
|
521
640
|
*
|
|
641
|
+
* With `onlyIfChanged`, a forced load is skipped when the package is running and the
|
|
642
|
+
* selected version and bundle are exactly the ones already loaded. A changed release
|
|
643
|
+
* channel updates the reported label and isolated contract maturity without restarting
|
|
644
|
+
* the worker. Sync reconciliation uses this so an echoed or unrelated row
|
|
645
|
+
* change does not restart the worker and fail calls in flight. Explicit reloads omit it.
|
|
646
|
+
*
|
|
522
647
|
* @param pkg - Package row.
|
|
523
|
-
* @param opts - Optional force reload, local path fallback, and PV id.
|
|
648
|
+
* @param opts - Optional force reload, `onlyIfChanged`, local path fallback, and PV id.
|
|
524
649
|
*/
|
|
525
650
|
async loadPackage(pkg, opts) {
|
|
526
651
|
if (pkg.disabled) {
|
|
527
|
-
await this.unloadPackage(pkg.packageId);
|
|
652
|
+
await this.unloadPackage(pkg.packageId, DISABLED_UNLOAD);
|
|
528
653
|
return undefined;
|
|
529
654
|
}
|
|
530
|
-
const assertCurrent = this.guardLoad(pkg.packageId);
|
|
531
655
|
if (this.packageInstances[pkg.packageId] && !opts?.force) {
|
|
532
656
|
return this.packageInstances[pkg.packageId];
|
|
533
657
|
}
|
|
658
|
+
const assertCurrent = this.guardLoad(pkg.packageId);
|
|
534
659
|
const localPath = opts?.localPath;
|
|
535
660
|
let selectedPackageVersion;
|
|
536
661
|
try {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
try {
|
|
543
|
-
const pv = await (0, package_versions_1.PackageVersions)(this.dataContext).get(pvId);
|
|
544
|
-
if (pv) {
|
|
545
|
-
selectedPackageVersion = pv;
|
|
546
|
-
bundleFileId = pv.packageBundleFileId;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
catch {
|
|
550
|
-
/* no version record */
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
if (bundleFileId) {
|
|
554
|
-
const filesTable = (0, files_1.Files)(this.dataContext);
|
|
555
|
-
const bundleBuffer = await filesTable.getFileContents(bundleFileId);
|
|
556
|
-
if (bundleBuffer) {
|
|
557
|
-
bundleCode = Buffer.from(bundleBuffer).toString("utf8");
|
|
558
|
-
}
|
|
559
|
-
else {
|
|
560
|
-
console.warn(`Package bundle file not found for ${pkg.name} (fileId: ${bundleFileId})`);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
else {
|
|
564
|
-
console.warn(`Package ${pkg.name} does not have a bundle file defined.`);
|
|
662
|
+
if (opts?.onlyIfChanged &&
|
|
663
|
+
this.packageInstances[pkg.packageId] &&
|
|
664
|
+
(await this.keepRunningBuild(pkg, opts, assertCurrent))) {
|
|
665
|
+
assertCurrent();
|
|
666
|
+
return this.packageInstances[pkg.packageId];
|
|
565
667
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
668
|
+
assertCurrent();
|
|
669
|
+
const selected = await this.readSelectedBundle(pkg, opts, true);
|
|
670
|
+
selectedPackageVersion = selected.packageVersion;
|
|
671
|
+
const { bundleCode, bundleFileId, usingLocalBundle } = selected;
|
|
672
|
+
if (!bundleCode) {
|
|
673
|
+
if (isCurrent(assertCurrent)) {
|
|
674
|
+
this.recordStatus(pkg.packageId, "unavailable", bundleFileId
|
|
675
|
+
? "The selected version's bundle file is not on this device yet"
|
|
676
|
+
: "No package version with a bundle is selected");
|
|
571
677
|
}
|
|
572
|
-
}
|
|
573
|
-
if (!bundleCode)
|
|
574
678
|
return;
|
|
575
|
-
|
|
679
|
+
}
|
|
680
|
+
return this.rememberBundle(pkg.packageId, bundleCode, await this._loadBundle(pkg, bundleCode, selectedPackageVersion, usingLocalBundle, opts?.force === true, assertCurrent), assertCurrent);
|
|
576
681
|
}
|
|
577
682
|
catch (err) {
|
|
578
683
|
if (isIsolatedArtifactError(err)) {
|
|
684
|
+
this.recordLoadFailure(pkg.packageId, selectedPackageVersion, err, assertCurrent);
|
|
579
685
|
throw err;
|
|
580
686
|
}
|
|
581
687
|
if (localPath && selectedPackageVersion?.versionTag === "dev") {
|
|
@@ -583,20 +689,116 @@ class PackageLoader {
|
|
|
583
689
|
try {
|
|
584
690
|
const localBundleCode = this._readLocalBundle(localPath);
|
|
585
691
|
if (localBundleCode) {
|
|
586
|
-
return await this._loadBundle(pkg, localBundleCode, selectedPackageVersion, true, opts?.force === true, assertCurrent);
|
|
692
|
+
return this.rememberBundle(pkg.packageId, localBundleCode, await this._loadBundle(pkg, localBundleCode, selectedPackageVersion, true, opts?.force === true, assertCurrent), assertCurrent);
|
|
587
693
|
}
|
|
588
694
|
}
|
|
589
695
|
catch (localErr) {
|
|
590
696
|
if (isIsolatedArtifactError(localErr)) {
|
|
697
|
+
this.recordLoadFailure(pkg.packageId, selectedPackageVersion, localErr, assertCurrent);
|
|
591
698
|
throw localErr;
|
|
592
699
|
}
|
|
593
700
|
console.debug(`Could not load local bundle for ${pkg.name}`, localErr);
|
|
594
701
|
}
|
|
595
702
|
}
|
|
703
|
+
this.recordLoadFailure(pkg.packageId, selectedPackageVersion, err, assertCurrent);
|
|
596
704
|
console.debug(`Could not load package bundle for ${pkg.name}`, err);
|
|
597
705
|
return;
|
|
598
706
|
}
|
|
599
707
|
}
|
|
708
|
+
/**
|
|
709
|
+
* Read the bundle a load would use: the selected version's stored file, replaced by the
|
|
710
|
+
* rebuilt local `dist/package.bundle.js` for a dev version with a local path.
|
|
711
|
+
*/
|
|
712
|
+
async readSelectedBundle(pkg, opts, warn) {
|
|
713
|
+
let packageVersion;
|
|
714
|
+
let bundleFileId;
|
|
715
|
+
let bundleCode = "";
|
|
716
|
+
let usingLocalBundle = false;
|
|
717
|
+
const pvId = opts?.packageVersionId ?? pkg.activePackageVersionId;
|
|
718
|
+
if (pvId) {
|
|
719
|
+
try {
|
|
720
|
+
const pv = await (0, package_versions_1.PackageVersions)(this.dataContext).get(pvId);
|
|
721
|
+
if (pv) {
|
|
722
|
+
packageVersion = pv;
|
|
723
|
+
bundleFileId = pv.packageBundleFileId;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
catch {
|
|
727
|
+
/* no version record */
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
if (bundleFileId) {
|
|
731
|
+
const bundleBuffer = await (0, files_1.Files)(this.dataContext).getFileContents(bundleFileId);
|
|
732
|
+
if (bundleBuffer) {
|
|
733
|
+
bundleCode = Buffer.from(bundleBuffer).toString("utf8");
|
|
734
|
+
}
|
|
735
|
+
else if (warn) {
|
|
736
|
+
console.warn(`Package bundle file not found for ${pkg.name} (fileId: ${bundleFileId})`);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
else if (warn) {
|
|
740
|
+
console.warn(`Package ${pkg.name} does not have a bundle file defined.`);
|
|
741
|
+
}
|
|
742
|
+
if (opts?.localPath && packageVersion?.versionTag === "dev") {
|
|
743
|
+
const localBundleCode = this._readLocalBundle(opts.localPath);
|
|
744
|
+
if (localBundleCode) {
|
|
745
|
+
bundleCode = localBundleCode;
|
|
746
|
+
usingLocalBundle = true;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return { packageVersion, bundleFileId, bundleCode, usingLocalBundle };
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* True when the package is running the exact version and bundle a load would select, so an
|
|
753
|
+
* `onlyIfChanged` load can keep it. Promotion refreshes the release-channel label and
|
|
754
|
+
* isolated contract maturity without reloading, since the tag is not part of the bundle.
|
|
755
|
+
*/
|
|
756
|
+
async keepRunningBuild(pkg, opts, assertCurrent) {
|
|
757
|
+
const loadedHash = this.loadedBundleHashes.get(pkg.packageId);
|
|
758
|
+
const state = this.runtimeStates()[pkg.packageId];
|
|
759
|
+
if (!loadedHash || state?.status !== "running" || !state.running)
|
|
760
|
+
return false;
|
|
761
|
+
try {
|
|
762
|
+
const { packageVersion, bundleCode } = await this.readSelectedBundle(pkg, opts, false);
|
|
763
|
+
assertCurrent();
|
|
764
|
+
if (!bundleCode ||
|
|
765
|
+
!packageVersion ||
|
|
766
|
+
packageVersion.packageVersionId !== state.running.packageVersionId ||
|
|
767
|
+
packageVersion.version !== state.running.version ||
|
|
768
|
+
(0, keys_1.hashValue)(bundleCode) !== loadedHash) {
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
if (packageVersion.versionTag !== state.running.versionTag) {
|
|
772
|
+
if (state.running.kind === "isolated") {
|
|
773
|
+
// Promotion must freeze stable contract shapes even though the worker's code
|
|
774
|
+
// stays identical. Older runtime implementations fall back to a normal load.
|
|
775
|
+
if (!this.isolatedRuntime?.updateContractLifecycle)
|
|
776
|
+
return false;
|
|
777
|
+
await this.isolatedRuntime.updateContractLifecycle(pkg.packageId, {
|
|
778
|
+
...isolatedContractLifecycle(packageVersion),
|
|
779
|
+
assertCurrent,
|
|
780
|
+
});
|
|
781
|
+
assertCurrent();
|
|
782
|
+
}
|
|
783
|
+
this.setRuntimeState({
|
|
784
|
+
...this.runtimeStates()[pkg.packageId],
|
|
785
|
+
running: { ...state.running, ...runningVersionFields(packageVersion) },
|
|
786
|
+
updatedAt: Date.now(),
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
return true;
|
|
790
|
+
}
|
|
791
|
+
catch {
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
/** Record the bundle a current, successful load ran, and pass its result through. */
|
|
796
|
+
rememberBundle(packageId, bundleCode, instance, assertCurrent) {
|
|
797
|
+
if (instance && isCurrent(assertCurrent)) {
|
|
798
|
+
this.loadedBundleHashes.set(packageId, (0, keys_1.hashValue)(bundleCode));
|
|
799
|
+
}
|
|
800
|
+
return instance;
|
|
801
|
+
}
|
|
600
802
|
_readLocalBundle(localPath) {
|
|
601
803
|
try {
|
|
602
804
|
const _require = this.require ?? defaultRequire ?? (typeof require === "function" ? require : null);
|
|
@@ -611,7 +813,8 @@ class PackageLoader {
|
|
|
611
813
|
}
|
|
612
814
|
}
|
|
613
815
|
guardLoad(packageId) {
|
|
614
|
-
const generation = this.loadGenerations.get(packageId) ?? 0;
|
|
816
|
+
const generation = (this.loadGenerations.get(packageId) ?? 0) + 1;
|
|
817
|
+
this.loadGenerations.set(packageId, generation);
|
|
615
818
|
return () => {
|
|
616
819
|
if (this.disposed || generation !== (this.loadGenerations.get(packageId) ?? 0))
|
|
617
820
|
throw new Error(`Package load invalidated: ${packageId}`);
|
|
@@ -694,20 +897,24 @@ class PackageLoader {
|
|
|
694
897
|
}
|
|
695
898
|
}
|
|
696
899
|
if (!this.isolatedRuntime) {
|
|
697
|
-
await this.unloadPackage(pkg.packageId
|
|
900
|
+
await this.unloadPackage(pkg.packageId, {
|
|
901
|
+
status: "unsupported",
|
|
902
|
+
reason: "This host has no isolated package runtime",
|
|
903
|
+
});
|
|
698
904
|
console.warn(`[PackageLoader] Skipping isolated package "${pkg.name}" because this host does not provide an isolated runtime.`);
|
|
699
905
|
return undefined;
|
|
700
906
|
}
|
|
701
907
|
assertCurrent();
|
|
702
908
|
const installOptions = { ...isolatedContractLifecycle(selectedPackageVersion), assertCurrent };
|
|
909
|
+
let installed;
|
|
703
910
|
if (this.isolatedRuntime.isInstalled(pkg.packageId)) {
|
|
704
911
|
if (!force) {
|
|
705
912
|
return this.packageInstances[pkg.packageId];
|
|
706
913
|
}
|
|
707
|
-
await this.isolatedRuntime.replace(artifact, installOptions);
|
|
914
|
+
installed = await this.isolatedRuntime.replace(artifact, installOptions);
|
|
708
915
|
}
|
|
709
916
|
else {
|
|
710
|
-
await this.isolatedRuntime.install(artifact, installOptions);
|
|
917
|
+
installed = await this.isolatedRuntime.install(artifact, installOptions);
|
|
711
918
|
}
|
|
712
919
|
assertCurrent();
|
|
713
920
|
this.retireHostEndpoints(pkg.packageId);
|
|
@@ -719,6 +926,13 @@ class PackageLoader {
|
|
|
719
926
|
appNavs: artifact.manifest.appNavs,
|
|
720
927
|
};
|
|
721
928
|
this.packageInstances[pkg.packageId] = instance;
|
|
929
|
+
this.recordRunning(pkg.packageId, {
|
|
930
|
+
...runningVersionFields(selectedPackageVersion),
|
|
931
|
+
kind: "isolated",
|
|
932
|
+
executionMode: artifact.manifest.executionMode ?? "sandbox",
|
|
933
|
+
provides: installed?.provided,
|
|
934
|
+
consumes: installed?.consumes,
|
|
935
|
+
});
|
|
722
936
|
return instance;
|
|
723
937
|
}
|
|
724
938
|
async _evaluateBundle(pkg, bundleCode, selectedPackageVersion, force, assertCurrent = this.guardLoad(pkg.packageId)) {
|
|
@@ -813,6 +1027,10 @@ class PackageLoader {
|
|
|
813
1027
|
this.hostConsumes.set(packageDefinition.packageId, packageDefinition.consumes);
|
|
814
1028
|
this.contractInstalledPackages.add(packageDefinition.packageId);
|
|
815
1029
|
this.packageInstances[pkg.packageId] = contractPackageInstance;
|
|
1030
|
+
this.recordRunning(pkg.packageId, {
|
|
1031
|
+
...runningVersionFields(selectedPackageVersion),
|
|
1032
|
+
kind: "contract",
|
|
1033
|
+
});
|
|
816
1034
|
},
|
|
817
1035
|
});
|
|
818
1036
|
// A package may declare remote/consume handles for its own contracts.
|
|
@@ -824,7 +1042,10 @@ class PackageLoader {
|
|
|
824
1042
|
}
|
|
825
1043
|
// Legacy path: extract IPeersPackage for backward compat
|
|
826
1044
|
const packageInstance = bundleExports?.exports || bundleExports?.package || bundleExports?.default || bundleExports;
|
|
827
|
-
if (packageInstance &&
|
|
1045
|
+
if (packageInstance &&
|
|
1046
|
+
typeof packageInstance === "object" &&
|
|
1047
|
+
typeof packageInstance.packageId === "string" &&
|
|
1048
|
+
packageInstance.packageId.length > 0) {
|
|
828
1049
|
const tools = packageInstance.toolInstances;
|
|
829
1050
|
if (Array.isArray(tools) && tools.length)
|
|
830
1051
|
throw new Error(`Package '${pkg.name}' (${pkg.packageId}) ships legacy executable tools, which this runtime no longer runs; upgrade it to definePackage() contract tools (docs/Packages/contracts.md#migrating-from-toolinstances)`);
|
|
@@ -841,12 +1062,17 @@ class PackageLoader {
|
|
|
841
1062
|
this.contractInstalledPackages.delete(pkg.packageId);
|
|
842
1063
|
}
|
|
843
1064
|
this.packageInstances[pkg.packageId] = packageInstance;
|
|
1065
|
+
this.recordRunning(pkg.packageId, {
|
|
1066
|
+
...runningVersionFields(selectedPackageVersion),
|
|
1067
|
+
kind: "legacy",
|
|
1068
|
+
});
|
|
844
1069
|
});
|
|
845
1070
|
return packageInstance;
|
|
846
1071
|
}
|
|
847
|
-
|
|
1072
|
+
throw new Error("Package bundle exports nothing usable");
|
|
848
1073
|
}
|
|
849
1074
|
catch (err) {
|
|
1075
|
+
this.recordLoadFailure(pkg.packageId, selectedPackageVersion, err, assertCurrent);
|
|
850
1076
|
console.warn(`[PackageLoader] Failed to evaluate bundle for "${pkg.name}":`, err.message);
|
|
851
1077
|
return undefined;
|
|
852
1078
|
}
|
|
@@ -854,6 +1080,28 @@ class PackageLoader {
|
|
|
854
1080
|
}
|
|
855
1081
|
exports.PackageLoader = PackageLoader;
|
|
856
1082
|
const ISOLATED_ARTIFACT_ERROR = "IsolatedPackageArtifactError";
|
|
1083
|
+
const DISABLED_UNLOAD = {
|
|
1084
|
+
status: "disabled",
|
|
1085
|
+
reason: "The package is disabled in this group",
|
|
1086
|
+
};
|
|
1087
|
+
/** True when a load guard still holds (no newer unload or disposal). */
|
|
1088
|
+
function isCurrent(assertCurrent) {
|
|
1089
|
+
try {
|
|
1090
|
+
assertCurrent();
|
|
1091
|
+
return true;
|
|
1092
|
+
}
|
|
1093
|
+
catch {
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
/** Copy the identifying fields of the loaded PackageVersion into a running-version record. */
|
|
1098
|
+
function runningVersionFields(packageVersion) {
|
|
1099
|
+
return {
|
|
1100
|
+
packageVersionId: packageVersion?.packageVersionId,
|
|
1101
|
+
version: packageVersion?.version,
|
|
1102
|
+
versionTag: packageVersion?.versionTag,
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
857
1105
|
function wrapIsolatedArtifactError(err) {
|
|
858
1106
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
859
1107
|
error.name = ISOLATED_ARTIFACT_ERROR;
|