@peers-app/peers-sdk 0.23.9 → 0.23.11
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/context/user-context-singleton.js +19 -4
- package/dist/contracts/__tests__/builder.test.js +25 -0
- package/dist/contracts/__tests__/contract-provider-router.test.js +45 -0
- package/dist/contracts/__tests__/contract-proxy.test.js +67 -0
- package/dist/contracts/__tests__/contract-resolvers.test.js +133 -0
- package/dist/contracts/__tests__/remote-contract-transport.test.d.ts +1 -0
- package/dist/contracts/__tests__/remote-contract-transport.test.js +89 -0
- package/dist/contracts/builder.d.ts +47 -1
- package/dist/contracts/builder.js +80 -22
- package/dist/contracts/contract-provider-router.d.ts +6 -0
- package/dist/contracts/contract-provider-router.js +25 -3
- package/dist/contracts/contract-proxy.d.ts +41 -1
- package/dist/contracts/contract-proxy.js +103 -0
- package/dist/contracts/contract-resolvers.d.ts +47 -13
- package/dist/contracts/contract-resolvers.js +125 -35
- package/dist/contracts/extract.d.ts +4 -1
- package/dist/contracts/extract.js +3 -1
- package/dist/contracts/index.d.ts +4 -3
- package/dist/contracts/index.js +10 -2
- package/dist/contracts/remote-contract-transport.d.ts +69 -0
- package/dist/contracts/remote-contract-transport.js +95 -0
- package/dist/contracts/types.d.ts +57 -0
- package/dist/data/persistent-vars.d.ts +8 -0
- package/dist/data/persistent-vars.js +56 -11
- package/dist/data/tool-access-approvals.d.ts +2 -2
- package/dist/data/tools.d.ts +4 -0
- package/dist/data/tools.js +7 -0
- package/dist/package-loader/isolated-artifact.js +16 -1
- package/dist/package-loader/isolated-runtime.d.ts +5 -2
- package/dist/package-loader/package-loader.d.ts +15 -1
- package/dist/package-loader/package-loader.js +48 -0
- package/dist/package-loader/resolve-consumed-contracts.d.ts +6 -2
- package/dist/package-loader/resolve-consumed-contracts.js +30 -2
- package/dist/package-loader/resolve-consumed-contracts.test.js +43 -0
- package/package.json +1 -1
|
@@ -277,26 +277,71 @@ function persistentVarFactory(name, opts) {
|
|
|
277
277
|
void (async () => {
|
|
278
278
|
try {
|
|
279
279
|
const userContext = opts?.userContext || (await (0, user_context_singleton_1.getUserContext)());
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
280
|
+
/**
|
|
281
|
+
* The data context whose `PersistentVars` table stores this variable.
|
|
282
|
+
*
|
|
283
|
+
* Only `group` scope lives in a group DB. Every other scope lives in the
|
|
284
|
+
* user's personal DB; for `groupDevice` / `groupUser` the route context
|
|
285
|
+
* (`opts.dataContext` or the current default group) only disambiguates
|
|
286
|
+
* the stored name (see {@link getVarNameInDb}). Storing the record in
|
|
287
|
+
* the personal DB regardless of whether a caller pinned a `dataContext`
|
|
288
|
+
* keeps server-side callers (which pin) and renderer callers (which
|
|
289
|
+
* don't) reading and writing the same row.
|
|
290
|
+
*/
|
|
291
|
+
function getStorageDataContext() {
|
|
284
292
|
if (scope === "group") {
|
|
285
|
-
return userContext.defaultDataContext();
|
|
293
|
+
return opts?.dataContext ?? userContext.defaultDataContext();
|
|
294
|
+
}
|
|
295
|
+
return userContext.userDataContext;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Before storage was unified in the personal DB, a `groupDevice` /
|
|
299
|
+
* `groupUser` var created with an explicit group `dataContext` was
|
|
300
|
+
* written to that group's DB. Return that legacy location so an
|
|
301
|
+
* existing value can be migrated forward, or `undefined` when there is
|
|
302
|
+
* no distinct legacy location for this variable.
|
|
303
|
+
*/
|
|
304
|
+
function getLegacyStorageDataContext() {
|
|
305
|
+
if (scope !== "groupDevice" && scope !== "groupUser") {
|
|
306
|
+
return undefined;
|
|
286
307
|
}
|
|
287
|
-
|
|
288
|
-
|
|
308
|
+
const pinned = opts?.dataContext;
|
|
309
|
+
if (!pinned || pinned.dataContextId === userContext.userDataContext.dataContextId) {
|
|
310
|
+
return undefined;
|
|
289
311
|
}
|
|
312
|
+
return pinned;
|
|
290
313
|
}
|
|
291
314
|
function getVarNameInDb() {
|
|
292
315
|
const dc = opts?.dataContext || userContext.defaultDataContext();
|
|
293
316
|
return (0, persistent_var_identity_1.persistentVarStorageName)(name, scope, dc?.dataContextId);
|
|
294
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Copy a record left in a group DB by the pre-unification storage rule
|
|
320
|
+
* into the personal DB. Secrets are skipped because their ciphertext is
|
|
321
|
+
* bound to the group key and cannot be moved verbatim. The legacy row is
|
|
322
|
+
* left in place: older builds on other devices may still read it, and
|
|
323
|
+
* the personal copy is authoritative from here on.
|
|
324
|
+
*/
|
|
325
|
+
async function migrateLegacyRec(table, name) {
|
|
326
|
+
const legacyContext = getLegacyStorageDataContext();
|
|
327
|
+
if (!legacyContext) {
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
const legacyRec = await PersistentVars(legacyContext).findOne({ name });
|
|
331
|
+
if (!legacyRec || legacyRec.scope !== scope || legacyRec.isSecret) {
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
const { persistentVarId: _legacyId, ...rest } = legacyRec;
|
|
335
|
+
return table.save({ ...rest, persistentVarId: "" }, { weakInsert: true });
|
|
336
|
+
}
|
|
295
337
|
async function loadRecFromDb() {
|
|
296
|
-
const dc =
|
|
338
|
+
const dc = getStorageDataContext();
|
|
297
339
|
const table = PersistentVars(dc);
|
|
298
340
|
const name = getVarNameInDb();
|
|
299
341
|
let dbRec = await table.findOne({ name });
|
|
342
|
+
if (!dbRec) {
|
|
343
|
+
dbRec = await migrateLegacyRec(table, name);
|
|
344
|
+
}
|
|
300
345
|
if (dbRec) {
|
|
301
346
|
if (dbRec.scope !== scope) {
|
|
302
347
|
console.warn(`${name}: deleting old persistent var record because scopes don't match. If this is unexpected there could be two different persistent variables using the same name`);
|
|
@@ -340,7 +385,7 @@ function persistentVarFactory(name, opts) {
|
|
|
340
385
|
return;
|
|
341
386
|
}
|
|
342
387
|
rec.value.value = value;
|
|
343
|
-
const dc =
|
|
388
|
+
const dc = getStorageDataContext();
|
|
344
389
|
const table = PersistentVars(dc);
|
|
345
390
|
try {
|
|
346
391
|
rec = await table.save(rec, { weakInsert: true });
|
|
@@ -362,7 +407,7 @@ function persistentVarFactory(name, opts) {
|
|
|
362
407
|
}
|
|
363
408
|
}
|
|
364
409
|
deleteVarImpl = async () => {
|
|
365
|
-
const dc =
|
|
410
|
+
const dc = getStorageDataContext();
|
|
366
411
|
const table = PersistentVars(dc);
|
|
367
412
|
if (rec?.persistentVarId) {
|
|
368
413
|
await table.delete(rec);
|
|
@@ -374,7 +419,7 @@ function persistentVarFactory(name, opts) {
|
|
|
374
419
|
};
|
|
375
420
|
// subscribe to db changes
|
|
376
421
|
userContext.subscribeToDataChangedAcrossAllGroups(exports.persistentVarsMetaData.name, async (evt) => {
|
|
377
|
-
const dc =
|
|
422
|
+
const dc = getStorageDataContext();
|
|
378
423
|
if (dc && evt.dataContext && evt.dataContext.dataContextId !== dc.dataContextId) {
|
|
379
424
|
return;
|
|
380
425
|
}
|
|
@@ -50,8 +50,8 @@ export declare const toolAccessApprovalSchema: z.ZodObject<{
|
|
|
50
50
|
userId: string;
|
|
51
51
|
expiresAt: Date;
|
|
52
52
|
toolId: string;
|
|
53
|
-
createdAt: Date;
|
|
54
53
|
dataContextId: string;
|
|
54
|
+
createdAt: Date;
|
|
55
55
|
toolAccessApprovalId: string;
|
|
56
56
|
toolName: string;
|
|
57
57
|
argsHash: string;
|
|
@@ -106,8 +106,8 @@ export declare function ToolAccessApprovals(dataContext?: DataContext): import("
|
|
|
106
106
|
userId: string;
|
|
107
107
|
expiresAt: Date;
|
|
108
108
|
toolId: string;
|
|
109
|
-
createdAt: Date;
|
|
110
109
|
dataContextId: string;
|
|
110
|
+
createdAt: Date;
|
|
111
111
|
toolAccessApprovalId: string;
|
|
112
112
|
toolName: string;
|
|
113
113
|
argsHash: string;
|
package/dist/data/tools.d.ts
CHANGED
|
@@ -220,6 +220,7 @@ export declare const toolSchema: z.ZodObject<{
|
|
|
220
220
|
isAssistantRunner: z.ZodOptional<z.ZodBoolean>;
|
|
221
221
|
packageId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
222
222
|
accessLevel: z.ZodDefault<z.ZodNumber>;
|
|
223
|
+
remoteAccessLevel: z.ZodOptional<z.ZodNumber>;
|
|
223
224
|
}, "strip", z.ZodTypeAny, {
|
|
224
225
|
code: string;
|
|
225
226
|
name: string;
|
|
@@ -266,6 +267,7 @@ export declare const toolSchema: z.ZodObject<{
|
|
|
266
267
|
codeRepository?: string | undefined;
|
|
267
268
|
isAssistantRunner?: boolean | undefined;
|
|
268
269
|
packageId?: string | undefined;
|
|
270
|
+
remoteAccessLevel?: number | undefined;
|
|
269
271
|
}, {
|
|
270
272
|
name: string;
|
|
271
273
|
toolId: string;
|
|
@@ -312,6 +314,7 @@ export declare const toolSchema: z.ZodObject<{
|
|
|
312
314
|
isAssistantRunner?: boolean | undefined;
|
|
313
315
|
packageId?: string | undefined;
|
|
314
316
|
accessLevel?: number | undefined;
|
|
317
|
+
remoteAccessLevel?: number | undefined;
|
|
315
318
|
}>;
|
|
316
319
|
/** A tool definition stored in the Tools table. */
|
|
317
320
|
export type ITool = z.infer<typeof toolSchema>;
|
|
@@ -373,4 +376,5 @@ export declare function Tools(dataContext?: DataContext): import("..").Table<{
|
|
|
373
376
|
codeRepository?: string | undefined;
|
|
374
377
|
isAssistantRunner?: boolean | undefined;
|
|
375
378
|
packageId?: string | undefined;
|
|
379
|
+
remoteAccessLevel?: number | undefined;
|
|
376
380
|
}>;
|
package/dist/data/tools.js
CHANGED
|
@@ -49,6 +49,13 @@ exports.toolSchema = zod_1.z.object({
|
|
|
49
49
|
.describe("Whether this tool is a model that can be used to run an assistant"),
|
|
50
50
|
packageId: zod_types_1.zodPeerId.optional().describe("The package that installed this tool, if any"),
|
|
51
51
|
accessLevel: access_level_1.accessLevelSchema.describe("Required access level (0-100) to run this tool. Unclassified tools default to 100 (Self)."),
|
|
52
|
+
remoteAccessLevel: zod_1.z
|
|
53
|
+
.number()
|
|
54
|
+
.int()
|
|
55
|
+
.min(access_level_1.MIN_ACCESS_LEVEL)
|
|
56
|
+
.max(access_level_1.MAX_ACCESS_LEVEL)
|
|
57
|
+
.optional()
|
|
58
|
+
.describe("Minimum group access level (0-100) a caller on another device needs to run this tool. Omit to keep the tool local-only."),
|
|
52
59
|
});
|
|
53
60
|
const metaData = {
|
|
54
61
|
name: "Tools",
|
|
@@ -228,7 +228,14 @@ function validateProvidedContract(contract, index, packageTables) {
|
|
|
228
228
|
if (!tool || typeof tool !== "object" || Array.isArray(tool)) {
|
|
229
229
|
throw new Error(`${toolLabel} must be an object`);
|
|
230
230
|
}
|
|
231
|
-
assertOnlyKeys(tool, [
|
|
231
|
+
assertOnlyKeys(tool, [
|
|
232
|
+
"name",
|
|
233
|
+
"usageDescription",
|
|
234
|
+
"inputFields",
|
|
235
|
+
"outputFields",
|
|
236
|
+
"suggestedAccessLevel",
|
|
237
|
+
"remoteAccessLevel",
|
|
238
|
+
], toolLabel);
|
|
232
239
|
if (typeof tool.name !== "string" || tool.name.trim() === "") {
|
|
233
240
|
throw new Error(`${toolLabel}.name is required`);
|
|
234
241
|
}
|
|
@@ -248,6 +255,13 @@ function validateProvidedContract(contract, index, packageTables) {
|
|
|
248
255
|
throw new Error(`${toolLabel}.suggestedAccessLevel must be an integer 0–100`);
|
|
249
256
|
}
|
|
250
257
|
}
|
|
258
|
+
if (tool.remoteAccessLevel !== undefined) {
|
|
259
|
+
if (!Number.isInteger(tool.remoteAccessLevel) ||
|
|
260
|
+
tool.remoteAccessLevel < access_level_1.MIN_ACCESS_LEVEL ||
|
|
261
|
+
tool.remoteAccessLevel > access_level_1.MAX_ACCESS_LEVEL) {
|
|
262
|
+
throw new Error(`${toolLabel}.remoteAccessLevel must be an integer 0–100`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
251
265
|
}
|
|
252
266
|
if (contract.tables === undefined) {
|
|
253
267
|
contract.tables = [];
|
|
@@ -530,6 +544,7 @@ function isolatedContractToDefinition(contract, packageTables) {
|
|
|
530
544
|
usageDescription: tool.usageDescription,
|
|
531
545
|
inputFields: tool.inputFields ?? [],
|
|
532
546
|
outputFields: tool.outputFields ?? [],
|
|
547
|
+
...(tool.remoteAccessLevel !== undefined ? { remoteAccessLevel: tool.remoteAccessLevel } : {}),
|
|
533
548
|
}));
|
|
534
549
|
const tables = (contract.tables ?? []).map((tableRef) => {
|
|
535
550
|
const table = findPackageTable(packageTables, tableRef.name);
|
|
@@ -168,8 +168,11 @@ export interface IIsolatedPackagePersistentVarGateway {
|
|
|
168
168
|
export interface IIsolatedContractProviderEndpointRequest extends IContractProviderEndpointRequest {
|
|
169
169
|
/** Installed isolated package making the consumed-contract call. */
|
|
170
170
|
consumerPackageId: string;
|
|
171
|
-
/**
|
|
172
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Registry-selected provider package. Omitted for a device-targeted route
|
|
173
|
+
* (`targetDeviceId` set), where the executing device selects the provider.
|
|
174
|
+
*/
|
|
175
|
+
providerPackageId?: string;
|
|
173
176
|
/** Optional deadline the gateway should enforce for provider setup/calls. */
|
|
174
177
|
timeoutMs?: number;
|
|
175
178
|
}
|
|
@@ -2,7 +2,7 @@ import type { DataContext } from "../context/data-context";
|
|
|
2
2
|
import { type IContractProviderEndpointRequest } from "../contracts/contract-provider-router";
|
|
3
3
|
import { type IContractConsumer, type IContractProviderEndpoint } from "../contracts/contract-proxy";
|
|
4
4
|
import { ContractRegistry } from "../contracts/registry";
|
|
5
|
-
import type { IConsumedContract } from "../contracts/types";
|
|
5
|
+
import type { IConsumedContract, IRemoteContract, IRemoteContractConsumer } from "../contracts/types";
|
|
6
6
|
import { type IPackage } from "../data/packages";
|
|
7
7
|
import type { IPeersPackage } from "../types/peers-package";
|
|
8
8
|
import { type IIsolatedPackageRuntime } from "./isolated-runtime";
|
|
@@ -96,6 +96,20 @@ export declare class PackageLoader {
|
|
|
96
96
|
* @returns A live consumer proxy, or `undefined` when no provider matches.
|
|
97
97
|
*/
|
|
98
98
|
resolveConsumedContract(dependency: IConsumedContract): Promise<IContractConsumer | undefined>;
|
|
99
|
+
/**
|
|
100
|
+
* Bind a legacy package's `remote()` declaration to a per-device consumer factory.
|
|
101
|
+
*
|
|
102
|
+
* No local provider is selected: every device consumer stamps `targetDeviceId` and
|
|
103
|
+
* this loader's own router forwards the call through
|
|
104
|
+
* {@link resolveContractEndpoint}, which hands remote targets to the mesh transport
|
|
105
|
+
* and resolves this device's id locally. The contract definition must already be
|
|
106
|
+
* registered in this context (from the package's own `provides` or another
|
|
107
|
+
* installed package) so the proxy knows the tool names.
|
|
108
|
+
*
|
|
109
|
+
* @param declaration - Validated package remote declaration.
|
|
110
|
+
* @returns A per-device consumer factory, or `undefined` when the contract is unknown.
|
|
111
|
+
*/
|
|
112
|
+
resolveRemoteContract(declaration: IRemoteContract): Promise<IRemoteContractConsumer<IContractConsumer> | undefined>;
|
|
99
113
|
/**
|
|
100
114
|
* Resolve a sandbox-backed provider endpoint when this context owns an
|
|
101
115
|
* isolated provider for the requested contract.
|
|
@@ -7,6 +7,7 @@ const contract_proxy_1 = require("../contracts/contract-proxy");
|
|
|
7
7
|
const contract_resolvers_1 = require("../contracts/contract-resolvers");
|
|
8
8
|
const contract_transport_1 = require("../contracts/contract-transport");
|
|
9
9
|
const registry_1 = require("../contracts/registry");
|
|
10
|
+
const remote_contract_transport_1 = require("../contracts/remote-contract-transport");
|
|
10
11
|
const system_contracts_1 = require("../contracts/system-contracts");
|
|
11
12
|
const files_1 = require("../data/files");
|
|
12
13
|
const package_version_resolver_1 = require("../data/package-version-resolver");
|
|
@@ -132,6 +133,16 @@ class PackageLoader {
|
|
|
132
133
|
* @throws If no provider or matching endpoint implementation is available.
|
|
133
134
|
*/
|
|
134
135
|
async resolveContractEndpoint(request, opts = {}) {
|
|
136
|
+
// A route that names another device is forwarded over the mesh instead of being
|
|
137
|
+
// resolved against a local provider. Only routers serving local principals set
|
|
138
|
+
// targetDeviceId; remote-caller routers reject it before reaching this point.
|
|
139
|
+
const remoteEndpoint = (0, remote_contract_transport_1.resolveRemoteTargetEndpoint)(request, {
|
|
140
|
+
localDeviceId: this.dataContext.userContext?.deviceId(),
|
|
141
|
+
userContext: this.dataContext.userContext,
|
|
142
|
+
});
|
|
143
|
+
if (remoteEndpoint) {
|
|
144
|
+
return remoteEndpoint;
|
|
145
|
+
}
|
|
135
146
|
const providerPackageId = opts.providerPackageId ??
|
|
136
147
|
request.providerPackageId ??
|
|
137
148
|
this.contractRegistry.getProviderPackageId(request.contractId, request.version);
|
|
@@ -244,6 +255,43 @@ class PackageLoader {
|
|
|
244
255
|
};
|
|
245
256
|
return consumer;
|
|
246
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Bind a legacy package's `remote()` declaration to a per-device consumer factory.
|
|
260
|
+
*
|
|
261
|
+
* No local provider is selected: every device consumer stamps `targetDeviceId` and
|
|
262
|
+
* this loader's own router forwards the call through
|
|
263
|
+
* {@link resolveContractEndpoint}, which hands remote targets to the mesh transport
|
|
264
|
+
* and resolves this device's id locally. The contract definition must already be
|
|
265
|
+
* registered in this context (from the package's own `provides` or another
|
|
266
|
+
* installed package) so the proxy knows the tool names.
|
|
267
|
+
*
|
|
268
|
+
* @param declaration - Validated package remote declaration.
|
|
269
|
+
* @returns A per-device consumer factory, or `undefined` when the contract is unknown.
|
|
270
|
+
*/
|
|
271
|
+
async resolveRemoteContract(declaration) {
|
|
272
|
+
const definition = this.contractRegistry.getDefinition(declaration.contractId, declaration.version);
|
|
273
|
+
if (!definition) {
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
const [consumerTransport, hostTransport] = (0, contract_transport_1.inProcessTransportPair)();
|
|
277
|
+
const router = (0, contract_provider_router_1.createContractProviderRouter)(hostTransport, {
|
|
278
|
+
authorizeCall: () => ({
|
|
279
|
+
dataContextId: this.dataContext.dataContextId,
|
|
280
|
+
context: { dataContext: this.dataContext },
|
|
281
|
+
}),
|
|
282
|
+
resolveProviderPackageId: (request) => this.contractRegistry.getProviderPackageId(request.contractId, request.version),
|
|
283
|
+
resolveEndpoint: (request) => this.resolveContractEndpoint(request),
|
|
284
|
+
});
|
|
285
|
+
const remote = (0, contract_proxy_1.createRemoteContractConsumer)(definition, consumerTransport, {
|
|
286
|
+
dataContextId: this.dataContext.dataContextId,
|
|
287
|
+
});
|
|
288
|
+
const dispose = remote.dispose.bind(remote);
|
|
289
|
+
remote.dispose = () => {
|
|
290
|
+
dispose();
|
|
291
|
+
router.dispose();
|
|
292
|
+
};
|
|
293
|
+
return remote;
|
|
294
|
+
}
|
|
247
295
|
/**
|
|
248
296
|
* Resolve a sandbox-backed provider endpoint when this context owns an
|
|
249
297
|
* isolated provider for the requested contract.
|
|
@@ -2,12 +2,16 @@ import type { DataContext } from "../context/data-context";
|
|
|
2
2
|
import type { ContractRegistry } from "../contracts/registry";
|
|
3
3
|
import type { IPackageDefinitionResult } from "../contracts/types";
|
|
4
4
|
/**
|
|
5
|
-
* Settle each deferred `consumes` handle on a package definition
|
|
6
|
-
* providers have been registered.
|
|
5
|
+
* Settle each deferred `consumes` and `remote` handle on a package definition
|
|
6
|
+
* after its providers have been registered.
|
|
7
7
|
*
|
|
8
8
|
* A consume succeeds only when {@link ContractRegistry.selectProvider} finds a
|
|
9
9
|
* matching provider and the owning PackageLoader can bind an endpoint. The
|
|
10
10
|
* endpoint may be resolver-backed or isolated. Missing optional deps settle to
|
|
11
11
|
* `undefined`; missing required deps reject the handle.
|
|
12
|
+
*
|
|
13
|
+
* A remote declaration needs no local provider: it settles to a per-device
|
|
14
|
+
* consumer factory as long as the contract definition is registered locally and
|
|
15
|
+
* a PackageLoader owns the context; otherwise the handle rejects.
|
|
12
16
|
*/
|
|
13
17
|
export declare function resolveConsumedContracts(dataContext: DataContext, registry: ContractRegistry, packageDefinition: IPackageDefinitionResult): Promise<void>;
|
|
@@ -5,15 +5,20 @@ const builder_1 = require("../contracts/builder");
|
|
|
5
5
|
const contract_resolvers_1 = require("../contracts/contract-resolvers");
|
|
6
6
|
const create_local_consumer_1 = require("../contracts/create-local-consumer");
|
|
7
7
|
/**
|
|
8
|
-
* Settle each deferred `consumes` handle on a package definition
|
|
9
|
-
* providers have been registered.
|
|
8
|
+
* Settle each deferred `consumes` and `remote` handle on a package definition
|
|
9
|
+
* after its providers have been registered.
|
|
10
10
|
*
|
|
11
11
|
* A consume succeeds only when {@link ContractRegistry.selectProvider} finds a
|
|
12
12
|
* matching provider and the owning PackageLoader can bind an endpoint. The
|
|
13
13
|
* endpoint may be resolver-backed or isolated. Missing optional deps settle to
|
|
14
14
|
* `undefined`; missing required deps reject the handle.
|
|
15
|
+
*
|
|
16
|
+
* A remote declaration needs no local provider: it settles to a per-device
|
|
17
|
+
* consumer factory as long as the contract definition is registered locally and
|
|
18
|
+
* a PackageLoader owns the context; otherwise the handle rejects.
|
|
15
19
|
*/
|
|
16
20
|
async function resolveConsumedContracts(dataContext, registry, packageDefinition) {
|
|
21
|
+
await resolveRemoteContracts(dataContext, packageDefinition);
|
|
17
22
|
for (const dep of packageDefinition.consumes) {
|
|
18
23
|
const handle = dep.handle;
|
|
19
24
|
if (!handle) {
|
|
@@ -44,6 +49,29 @@ async function resolveConsumedContracts(dataContext, registry, packageDefinition
|
|
|
44
49
|
}
|
|
45
50
|
}
|
|
46
51
|
}
|
|
52
|
+
async function resolveRemoteContracts(dataContext, packageDefinition) {
|
|
53
|
+
for (const declaration of packageDefinition.remotes ?? []) {
|
|
54
|
+
const handle = declaration.handle;
|
|
55
|
+
if (!handle) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const key = `${declaration.contractId}@${declaration.version}`;
|
|
59
|
+
try {
|
|
60
|
+
const loader = dataContext.packageLoader;
|
|
61
|
+
if (!loader?.resolveRemoteContract) {
|
|
62
|
+
throw new Error(`Remote contract ${key} requires a package loader on the data context`);
|
|
63
|
+
}
|
|
64
|
+
const remote = await loader.resolveRemoteContract(declaration);
|
|
65
|
+
if (!remote) {
|
|
66
|
+
throw new Error(`Remote contract ${key} could not be resolved: its definition is not registered on this device`);
|
|
67
|
+
}
|
|
68
|
+
(0, builder_1.settleContractHandle)(handle, remote);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
(0, builder_1.rejectContractHandle)(handle, err);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
47
75
|
async function resolveLegacyLocalConsumer(dataContext, selected, contractId, version) {
|
|
48
76
|
const resolver = (0, contract_resolvers_1.getContractResolver)(contractId, version, selected.providerPackageId);
|
|
49
77
|
if (!resolver) {
|
|
@@ -120,4 +120,47 @@ describe("resolveConsumedContracts", () => {
|
|
|
120
120
|
}
|
|
121
121
|
await expect(consumeHandle.consumer).resolves.toBeUndefined();
|
|
122
122
|
});
|
|
123
|
+
describe("remote declarations", () => {
|
|
124
|
+
it("settles a remote handle through the package loader without a local provider", async () => {
|
|
125
|
+
const remote = { contractId, version: 1, device: jest.fn(), dispose: jest.fn() };
|
|
126
|
+
const resolveRemoteContract = jest.fn().mockResolvedValue(remote);
|
|
127
|
+
const dataContext = {
|
|
128
|
+
dataContextId: "test-ctx",
|
|
129
|
+
packageLoader: { resolveRemoteContract },
|
|
130
|
+
};
|
|
131
|
+
let remoteHandle;
|
|
132
|
+
const packageDefinition = (0, builder_1.definePackage)((pkg) => {
|
|
133
|
+
pkg.packageId = (0, utils_1.newid)();
|
|
134
|
+
pkg.version = "1.0.0";
|
|
135
|
+
remoteHandle = pkg.remote(contractId, 1);
|
|
136
|
+
});
|
|
137
|
+
await (0, resolve_consumed_contracts_1.resolveConsumedContracts)(dataContext, new registry_1.ContractRegistry(), packageDefinition);
|
|
138
|
+
expect(resolveRemoteContract).toHaveBeenCalledWith(expect.objectContaining({ contractId, version: 1 }));
|
|
139
|
+
await expect(remoteHandle?.consumer).resolves.toBe(remote);
|
|
140
|
+
});
|
|
141
|
+
it("rejects a remote handle when the contract definition is unknown locally", async () => {
|
|
142
|
+
const dataContext = {
|
|
143
|
+
dataContextId: "test-ctx",
|
|
144
|
+
packageLoader: { resolveRemoteContract: jest.fn().mockResolvedValue(undefined) },
|
|
145
|
+
};
|
|
146
|
+
let remoteHandle;
|
|
147
|
+
const packageDefinition = (0, builder_1.definePackage)((pkg) => {
|
|
148
|
+
pkg.packageId = (0, utils_1.newid)();
|
|
149
|
+
pkg.version = "1.0.0";
|
|
150
|
+
remoteHandle = pkg.remote(contractId, 1);
|
|
151
|
+
});
|
|
152
|
+
await (0, resolve_consumed_contracts_1.resolveConsumedContracts)(dataContext, new registry_1.ContractRegistry(), packageDefinition);
|
|
153
|
+
await expect(remoteHandle?.consumer).rejects.toThrow(/not registered on this device/);
|
|
154
|
+
});
|
|
155
|
+
it("rejects a remote handle when no package loader owns the context", async () => {
|
|
156
|
+
let remoteHandle;
|
|
157
|
+
const packageDefinition = (0, builder_1.definePackage)((pkg) => {
|
|
158
|
+
pkg.packageId = (0, utils_1.newid)();
|
|
159
|
+
pkg.version = "1.0.0";
|
|
160
|
+
remoteHandle = pkg.remote(contractId, 1);
|
|
161
|
+
});
|
|
162
|
+
await (0, resolve_consumed_contracts_1.resolveConsumedContracts)(mockDataContext(), new registry_1.ContractRegistry(), packageDefinition);
|
|
163
|
+
await expect(remoteHandle?.consumer).rejects.toThrow(/requires a package loader/);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
123
166
|
});
|