@peers-app/peers-sdk 0.21.0 → 0.21.2
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.d.ts +10 -3
- package/dist/context/user-context.js +12 -12
- package/dist/contracts/__tests__/builder.test.js +158 -4
- package/dist/contracts/__tests__/registry.test.js +45 -303
- package/dist/contracts/__tests__/system-contracts.test.d.ts +1 -0
- package/dist/contracts/__tests__/system-contracts.test.js +52 -0
- package/dist/contracts/builder.d.ts +43 -2
- package/dist/contracts/builder.js +79 -5
- package/dist/contracts/contract-proxy.d.ts +61 -21
- package/dist/contracts/create-local-consumer.d.ts +12 -0
- package/dist/contracts/create-local-consumer.js +19 -0
- package/dist/contracts/index.d.ts +5 -3
- package/dist/contracts/index.js +8 -1
- package/dist/contracts/registry.d.ts +17 -0
- package/dist/contracts/registry.js +36 -0
- package/dist/contracts/system/device-operations-contract.js +7 -0
- package/dist/contracts/system/index.d.ts +8 -0
- package/dist/contracts/system/index.js +22 -0
- package/dist/contracts/system/logs-contract.d.ts +18 -1
- package/dist/contracts/system/logs-contract.js +8 -2
- package/dist/contracts/system-contracts.d.ts +47 -0
- package/dist/contracts/system-contracts.js +69 -0
- package/dist/contracts/types.d.ts +44 -0
- package/dist/package-installer/index.d.ts +1 -0
- package/dist/package-installer/index.js +1 -0
- package/dist/package-installer/package-author-signer.d.ts +25 -0
- package/dist/package-installer/package-author-signer.js +57 -0
- package/dist/package-installer/package-installer.d.ts +27 -0
- package/dist/package-installer/package-installer.js +88 -18
- package/dist/package-installer/package-installer.test.js +49 -0
- package/dist/package-installer/types.d.ts +1 -1
- package/dist/package-loader/contract-package-loader.test.d.ts +1 -0
- package/dist/package-loader/contract-package-loader.test.js +88 -0
- package/dist/package-loader/package-loader.d.ts +11 -3
- package/dist/package-loader/package-loader.js +31 -5
- package/dist/package-loader/package-loader.test.d.ts +1 -0
- package/dist/package-loader/package-loader.test.js +98 -0
- package/dist/package-loader/resolve-consumed-contracts.d.ts +13 -0
- package/dist/package-loader/resolve-consumed-contracts.js +55 -0
- package/dist/package-loader/resolve-consumed-contracts.test.d.ts +1 -0
- package/dist/package-loader/resolve-consumed-contracts.test.js +123 -0
- package/dist/rpc-types.d.ts +6 -0
- package/dist/rpc-types.js +2 -0
- package/package.json +1 -1
|
@@ -23,10 +23,17 @@ export declare class UserContext {
|
|
|
23
23
|
constructor(userId: string, dataSourceFactory: DataSourceFactory, ephemeral?: boolean | undefined);
|
|
24
24
|
private init;
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
* Loads all packages for the user's personal data context and every group
|
|
27
|
+
* they are a part of.
|
|
28
|
+
*
|
|
29
|
+
* Callers that `await` this (e.g. device init) block until every package
|
|
30
|
+
* bundle has been evaluated. That is intentional when tools/contracts must
|
|
31
|
+
* be ready before the device is considered initialized, but it does add
|
|
32
|
+
* package-load latency to startup compared to fire-and-forget loading.
|
|
28
33
|
*/
|
|
29
|
-
|
|
34
|
+
loadAllPackages(opts?: {
|
|
35
|
+
force?: boolean;
|
|
36
|
+
}): Promise<void>;
|
|
30
37
|
private loadGroupContexts;
|
|
31
38
|
getDataContext(groupId: string): DataContext;
|
|
32
39
|
/** Wait for any pending `currentlyActiveGroupId` pvar write to be flushed to the DB. */
|
|
@@ -44,22 +44,22 @@ class UserContext {
|
|
|
44
44
|
await this.loadUserContextObservablesFromDB();
|
|
45
45
|
}
|
|
46
46
|
await this.loadGroupContexts();
|
|
47
|
-
await this.loadAllPackages();
|
|
48
47
|
return this;
|
|
49
48
|
}
|
|
50
49
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
50
|
+
* Loads all packages for the user's personal data context and every group
|
|
51
|
+
* they are a part of.
|
|
52
|
+
*
|
|
53
|
+
* Callers that `await` this (e.g. device init) block until every package
|
|
54
|
+
* bundle has been evaluated. That is intentional when tools/contracts must
|
|
55
|
+
* be ready before the device is considered initialized, but it does add
|
|
56
|
+
* package-load latency to startup compared to fire-and-forget loading.
|
|
53
57
|
*/
|
|
54
|
-
loadAllPackages() {
|
|
55
|
-
|
|
56
|
-
.loadAllPackages()
|
|
57
|
-
.
|
|
58
|
-
|
|
59
|
-
groupContext.packageLoader
|
|
60
|
-
.loadAllPackages()
|
|
61
|
-
.catch((err) => console.error("[UserContext] loadAllPackages failed for groupContext:", err));
|
|
62
|
-
}
|
|
58
|
+
async loadAllPackages(opts) {
|
|
59
|
+
await Promise.all([
|
|
60
|
+
this.userDataContext.packageLoader.loadAllPackages(opts),
|
|
61
|
+
...[...this.groupDataContexts.values()].map((ctx) => ctx.packageLoader.loadAllPackages(opts)),
|
|
62
|
+
]);
|
|
63
63
|
}
|
|
64
64
|
async loadGroupContexts() {
|
|
65
65
|
const groupsTable = (0, data_1.Groups)(this.userDataContext);
|
|
@@ -97,10 +97,12 @@ describe("definePackage", () => {
|
|
|
97
97
|
expect(result.contracts[1].contractId).toBe(NOTES_ID);
|
|
98
98
|
});
|
|
99
99
|
it("records consumed contracts", () => {
|
|
100
|
+
let tasksHandle;
|
|
101
|
+
let authHandle;
|
|
100
102
|
const result = (0, builder_1.definePackage)((pkg) => {
|
|
101
103
|
pkg.packageId = TEST_PKG_ID;
|
|
102
|
-
pkg.consumes(TASKS_ID, 1);
|
|
103
|
-
pkg.consumes(AUTH_ID, 2);
|
|
104
|
+
tasksHandle = pkg.consumes(TASKS_ID, 1);
|
|
105
|
+
authHandle = pkg.consumes(AUTH_ID, 2);
|
|
104
106
|
const projects = pkg.contract(PROJECTS_ID, 1, "Projects");
|
|
105
107
|
projects.tables = [
|
|
106
108
|
{
|
|
@@ -114,8 +116,160 @@ describe("definePackage", () => {
|
|
|
114
116
|
];
|
|
115
117
|
});
|
|
116
118
|
expect(result.consumes).toHaveLength(2);
|
|
117
|
-
expect(result.consumes[0]).
|
|
118
|
-
expect(result.consumes[
|
|
119
|
+
expect(result.consumes[0].contractId).toBe(TASKS_ID);
|
|
120
|
+
expect(result.consumes[0].version).toBe(1);
|
|
121
|
+
expect(result.consumes[0].handle).toBe(tasksHandle);
|
|
122
|
+
expect(result.consumes[1].contractId).toBe(AUTH_ID);
|
|
123
|
+
expect(result.consumes[1].version).toBe(2);
|
|
124
|
+
expect(result.consumes[1].handle).toBe(authHandle);
|
|
125
|
+
});
|
|
126
|
+
it("preserves consume options and returns a deferred handle", () => {
|
|
127
|
+
const preferredProviderId = (0, utils_1.newid)();
|
|
128
|
+
const result = (0, builder_1.definePackage)((pkg) => {
|
|
129
|
+
pkg.packageId = TEST_PKG_ID;
|
|
130
|
+
pkg.consumes(TASKS_ID, 1, {
|
|
131
|
+
providerPackageIds: [preferredProviderId],
|
|
132
|
+
fallbackToDefaultProvider: true,
|
|
133
|
+
});
|
|
134
|
+
pkg.consumes(AUTH_ID, 2, {
|
|
135
|
+
optional: true,
|
|
136
|
+
providerPackageIds: [preferredProviderId],
|
|
137
|
+
fallbackToDefaultProvider: false,
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
const [required, optional] = result.consumes;
|
|
141
|
+
expect(required).toMatchObject({
|
|
142
|
+
contractId: TASKS_ID,
|
|
143
|
+
version: 1,
|
|
144
|
+
providerPackageIds: [preferredProviderId],
|
|
145
|
+
fallbackToDefaultProvider: true,
|
|
146
|
+
});
|
|
147
|
+
expect(required.optional).toBeUndefined();
|
|
148
|
+
expect(required.handle).toBeDefined();
|
|
149
|
+
expect(typeof required.handle?.consumer.then).toBe("function");
|
|
150
|
+
// Handle itself must not be thenable (naive await on the return value must not wait).
|
|
151
|
+
expect(required.handle.then).toBeUndefined();
|
|
152
|
+
expect(optional).toMatchObject({
|
|
153
|
+
contractId: AUTH_ID,
|
|
154
|
+
version: 2,
|
|
155
|
+
optional: true,
|
|
156
|
+
providerPackageIds: [preferredProviderId],
|
|
157
|
+
fallbackToDefaultProvider: false,
|
|
158
|
+
});
|
|
159
|
+
expect(optional.handle).toBeDefined();
|
|
160
|
+
});
|
|
161
|
+
it("accepts a type argument that narrows the resolved consumer", () => {
|
|
162
|
+
(0, builder_1.definePackage)((pkg) => {
|
|
163
|
+
pkg.packageId = TEST_PKG_ID;
|
|
164
|
+
const required = pkg.consumes(TASKS_ID, 1);
|
|
165
|
+
const optional = pkg.consumes(AUTH_ID, 1, { optional: true });
|
|
166
|
+
// Compile-time checks: required → TypedConsumer; optional → TypedConsumer | undefined.
|
|
167
|
+
void (async () => {
|
|
168
|
+
const requiredConsumer = await required.consumer;
|
|
169
|
+
const optionalConsumer = await optional.consumer;
|
|
170
|
+
void requiredConsumer;
|
|
171
|
+
void optionalConsumer;
|
|
172
|
+
// Named table methods (not only dataChanged) must be present on ITableProxy.
|
|
173
|
+
const _list = requiredConsumer.tables.ConsoleLogs.list;
|
|
174
|
+
const _deleteOldLogs = requiredConsumer.tables.ConsoleLogs.deleteOldLogs;
|
|
175
|
+
void _list;
|
|
176
|
+
void _deleteOldLogs;
|
|
177
|
+
// Typed consumers may omit empty bags — these must not exist on TypedConsumer.
|
|
178
|
+
// @ts-expect-error no tools bag on slim typed consumer
|
|
179
|
+
void requiredConsumer.tools;
|
|
180
|
+
// @ts-expect-error no events bag on slim typed consumer
|
|
181
|
+
void requiredConsumer.events;
|
|
182
|
+
// @ts-expect-error no observables bag on slim typed consumer
|
|
183
|
+
void requiredConsumer.observables;
|
|
184
|
+
// @ts-expect-error optional consume must allow undefined
|
|
185
|
+
const tooNarrow = await optional.consumer;
|
|
186
|
+
void tooNarrow;
|
|
187
|
+
})();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
it("ILogsConsumer exposes ConsoleLogs as ITableProxy without empty bags", () => {
|
|
191
|
+
(0, builder_1.definePackage)((pkg) => {
|
|
192
|
+
pkg.packageId = TEST_PKG_ID;
|
|
193
|
+
const handle = pkg.consumes(TASKS_ID, 1, { optional: true });
|
|
194
|
+
void (async () => {
|
|
195
|
+
const logs = await handle.consumer;
|
|
196
|
+
if (!logs)
|
|
197
|
+
return;
|
|
198
|
+
const _list = logs.tables.ConsoleLogs.list;
|
|
199
|
+
const _deleteOldLogs = logs.tables.ConsoleLogs.deleteOldLogs;
|
|
200
|
+
void _list;
|
|
201
|
+
void _deleteOldLogs;
|
|
202
|
+
// @ts-expect-error ILogsConsumer has no tools bag
|
|
203
|
+
void logs.tools;
|
|
204
|
+
// @ts-expect-error ILogsConsumer has no events bag
|
|
205
|
+
void logs.events;
|
|
206
|
+
// @ts-expect-error ILogsConsumer has no observables bag
|
|
207
|
+
void logs.observables;
|
|
208
|
+
})();
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
it("settles required handles with a consumer and optional handles with undefined", async () => {
|
|
212
|
+
const { settleConsumedContractHandle, rejectConsumedContractHandle } = await Promise.resolve().then(() => require("../builder"));
|
|
213
|
+
const result = (0, builder_1.definePackage)((pkg) => {
|
|
214
|
+
pkg.packageId = TEST_PKG_ID;
|
|
215
|
+
pkg.consumes(TASKS_ID, 1);
|
|
216
|
+
pkg.consumes(AUTH_ID, 1, { optional: true });
|
|
217
|
+
});
|
|
218
|
+
const requiredHandle = result.consumes[0].handle;
|
|
219
|
+
const optionalHandle = result.consumes[1].handle;
|
|
220
|
+
expect(requiredHandle).toBeDefined();
|
|
221
|
+
expect(optionalHandle).toBeDefined();
|
|
222
|
+
if (!requiredHandle || !optionalHandle) {
|
|
223
|
+
throw new Error("expected consumes handles");
|
|
224
|
+
}
|
|
225
|
+
const fakeConsumer = {
|
|
226
|
+
contractId: TASKS_ID,
|
|
227
|
+
version: 1,
|
|
228
|
+
tables: {},
|
|
229
|
+
tools: {},
|
|
230
|
+
observables: {},
|
|
231
|
+
events: {},
|
|
232
|
+
loadingPromise: Promise.resolve(),
|
|
233
|
+
dispose: () => { },
|
|
234
|
+
};
|
|
235
|
+
settleConsumedContractHandle(requiredHandle, fakeConsumer);
|
|
236
|
+
settleConsumedContractHandle(optionalHandle, undefined);
|
|
237
|
+
await expect(requiredHandle.consumer).resolves.toBe(fakeConsumer);
|
|
238
|
+
await expect(optionalHandle.consumer).resolves.toBeUndefined();
|
|
239
|
+
expect(() => settleConsumedContractHandle(requiredHandle, fakeConsumer)).toThrow("already been settled");
|
|
240
|
+
expect(() => rejectConsumedContractHandle(optionalHandle, new Error("late"))).toThrow("already been settled");
|
|
241
|
+
});
|
|
242
|
+
it("rejects a required consume handle once", async () => {
|
|
243
|
+
const { rejectConsumedContractHandle } = await Promise.resolve().then(() => require("../builder"));
|
|
244
|
+
const result = (0, builder_1.definePackage)((pkg) => {
|
|
245
|
+
pkg.packageId = TEST_PKG_ID;
|
|
246
|
+
pkg.consumes(TASKS_ID, 1);
|
|
247
|
+
});
|
|
248
|
+
const requiredHandle = result.consumes[0].handle;
|
|
249
|
+
expect(requiredHandle).toBeDefined();
|
|
250
|
+
if (!requiredHandle) {
|
|
251
|
+
throw new Error("expected consumes handle");
|
|
252
|
+
}
|
|
253
|
+
const err = new Error("No provider for Tasks");
|
|
254
|
+
rejectConsumedContractHandle(requiredHandle, err);
|
|
255
|
+
await expect(requiredHandle.consumer).rejects.toThrow("No provider for Tasks");
|
|
256
|
+
expect(() => rejectConsumedContractHandle(requiredHandle, err)).toThrow("already been settled");
|
|
257
|
+
});
|
|
258
|
+
it("throws when consumes providerPackageIds contains an invalid peer ID", () => {
|
|
259
|
+
expect(() => {
|
|
260
|
+
(0, builder_1.definePackage)((pkg) => {
|
|
261
|
+
pkg.packageId = TEST_PKG_ID;
|
|
262
|
+
pkg.consumes(TASKS_ID, 1, { providerPackageIds: ["not-valid"] });
|
|
263
|
+
});
|
|
264
|
+
}).toThrow("providerPackageIds");
|
|
265
|
+
});
|
|
266
|
+
it("throws when consumes version is not a positive integer", () => {
|
|
267
|
+
expect(() => {
|
|
268
|
+
(0, builder_1.definePackage)((pkg) => {
|
|
269
|
+
pkg.packageId = TEST_PKG_ID;
|
|
270
|
+
pkg.consumes(TASKS_ID, 0);
|
|
271
|
+
});
|
|
272
|
+
}).toThrow("positive integer");
|
|
119
273
|
});
|
|
120
274
|
it("records alsoImplements declarations", () => {
|
|
121
275
|
const result = (0, builder_1.definePackage)((pkg) => {
|
|
@@ -1,324 +1,66 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const field_type_1 = require("../../types/field-type");
|
|
4
3
|
const utils_1 = require("../../utils");
|
|
5
4
|
const registry_1 = require("../registry");
|
|
6
|
-
|
|
7
|
-
function makeContract(overrides = {}) {
|
|
5
|
+
function makeDefinition(contractId, version, name = "Test Contract") {
|
|
8
6
|
return {
|
|
9
|
-
contractId
|
|
10
|
-
version
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
contractId,
|
|
8
|
+
version,
|
|
9
|
+
devTag: "dev",
|
|
10
|
+
name,
|
|
11
|
+
description: "test",
|
|
13
12
|
tables: [],
|
|
14
13
|
tools: [],
|
|
15
14
|
observables: [],
|
|
16
|
-
|
|
15
|
+
events: [],
|
|
17
16
|
};
|
|
18
17
|
}
|
|
19
|
-
describe("ContractRegistry", () => {
|
|
18
|
+
describe("ContractRegistry.selectProvider", () => {
|
|
19
|
+
const contractId = (0, utils_1.newid)();
|
|
20
|
+
const packageA = (0, utils_1.newid)();
|
|
21
|
+
const packageB = (0, utils_1.newid)();
|
|
22
|
+
const packageC = (0, utils_1.newid)();
|
|
20
23
|
let registry;
|
|
21
24
|
beforeEach(() => {
|
|
22
25
|
registry = new registry_1.ContractRegistry();
|
|
26
|
+
registry.register(packageA, makeDefinition(contractId, 1, "A"));
|
|
27
|
+
registry.register(packageB, makeDefinition(contractId, 1, "B"));
|
|
23
28
|
});
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const def = makeContract({
|
|
27
|
-
tables: [
|
|
28
|
-
{
|
|
29
|
-
name: "Tasks",
|
|
30
|
-
description: "d",
|
|
31
|
-
primaryKeyName: "taskId",
|
|
32
|
-
fields: [{ name: "taskId", type: field_type_1.FieldType.id }],
|
|
33
|
-
},
|
|
34
|
-
],
|
|
35
|
-
});
|
|
36
|
-
const pkgId = (0, utils_1.newid)();
|
|
37
|
-
const result = registry.register(pkgId, def);
|
|
38
|
-
expect(result.valid).toBe(true);
|
|
39
|
-
const resolved = registry.resolve(DEFAULT_CONTRACT_ID, 1);
|
|
40
|
-
expect(resolved).toBeDefined();
|
|
41
|
-
expect(resolved?.tables[0].name).toBe("Tasks");
|
|
42
|
-
});
|
|
43
|
-
it("returns the provider package ID", () => {
|
|
44
|
-
const def = makeContract();
|
|
45
|
-
const pkgId = (0, utils_1.newid)();
|
|
46
|
-
registry.register(pkgId, def);
|
|
47
|
-
expect(registry.getProviderPackageId(DEFAULT_CONTRACT_ID, 1)).toBe(pkgId);
|
|
48
|
-
});
|
|
49
|
-
it("returns undefined for unregistered contracts", () => {
|
|
50
|
-
const unknownId = (0, utils_1.newid)();
|
|
51
|
-
expect(registry.resolve(unknownId, 1)).toBeUndefined();
|
|
52
|
-
expect(registry.getProviderPackageId(unknownId, 1)).toBeUndefined();
|
|
53
|
-
});
|
|
54
|
-
it("auto-activates the first registered provider", () => {
|
|
55
|
-
const def = makeContract();
|
|
56
|
-
const pkgA = (0, utils_1.newid)();
|
|
57
|
-
registry.register(pkgA, def);
|
|
58
|
-
expect(registry.getProviderPackageId(DEFAULT_CONTRACT_ID, 1)).toBe(pkgA);
|
|
59
|
-
});
|
|
60
|
-
it("does not auto-switch when a second provider is registered", () => {
|
|
61
|
-
const def = makeContract();
|
|
62
|
-
const pkgA = (0, utils_1.newid)();
|
|
63
|
-
const pkgB = (0, utils_1.newid)();
|
|
64
|
-
registry.register(pkgA, def);
|
|
65
|
-
registry.register(pkgB, def);
|
|
66
|
-
expect(registry.getProviderPackageId(DEFAULT_CONTRACT_ID, 1)).toBe(pkgA);
|
|
67
|
-
});
|
|
29
|
+
it("returns registered provider ids in registration order", () => {
|
|
30
|
+
expect(registry.getRegisteredProviderIds(contractId, 1)).toEqual([packageA, packageB]);
|
|
68
31
|
});
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const pkgA = (0, utils_1.newid)();
|
|
73
|
-
const pkgB = (0, utils_1.newid)();
|
|
74
|
-
registry.register(pkgA, def);
|
|
75
|
-
registry.register(pkgB, def);
|
|
76
|
-
const switched = registry.setActiveProvider(DEFAULT_CONTRACT_ID, 1, pkgB);
|
|
77
|
-
expect(switched).toBe(true);
|
|
78
|
-
expect(registry.getProviderPackageId(DEFAULT_CONTRACT_ID, 1)).toBe(pkgB);
|
|
79
|
-
});
|
|
80
|
-
it("returns false if the target package is not registered", () => {
|
|
81
|
-
const def = makeContract();
|
|
82
|
-
const pkgA = (0, utils_1.newid)();
|
|
83
|
-
const unknownPkg = (0, utils_1.newid)();
|
|
84
|
-
registry.register(pkgA, def);
|
|
85
|
-
const switched = registry.setActiveProvider(DEFAULT_CONTRACT_ID, 1, unknownPkg);
|
|
86
|
-
expect(switched).toBe(false);
|
|
87
|
-
expect(registry.getProviderPackageId(DEFAULT_CONTRACT_ID, 1)).toBe(pkgA);
|
|
88
|
-
});
|
|
89
|
-
it("returns false for unknown contracts", () => {
|
|
90
|
-
const unknownId = (0, utils_1.newid)();
|
|
91
|
-
const pkgA = (0, utils_1.newid)();
|
|
92
|
-
expect(registry.setActiveProvider(unknownId, 1, pkgA)).toBe(false);
|
|
93
|
-
});
|
|
32
|
+
it("selects the active provider when no preference is given", () => {
|
|
33
|
+
const selected = registry.selectProvider({ contractId, version: 1 });
|
|
34
|
+
expect(selected?.providerPackageId).toBe(packageA);
|
|
94
35
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
expect(registry.getProviderPackageId(DEFAULT_CONTRACT_ID, 1)).toBeUndefined();
|
|
103
|
-
});
|
|
104
|
-
it("falls back to next provider when active is removed", () => {
|
|
105
|
-
const def = makeContract();
|
|
106
|
-
const pkgA = (0, utils_1.newid)();
|
|
107
|
-
const pkgB = (0, utils_1.newid)();
|
|
108
|
-
registry.register(pkgA, def);
|
|
109
|
-
registry.register(pkgB, def);
|
|
110
|
-
registry.unregister(pkgA);
|
|
111
|
-
expect(registry.getProviderPackageId(DEFAULT_CONTRACT_ID, 1)).toBe(pkgB);
|
|
112
|
-
});
|
|
113
|
-
it("is a no-op for unknown packages", () => {
|
|
114
|
-
const unknownPkg = (0, utils_1.newid)();
|
|
115
|
-
expect(() => registry.unregister(unknownPkg)).not.toThrow();
|
|
116
|
-
});
|
|
117
|
-
it("handles a package that provides multiple contracts", () => {
|
|
118
|
-
const contractId1 = (0, utils_1.newid)();
|
|
119
|
-
const contractId2 = (0, utils_1.newid)();
|
|
120
|
-
const def1 = makeContract({ contractId: contractId1 });
|
|
121
|
-
const def2 = makeContract({ contractId: contractId2 });
|
|
122
|
-
const pkgA = (0, utils_1.newid)();
|
|
123
|
-
registry.register(pkgA, def1);
|
|
124
|
-
registry.register(pkgA, def2);
|
|
125
|
-
registry.unregister(pkgA);
|
|
126
|
-
expect(registry.resolve(contractId1, 1)).toBeUndefined();
|
|
127
|
-
expect(registry.resolve(contractId2, 1)).toBeUndefined();
|
|
128
|
-
});
|
|
36
|
+
it("selects the first preferred provider that is registered", () => {
|
|
37
|
+
const dep = {
|
|
38
|
+
contractId,
|
|
39
|
+
version: 1,
|
|
40
|
+
providerPackageIds: [packageC, packageB, packageA],
|
|
41
|
+
};
|
|
42
|
+
expect(registry.selectProvider(dep)?.providerPackageId).toBe(packageB);
|
|
129
43
|
});
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const result = registry.checkConsumerDependencies([
|
|
139
|
-
{ contractId: tasksId, version: 1 },
|
|
140
|
-
{ contractId: authId, version: 1 },
|
|
141
|
-
]);
|
|
142
|
-
expect(result.satisfied).toHaveLength(2);
|
|
143
|
-
expect(result.missing).toHaveLength(0);
|
|
144
|
-
});
|
|
145
|
-
it("reports missing when no provider exists", () => {
|
|
146
|
-
const tasksId = (0, utils_1.newid)();
|
|
147
|
-
const authId = (0, utils_1.newid)();
|
|
148
|
-
const pkgA = (0, utils_1.newid)();
|
|
149
|
-
registry.register(pkgA, makeContract({ contractId: tasksId }));
|
|
150
|
-
const result = registry.checkConsumerDependencies([
|
|
151
|
-
{ contractId: tasksId, version: 1 },
|
|
152
|
-
{ contractId: authId, version: 1 },
|
|
153
|
-
]);
|
|
154
|
-
expect(result.satisfied).toHaveLength(1);
|
|
155
|
-
expect(result.missing).toHaveLength(1);
|
|
156
|
-
expect(result.missing[0].contractId).toBe(authId);
|
|
157
|
-
});
|
|
158
|
-
it("reports all missing when registry is empty", () => {
|
|
159
|
-
const tasksId = (0, utils_1.newid)();
|
|
160
|
-
const result = registry.checkConsumerDependencies([{ contractId: tasksId, version: 1 }]);
|
|
161
|
-
expect(result.satisfied).toHaveLength(0);
|
|
162
|
-
expect(result.missing).toHaveLength(1);
|
|
163
|
-
});
|
|
164
|
-
it("returns empty lists for empty consumer list", () => {
|
|
165
|
-
const result = registry.checkConsumerDependencies([]);
|
|
166
|
-
expect(result.satisfied).toHaveLength(0);
|
|
167
|
-
expect(result.missing).toHaveLength(0);
|
|
168
|
-
});
|
|
44
|
+
it("falls back to the active provider when preferred are missing and fallback is enabled", () => {
|
|
45
|
+
const dep = {
|
|
46
|
+
contractId,
|
|
47
|
+
version: 1,
|
|
48
|
+
providerPackageIds: [packageC],
|
|
49
|
+
fallbackToDefaultProvider: true,
|
|
50
|
+
};
|
|
51
|
+
expect(registry.selectProvider(dep)?.providerPackageId).toBe(packageA);
|
|
169
52
|
});
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
fields: [{ name: "id", type: field_type_1.FieldType.id }],
|
|
179
|
-
},
|
|
180
|
-
],
|
|
181
|
-
});
|
|
182
|
-
const pkgA = (0, utils_1.newid)();
|
|
183
|
-
registry.register(pkgA, def1);
|
|
184
|
-
const def2 = makeContract({
|
|
185
|
-
tables: [
|
|
186
|
-
{
|
|
187
|
-
name: "T",
|
|
188
|
-
description: "d",
|
|
189
|
-
primaryKeyName: "id",
|
|
190
|
-
fields: [
|
|
191
|
-
{ name: "id", type: field_type_1.FieldType.id },
|
|
192
|
-
{ name: "extra", type: field_type_1.FieldType.string },
|
|
193
|
-
],
|
|
194
|
-
},
|
|
195
|
-
],
|
|
196
|
-
});
|
|
197
|
-
const pkgB = (0, utils_1.newid)();
|
|
198
|
-
const result = registry.register(pkgB, def2);
|
|
199
|
-
expect(result.valid).toBe(false);
|
|
200
|
-
expect(result.errors[0].message).toContain("frozen");
|
|
201
|
-
});
|
|
202
|
-
it("allows re-registration of a dev contract with a different shape", () => {
|
|
203
|
-
const def1 = makeContract({
|
|
204
|
-
devTag: "dev",
|
|
205
|
-
tables: [
|
|
206
|
-
{
|
|
207
|
-
name: "T",
|
|
208
|
-
description: "d",
|
|
209
|
-
primaryKeyName: "id",
|
|
210
|
-
fields: [{ name: "id", type: field_type_1.FieldType.id }],
|
|
211
|
-
},
|
|
212
|
-
],
|
|
213
|
-
});
|
|
214
|
-
const pkgA = (0, utils_1.newid)();
|
|
215
|
-
registry.register(pkgA, def1);
|
|
216
|
-
const def2 = makeContract({
|
|
217
|
-
devTag: "dev",
|
|
218
|
-
tables: [
|
|
219
|
-
{
|
|
220
|
-
name: "T",
|
|
221
|
-
description: "d",
|
|
222
|
-
primaryKeyName: "id",
|
|
223
|
-
fields: [
|
|
224
|
-
{ name: "id", type: field_type_1.FieldType.id },
|
|
225
|
-
{ name: "extra", type: field_type_1.FieldType.string },
|
|
226
|
-
],
|
|
227
|
-
},
|
|
228
|
-
],
|
|
229
|
-
});
|
|
230
|
-
const pkgB = (0, utils_1.newid)();
|
|
231
|
-
const result = registry.register(pkgB, def2);
|
|
232
|
-
expect(result.valid).toBe(true);
|
|
233
|
-
});
|
|
234
|
-
it("rejects dev registration after a stable version exists", () => {
|
|
235
|
-
const stableDef = makeContract({
|
|
236
|
-
tables: [
|
|
237
|
-
{
|
|
238
|
-
name: "T",
|
|
239
|
-
description: "d",
|
|
240
|
-
primaryKeyName: "id",
|
|
241
|
-
fields: [{ name: "id", type: field_type_1.FieldType.id }],
|
|
242
|
-
},
|
|
243
|
-
],
|
|
244
|
-
});
|
|
245
|
-
const pkgA = (0, utils_1.newid)();
|
|
246
|
-
registry.register(pkgA, stableDef);
|
|
247
|
-
const devDef = makeContract({
|
|
248
|
-
devTag: "dev",
|
|
249
|
-
tables: [
|
|
250
|
-
{
|
|
251
|
-
name: "T",
|
|
252
|
-
description: "d",
|
|
253
|
-
primaryKeyName: "id",
|
|
254
|
-
fields: [{ name: "id", type: field_type_1.FieldType.id }],
|
|
255
|
-
},
|
|
256
|
-
],
|
|
257
|
-
});
|
|
258
|
-
const pkgB = (0, utils_1.newid)();
|
|
259
|
-
const result = registry.register(pkgB, devDef);
|
|
260
|
-
expect(result.valid).toBe(false);
|
|
261
|
-
expect(result.errors[0].message).toContain("frozen");
|
|
262
|
-
});
|
|
263
|
-
it("allows dev-to-stable transition, then rejects subsequent dev registration", () => {
|
|
264
|
-
const devDef = makeContract({
|
|
265
|
-
devTag: "dev",
|
|
266
|
-
tables: [
|
|
267
|
-
{
|
|
268
|
-
name: "T",
|
|
269
|
-
description: "d",
|
|
270
|
-
primaryKeyName: "id",
|
|
271
|
-
fields: [{ name: "id", type: field_type_1.FieldType.id }],
|
|
272
|
-
},
|
|
273
|
-
],
|
|
274
|
-
});
|
|
275
|
-
const pkgA = (0, utils_1.newid)();
|
|
276
|
-
registry.register(pkgA, devDef);
|
|
277
|
-
const stableDef = makeContract({
|
|
278
|
-
tables: [
|
|
279
|
-
{
|
|
280
|
-
name: "T",
|
|
281
|
-
description: "d",
|
|
282
|
-
primaryKeyName: "id",
|
|
283
|
-
fields: [
|
|
284
|
-
{ name: "id", type: field_type_1.FieldType.id },
|
|
285
|
-
{ name: "title", type: field_type_1.FieldType.string },
|
|
286
|
-
],
|
|
287
|
-
},
|
|
288
|
-
],
|
|
289
|
-
});
|
|
290
|
-
const pkgB = (0, utils_1.newid)();
|
|
291
|
-
const stableResult = registry.register(pkgB, stableDef);
|
|
292
|
-
expect(stableResult.valid).toBe(true);
|
|
293
|
-
const devDef2 = makeContract({
|
|
294
|
-
devTag: "dev",
|
|
295
|
-
tables: [
|
|
296
|
-
{
|
|
297
|
-
name: "T",
|
|
298
|
-
description: "d",
|
|
299
|
-
primaryKeyName: "id",
|
|
300
|
-
fields: [{ name: "id", type: field_type_1.FieldType.id }],
|
|
301
|
-
},
|
|
302
|
-
],
|
|
303
|
-
});
|
|
304
|
-
const pkgC = (0, utils_1.newid)();
|
|
305
|
-
const devResult = registry.register(pkgC, devDef2);
|
|
306
|
-
expect(devResult.valid).toBe(false);
|
|
307
|
-
expect(devResult.errors[0].message).toContain("frozen");
|
|
308
|
-
});
|
|
53
|
+
it("returns undefined when preferred are missing and fallback is disabled", () => {
|
|
54
|
+
const dep = {
|
|
55
|
+
contractId,
|
|
56
|
+
version: 1,
|
|
57
|
+
providerPackageIds: [packageC],
|
|
58
|
+
fallbackToDefaultProvider: false,
|
|
59
|
+
};
|
|
60
|
+
expect(registry.selectProvider(dep)).toBeUndefined();
|
|
309
61
|
});
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const pkgA = (0, utils_1.newid)();
|
|
314
|
-
registry.register(pkgA, def);
|
|
315
|
-
const stored = registry.getDefinition(DEFAULT_CONTRACT_ID, 1);
|
|
316
|
-
expect(stored).toBeDefined();
|
|
317
|
-
expect(stored?.name).toBe("My Contract");
|
|
318
|
-
});
|
|
319
|
-
it("returns undefined for unknown contracts", () => {
|
|
320
|
-
const unknownId = (0, utils_1.newid)();
|
|
321
|
-
expect(registry.getDefinition(unknownId, 1)).toBeUndefined();
|
|
322
|
-
});
|
|
62
|
+
it("returns undefined when no providers are registered", () => {
|
|
63
|
+
const empty = new registry_1.ContractRegistry();
|
|
64
|
+
expect(empty.selectProvider({ contractId, version: 1 })).toBeUndefined();
|
|
323
65
|
});
|
|
324
66
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "../system";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const system_ids_1 = require("../../system-ids");
|
|
4
|
+
const utils_1 = require("../../utils");
|
|
5
|
+
const contract_resolvers_1 = require("../contract-resolvers");
|
|
6
|
+
const registry_1 = require("../registry");
|
|
7
|
+
const system_contracts_1 = require("../system-contracts");
|
|
8
|
+
// Populate the catalog with built-in system contracts.
|
|
9
|
+
require("../system");
|
|
10
|
+
describe("system contract catalog", () => {
|
|
11
|
+
it("self-registers Logs and Device Operations", () => {
|
|
12
|
+
const ids = (0, system_contracts_1.listSystemContracts)()
|
|
13
|
+
.map((entry) => entry.contractId)
|
|
14
|
+
.filter(Boolean);
|
|
15
|
+
expect(ids).toEqual(expect.arrayContaining([system_ids_1.logsContractId, system_ids_1.deviceOperationsContractId]));
|
|
16
|
+
});
|
|
17
|
+
it("registers the Logs live resolver", async () => {
|
|
18
|
+
await new Promise((resolve) => queueMicrotask(resolve));
|
|
19
|
+
expect((0, contract_resolvers_1.getContractResolver)(system_ids_1.logsContractId, 1)).toEqual(expect.any(Function));
|
|
20
|
+
});
|
|
21
|
+
it("installSystemContracts seeds a registry with system providers", () => {
|
|
22
|
+
const registry = new registry_1.ContractRegistry();
|
|
23
|
+
(0, system_contracts_1.installSystemContracts)(registry);
|
|
24
|
+
expect(registry.resolve(system_ids_1.logsContractId, 1)?.name).toBe("System Logs");
|
|
25
|
+
expect(registry.getProviderPackageId(system_ids_1.logsContractId, 1)).toBe(system_ids_1.peersSystemPackageId);
|
|
26
|
+
expect(registry.resolve(system_ids_1.deviceOperationsContractId, 1)?.name).toBe("System Device Operations");
|
|
27
|
+
expect(registry.getProviderPackageId(system_ids_1.deviceOperationsContractId, 1)).toBe(system_ids_1.peersSystemPackageId);
|
|
28
|
+
});
|
|
29
|
+
it("registerSystemContract makes new contracts installable without loader changes", () => {
|
|
30
|
+
const fakeContractId = (0, utils_1.newid)();
|
|
31
|
+
const providerPackageId = (0, utils_1.newid)();
|
|
32
|
+
(0, system_contracts_1.registerSystemContract)({
|
|
33
|
+
providerPackageId,
|
|
34
|
+
contractId: fakeContractId,
|
|
35
|
+
version: 1,
|
|
36
|
+
getDefinition: () => ({
|
|
37
|
+
contractId: fakeContractId,
|
|
38
|
+
version: 1,
|
|
39
|
+
devTag: "dev",
|
|
40
|
+
name: "Fake System Contract",
|
|
41
|
+
description: "test",
|
|
42
|
+
tables: [],
|
|
43
|
+
tools: [],
|
|
44
|
+
observables: [],
|
|
45
|
+
events: [],
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
const registry = new registry_1.ContractRegistry();
|
|
49
|
+
(0, system_contracts_1.installSystemContracts)(registry);
|
|
50
|
+
expect(registry.getProviderPackageId(fakeContractId, 1)).toBe(providerPackageId);
|
|
51
|
+
});
|
|
52
|
+
});
|