@peers-app/peers-sdk 0.18.3 → 0.18.4
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__/builder.test.js +5 -8
- package/dist/contracts/__tests__/integration.test.js +1 -1
- package/dist/contracts/builder.d.ts +6 -8
- package/dist/contracts/builder.js +8 -16
- package/dist/contracts/contract-providers.table.d.ts +2 -2
- package/dist/contracts/registry.d.ts +4 -0
- package/dist/contracts/registry.js +16 -0
- package/dist/contracts/types.d.ts +0 -2
- package/dist/data/package-permissions.d.ts +9 -2
- package/dist/data/package-permissions.js +12 -4
- package/dist/data/package-version-permissions.d.ts +5 -1
- package/dist/data/package-version-permissions.js +11 -3
- package/dist/data/package-versions.d.ts +42 -2
- package/dist/data/package-versions.js +28 -9
- package/dist/package-loader/contract-package-loader.d.ts +12 -0
- package/dist/package-loader/contract-package-loader.js +36 -2
- package/dist/package-loader/package-loader.d.ts +1 -1
- package/dist/package-loader/package-loader.js +24 -26
- package/package.json +1 -1
|
@@ -57,7 +57,7 @@ describe("definePackage", () => {
|
|
|
57
57
|
expect(contract.version).toBe(1);
|
|
58
58
|
expect(contract.name).toBe("Tasks");
|
|
59
59
|
expect(contract.description).toBe("");
|
|
60
|
-
expect(contract.devTag).
|
|
60
|
+
expect(contract.devTag).toBe("dev");
|
|
61
61
|
expect(contract.tables).toHaveLength(1);
|
|
62
62
|
expect(contract.tables[0].name).toBe("Tasks");
|
|
63
63
|
expect(contract.tables[0].fields).toHaveLength(2);
|
|
@@ -163,11 +163,11 @@ describe("definePackage", () => {
|
|
|
163
163
|
expect(result.assistants).toEqual([assistant]);
|
|
164
164
|
expect(result.appNavs).toEqual([nav]);
|
|
165
165
|
});
|
|
166
|
-
it("
|
|
166
|
+
it("always assigns devTag 'dev' to contracts from definePackage", () => {
|
|
167
167
|
const devContractId = (0, utils_1.newid)();
|
|
168
168
|
const result = (0, builder_1.definePackage)((pkg) => {
|
|
169
169
|
pkg.packageId = TEST_PKG_ID;
|
|
170
|
-
const c = pkg.contract(devContractId, 1, "Dev Contract"
|
|
170
|
+
const c = pkg.contract(devContractId, 1, "Dev Contract");
|
|
171
171
|
c.tables = [
|
|
172
172
|
{
|
|
173
173
|
metaData: {
|
|
@@ -227,12 +227,11 @@ describe("definePackage", () => {
|
|
|
227
227
|
expect(result.contracts[0].name).toBe("Human-Readable Name");
|
|
228
228
|
expect(result.contracts[0].description).toBe("");
|
|
229
229
|
});
|
|
230
|
-
it("includes packageId
|
|
230
|
+
it("includes packageId and version in the result", () => {
|
|
231
231
|
const cid = (0, utils_1.newid)();
|
|
232
232
|
const result = (0, builder_1.definePackage)((pkg) => {
|
|
233
233
|
pkg.packageId = TEST_PKG_ID;
|
|
234
234
|
pkg.version = "1.2.3";
|
|
235
|
-
pkg.versionTag = "dev";
|
|
236
235
|
const c = pkg.contract(cid, 1, "C");
|
|
237
236
|
c.tables = [
|
|
238
237
|
{
|
|
@@ -247,9 +246,8 @@ describe("definePackage", () => {
|
|
|
247
246
|
});
|
|
248
247
|
expect(result.packageId).toBe(TEST_PKG_ID);
|
|
249
248
|
expect(result.version).toBe("1.2.3");
|
|
250
|
-
expect(result.versionTag).toBe("dev");
|
|
251
249
|
});
|
|
252
|
-
it("leaves version
|
|
250
|
+
it("leaves version undefined when not set", () => {
|
|
253
251
|
const cid = (0, utils_1.newid)();
|
|
254
252
|
const result = (0, builder_1.definePackage)((pkg) => {
|
|
255
253
|
pkg.packageId = TEST_PKG_ID;
|
|
@@ -267,7 +265,6 @@ describe("definePackage", () => {
|
|
|
267
265
|
});
|
|
268
266
|
expect(result.packageId).toBe(TEST_PKG_ID);
|
|
269
267
|
expect(result.version).toBeUndefined();
|
|
270
|
-
expect(result.versionTag).toBeUndefined();
|
|
271
268
|
});
|
|
272
269
|
});
|
|
273
270
|
describe("definePackage — error cases", () => {
|
|
@@ -266,7 +266,7 @@ describe("Package Contracts — integration", () => {
|
|
|
266
266
|
// Define a broken fancy tasks that is missing the taskCount observable
|
|
267
267
|
const brokenFancy = (0, builder_1.definePackage)((pkg) => {
|
|
268
268
|
pkg.packageId = (0, utils_1.newid)();
|
|
269
|
-
const fancy = pkg.contract(FANCY_TASKS_CONTRACT_ID, 1, "Broken Fancy Tasks"
|
|
269
|
+
const fancy = pkg.contract(FANCY_TASKS_CONTRACT_ID, 1, "Broken Fancy Tasks");
|
|
270
270
|
fancy.alsoImplements(TASKS_CONTRACT_ID, 1);
|
|
271
271
|
fancy.tables = [
|
|
272
272
|
{
|
|
@@ -11,7 +11,6 @@ export declare class ContractBuilder {
|
|
|
11
11
|
readonly version: number;
|
|
12
12
|
readonly name: string;
|
|
13
13
|
readonly description?: string | undefined;
|
|
14
|
-
readonly devTag?: "dev" | undefined;
|
|
15
14
|
private _tables;
|
|
16
15
|
private _tools;
|
|
17
16
|
private _observables;
|
|
@@ -20,7 +19,7 @@ export declare class ContractBuilder {
|
|
|
20
19
|
private _toolInstances;
|
|
21
20
|
/** Full table definitions for this contract (used by the install flow). */
|
|
22
21
|
private _tableDefinitions;
|
|
23
|
-
constructor(contractId: string, version: number, name: string, description?: string | undefined
|
|
22
|
+
constructor(contractId: string, version: number, name: string, description?: string | undefined);
|
|
24
23
|
set tables(defs: IExtractableTableDef[]);
|
|
25
24
|
get tables(): IExtractableTableDef[];
|
|
26
25
|
set tools(instances: IExtractableToolInstance[]);
|
|
@@ -61,7 +60,6 @@ export declare class ContractBuilder {
|
|
|
61
60
|
export declare class PackageBuilder {
|
|
62
61
|
private _packageId?;
|
|
63
62
|
private _version?;
|
|
64
|
-
private _versionTag?;
|
|
65
63
|
private _contracts;
|
|
66
64
|
private _consumes;
|
|
67
65
|
/** Package-level assistants (not part of any contract). */
|
|
@@ -74,19 +72,19 @@ export declare class PackageBuilder {
|
|
|
74
72
|
/** Semantic version string (e.g. "1.0.0"). Typically imported from package.json at build time. */
|
|
75
73
|
set version(v: string);
|
|
76
74
|
get version(): string | undefined;
|
|
77
|
-
/** Version channel tag (e.g. "dev", "beta", "stable"). */
|
|
78
|
-
set versionTag(tag: string);
|
|
79
|
-
get versionTag(): string | undefined;
|
|
80
75
|
/**
|
|
81
76
|
* Define (and implement) a contract at the given version.
|
|
82
77
|
* Returns a `ContractBuilder` for assigning tables, tools, and observables.
|
|
83
78
|
*
|
|
79
|
+
* Contracts are always registered as `devTag: "dev"` from code. The platform
|
|
80
|
+
* finalizes contracts (removes devTag) when the package version is promoted
|
|
81
|
+
* to stable. Previously-frozen contracts are preserved by the installer.
|
|
82
|
+
*
|
|
84
83
|
* @param contractId - Must be a valid peer ID (25-char alphanumeric from `newid()`).
|
|
85
84
|
* @param version - Positive integer version number.
|
|
86
85
|
* @param name - Human-readable contract name. This is the meaningful identifier that creators can change over time.
|
|
87
|
-
* @param devTag - Set to `"dev"` while the contract shape is still mutable.
|
|
88
86
|
*/
|
|
89
|
-
contract(contractId: string, version: number, name: string
|
|
87
|
+
contract(contractId: string, version: number, name: string): ContractBuilder;
|
|
90
88
|
/**
|
|
91
89
|
* Declare a dependency on another package's contract.
|
|
92
90
|
*/
|
|
@@ -14,7 +14,6 @@ class ContractBuilder {
|
|
|
14
14
|
version;
|
|
15
15
|
name;
|
|
16
16
|
description;
|
|
17
|
-
devTag;
|
|
18
17
|
_tables = [];
|
|
19
18
|
_tools = [];
|
|
20
19
|
_observables = [];
|
|
@@ -23,12 +22,11 @@ class ContractBuilder {
|
|
|
23
22
|
_toolInstances = [];
|
|
24
23
|
/** Full table definitions for this contract (used by the install flow). */
|
|
25
24
|
_tableDefinitions = [];
|
|
26
|
-
constructor(contractId, version, name, description
|
|
25
|
+
constructor(contractId, version, name, description) {
|
|
27
26
|
this.contractId = contractId;
|
|
28
27
|
this.version = version;
|
|
29
28
|
this.name = name;
|
|
30
29
|
this.description = description;
|
|
31
|
-
this.devTag = devTag;
|
|
32
30
|
}
|
|
33
31
|
set tables(defs) {
|
|
34
32
|
this._tables = defs;
|
|
@@ -78,7 +76,7 @@ class ContractBuilder {
|
|
|
78
76
|
definition: {
|
|
79
77
|
contractId: this.contractId,
|
|
80
78
|
version: this.version,
|
|
81
|
-
devTag:
|
|
79
|
+
devTag: "dev",
|
|
82
80
|
name: this.name,
|
|
83
81
|
description: this.description ?? "",
|
|
84
82
|
tables: this._tables.map(extract_1.extractTableShape),
|
|
@@ -104,7 +102,6 @@ exports.ContractBuilder = ContractBuilder;
|
|
|
104
102
|
class PackageBuilder {
|
|
105
103
|
_packageId;
|
|
106
104
|
_version;
|
|
107
|
-
_versionTag;
|
|
108
105
|
_contracts = [];
|
|
109
106
|
_consumes = [];
|
|
110
107
|
/** Package-level assistants (not part of any contract). */
|
|
@@ -128,23 +125,19 @@ class PackageBuilder {
|
|
|
128
125
|
get version() {
|
|
129
126
|
return this._version;
|
|
130
127
|
}
|
|
131
|
-
/** Version channel tag (e.g. "dev", "beta", "stable"). */
|
|
132
|
-
set versionTag(tag) {
|
|
133
|
-
this._versionTag = tag;
|
|
134
|
-
}
|
|
135
|
-
get versionTag() {
|
|
136
|
-
return this._versionTag;
|
|
137
|
-
}
|
|
138
128
|
/**
|
|
139
129
|
* Define (and implement) a contract at the given version.
|
|
140
130
|
* Returns a `ContractBuilder` for assigning tables, tools, and observables.
|
|
141
131
|
*
|
|
132
|
+
* Contracts are always registered as `devTag: "dev"` from code. The platform
|
|
133
|
+
* finalizes contracts (removes devTag) when the package version is promoted
|
|
134
|
+
* to stable. Previously-frozen contracts are preserved by the installer.
|
|
135
|
+
*
|
|
142
136
|
* @param contractId - Must be a valid peer ID (25-char alphanumeric from `newid()`).
|
|
143
137
|
* @param version - Positive integer version number.
|
|
144
138
|
* @param name - Human-readable contract name. This is the meaningful identifier that creators can change over time.
|
|
145
|
-
* @param devTag - Set to `"dev"` while the contract shape is still mutable.
|
|
146
139
|
*/
|
|
147
|
-
contract(contractId, version, name
|
|
140
|
+
contract(contractId, version, name) {
|
|
148
141
|
if (!(0, utils_1.isid)(contractId)) {
|
|
149
142
|
throw new Error(`contractId must be a valid peer ID (25-char alphanumeric), got "${contractId}"`);
|
|
150
143
|
}
|
|
@@ -155,7 +148,7 @@ class PackageBuilder {
|
|
|
155
148
|
if (existing) {
|
|
156
149
|
throw new Error(`Duplicate contract: ${contractId} v${version} is already defined in this package`);
|
|
157
150
|
}
|
|
158
|
-
const builder = new ContractBuilder(contractId, version, name
|
|
151
|
+
const builder = new ContractBuilder(contractId, version, name);
|
|
159
152
|
this._contracts.push(builder);
|
|
160
153
|
return builder;
|
|
161
154
|
}
|
|
@@ -192,7 +185,6 @@ class PackageBuilder {
|
|
|
192
185
|
return {
|
|
193
186
|
packageId: this._packageId,
|
|
194
187
|
version: this._version,
|
|
195
|
-
versionTag: this._versionTag,
|
|
196
188
|
contracts,
|
|
197
189
|
consumes: [...this._consumes],
|
|
198
190
|
alsoImplements: alsoImplementsMap,
|
|
@@ -16,15 +16,15 @@ declare const schema: z.ZodObject<{
|
|
|
16
16
|
registeredAt: z.ZodString;
|
|
17
17
|
}, "strip", z.ZodTypeAny, {
|
|
18
18
|
version: number;
|
|
19
|
-
contractProviderId: string;
|
|
20
19
|
contractId: string;
|
|
20
|
+
contractProviderId: string;
|
|
21
21
|
providerPackageId: string;
|
|
22
22
|
isActive: boolean;
|
|
23
23
|
registeredAt: string;
|
|
24
24
|
}, {
|
|
25
25
|
version: number;
|
|
26
|
-
contractProviderId: string;
|
|
27
26
|
contractId: string;
|
|
27
|
+
contractProviderId: string;
|
|
28
28
|
providerPackageId: string;
|
|
29
29
|
isActive: boolean;
|
|
30
30
|
registeredAt: string;
|
|
@@ -41,6 +41,10 @@ export declare class ContractRegistry {
|
|
|
41
41
|
getProviderPackageId(contractId: string, version: number): string | undefined;
|
|
42
42
|
/** Get the stored contract definition (regardless of active provider). */
|
|
43
43
|
getDefinition(contractId: string, version: number): IContractDefinition | undefined;
|
|
44
|
+
/** Replace the stored definition for a contract (e.g. to freeze a dev contract). */
|
|
45
|
+
updateDefinition(contractId: string, version: number, definition: IContractDefinition): void;
|
|
46
|
+
/** Get all contract keys provided by a package. */
|
|
47
|
+
getContractKeysForPackage(packageId: string): string[];
|
|
44
48
|
/**
|
|
45
49
|
* Remove all contracts provided by a package. If the removed package was
|
|
46
50
|
* the active provider for a contract, the next registered provider (if any)
|
|
@@ -102,6 +102,22 @@ class ContractRegistry {
|
|
|
102
102
|
getDefinition(contractId, version) {
|
|
103
103
|
return this.definitions.get((0, types_1.contractKey)(contractId, version));
|
|
104
104
|
}
|
|
105
|
+
/** Replace the stored definition for a contract (e.g. to freeze a dev contract). */
|
|
106
|
+
updateDefinition(contractId, version, definition) {
|
|
107
|
+
const key = (0, types_1.contractKey)(contractId, version);
|
|
108
|
+
this.definitions.set(key, definition);
|
|
109
|
+
// Also update any active/all providers that reference this definition
|
|
110
|
+
const providers = this.allProviders.get(key);
|
|
111
|
+
if (providers) {
|
|
112
|
+
for (const p of providers) {
|
|
113
|
+
p.definition = definition;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** Get all contract keys provided by a package. */
|
|
118
|
+
getContractKeysForPackage(packageId) {
|
|
119
|
+
return this.packageContracts.get(packageId) ?? [];
|
|
120
|
+
}
|
|
105
121
|
/**
|
|
106
122
|
* Remove all contracts provided by a package. If the removed package was
|
|
107
123
|
* the active provider for a contract, the next registered provider (if any)
|
|
@@ -64,8 +64,6 @@ export interface IPackageDefinitionResult {
|
|
|
64
64
|
packageId: string;
|
|
65
65
|
/** Semantic version string baked in from package.json at build time. */
|
|
66
66
|
version?: string;
|
|
67
|
-
/** Version channel tag (e.g. "dev", "beta", "stable"). */
|
|
68
|
-
versionTag?: string;
|
|
69
67
|
/** Contracts this package defines and provides. */
|
|
70
68
|
contracts: IContractDefinition[];
|
|
71
69
|
/** Contracts this package depends on. */
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type { IPackage } from "./packages";
|
|
2
2
|
/**
|
|
3
|
-
* Verifies that a package update signature is valid
|
|
3
|
+
* Verifies that a package update signature is valid.
|
|
4
|
+
*
|
|
5
|
+
* Writers can create or update packages that have a dev active version.
|
|
6
|
+
* Admins are required for packages with beta or stable active versions.
|
|
7
|
+
*
|
|
4
8
|
* @param packageObj The package record to verify
|
|
9
|
+
* @param opts.isDevVersion When true, allows Writer role (for dev version operations)
|
|
5
10
|
* @throws Error if signature is invalid or unauthorized
|
|
6
11
|
*/
|
|
7
|
-
export declare function verifyPackageSignature(packageObj: IPackage, groupId: string
|
|
12
|
+
export declare function verifyPackageSignature(packageObj: IPackage, groupId: string, opts?: {
|
|
13
|
+
isDevVersion?: boolean;
|
|
14
|
+
}): Promise<void>;
|
|
@@ -4,15 +4,23 @@ exports.verifyPackageSignature = verifyPackageSignature;
|
|
|
4
4
|
const keys_1 = require("../keys");
|
|
5
5
|
const group_permissions_1 = require("./group-permissions");
|
|
6
6
|
/**
|
|
7
|
-
* Verifies that a package update signature is valid
|
|
7
|
+
* Verifies that a package update signature is valid.
|
|
8
|
+
*
|
|
9
|
+
* Writers can create or update packages that have a dev active version.
|
|
10
|
+
* Admins are required for packages with beta or stable active versions.
|
|
11
|
+
*
|
|
8
12
|
* @param packageObj The package record to verify
|
|
13
|
+
* @param opts.isDevVersion When true, allows Writer role (for dev version operations)
|
|
9
14
|
* @throws Error if signature is invalid or unauthorized
|
|
10
15
|
*/
|
|
11
|
-
async function verifyPackageSignature(packageObj, groupId) {
|
|
16
|
+
async function verifyPackageSignature(packageObj, groupId, opts) {
|
|
12
17
|
(0, keys_1.verifyObjectSignature)(packageObj);
|
|
13
18
|
const signerPublicKey = (0, keys_1.getPublicKeyFromObjectSignature)(packageObj) ?? "";
|
|
14
19
|
const signerRole = await (0, group_permissions_1.getUserRoleFromPublicKey)(groupId, signerPublicKey);
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
const requiredRole = opts?.isDevVersion ? group_permissions_1.GroupMemberRole.Writer : group_permissions_1.GroupMemberRole.Admin;
|
|
21
|
+
if (signerRole < requiredRole) {
|
|
22
|
+
throw new Error(opts?.isDevVersion
|
|
23
|
+
? "Only group writers or above can create or update dev packages"
|
|
24
|
+
: "Only group admins can create or update packages");
|
|
17
25
|
}
|
|
18
26
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { IPackageVersion } from "./package-versions";
|
|
2
2
|
/**
|
|
3
|
-
* Verifies that a package version update signature is valid
|
|
3
|
+
* Verifies that a package version update signature is valid.
|
|
4
|
+
*
|
|
5
|
+
* Dev versions (`versionTag === "dev"`) require Writer role.
|
|
6
|
+
* Beta and stable versions require Admin role.
|
|
7
|
+
*
|
|
4
8
|
* @param packageVersion The package version record to verify
|
|
5
9
|
* @throws Error if signature is invalid or unauthorized
|
|
6
10
|
*/
|
|
@@ -4,7 +4,11 @@ exports.verifyPackageVersionSignature = verifyPackageVersionSignature;
|
|
|
4
4
|
const keys_1 = require("../keys");
|
|
5
5
|
const group_permissions_1 = require("./group-permissions");
|
|
6
6
|
/**
|
|
7
|
-
* Verifies that a package version update signature is valid
|
|
7
|
+
* Verifies that a package version update signature is valid.
|
|
8
|
+
*
|
|
9
|
+
* Dev versions (`versionTag === "dev"`) require Writer role.
|
|
10
|
+
* Beta and stable versions require Admin role.
|
|
11
|
+
*
|
|
8
12
|
* @param packageVersion The package version record to verify
|
|
9
13
|
* @throws Error if signature is invalid or unauthorized
|
|
10
14
|
*/
|
|
@@ -12,7 +16,11 @@ async function verifyPackageVersionSignature(packageVersion, groupId) {
|
|
|
12
16
|
(0, keys_1.verifyObjectSignature)(packageVersion);
|
|
13
17
|
const signerPublicKey = (0, keys_1.getPublicKeyFromObjectSignature)(packageVersion) ?? "";
|
|
14
18
|
const signerRole = await (0, group_permissions_1.getUserRoleFromPublicKey)(groupId, signerPublicKey);
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
const isDevVersion = packageVersion.versionTag === "dev";
|
|
20
|
+
const requiredRole = isDevVersion ? group_permissions_1.GroupMemberRole.Writer : group_permissions_1.GroupMemberRole.Admin;
|
|
21
|
+
if (signerRole < requiredRole) {
|
|
22
|
+
throw new Error(isDevVersion
|
|
23
|
+
? "Only group writers or above can create dev package versions"
|
|
24
|
+
: "Only group admins can create or update beta/stable package versions");
|
|
17
25
|
}
|
|
18
26
|
}
|
|
@@ -30,6 +30,22 @@ declare const schema: z.ZodObject<{
|
|
|
30
30
|
navigationPath: string;
|
|
31
31
|
displayName?: string | undefined;
|
|
32
32
|
}>, "many">>;
|
|
33
|
+
history: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
34
|
+
action: z.ZodString;
|
|
35
|
+
by: z.ZodString;
|
|
36
|
+
at: z.ZodString;
|
|
37
|
+
signature: z.ZodString;
|
|
38
|
+
}, "strip", z.ZodTypeAny, {
|
|
39
|
+
at: string;
|
|
40
|
+
action: string;
|
|
41
|
+
signature: string;
|
|
42
|
+
by: string;
|
|
43
|
+
}, {
|
|
44
|
+
at: string;
|
|
45
|
+
action: string;
|
|
46
|
+
signature: string;
|
|
47
|
+
by: string;
|
|
48
|
+
}>, "many">>;
|
|
33
49
|
signature: z.ZodString;
|
|
34
50
|
createdBy: z.ZodEffects<z.ZodString, string, string>;
|
|
35
51
|
createdAt: z.ZodString;
|
|
@@ -43,6 +59,12 @@ declare const schema: z.ZodObject<{
|
|
|
43
59
|
packageBundleFileHash: string;
|
|
44
60
|
createdBy: string;
|
|
45
61
|
createdAt: string;
|
|
62
|
+
history?: {
|
|
63
|
+
at: string;
|
|
64
|
+
action: string;
|
|
65
|
+
signature: string;
|
|
66
|
+
by: string;
|
|
67
|
+
}[] | undefined;
|
|
46
68
|
versionTag?: string | undefined;
|
|
47
69
|
routesBundleFileId?: string | undefined;
|
|
48
70
|
routesBundleFileHash?: string | undefined;
|
|
@@ -64,6 +86,12 @@ declare const schema: z.ZodObject<{
|
|
|
64
86
|
packageBundleFileHash: string;
|
|
65
87
|
createdBy: string;
|
|
66
88
|
createdAt: string;
|
|
89
|
+
history?: {
|
|
90
|
+
at: string;
|
|
91
|
+
action: string;
|
|
92
|
+
signature: string;
|
|
93
|
+
by: string;
|
|
94
|
+
}[] | undefined;
|
|
67
95
|
versionTag?: string | undefined;
|
|
68
96
|
routesBundleFileId?: string | undefined;
|
|
69
97
|
routesBundleFileHash?: string | undefined;
|
|
@@ -88,13 +116,25 @@ export declare class PackageVersionsTable extends Table<IPackageVersion> {
|
|
|
88
116
|
}
|
|
89
117
|
export declare function PackageVersions(dataContext?: DataContext): PackageVersionsTable;
|
|
90
118
|
/**
|
|
91
|
-
* Compute a hash
|
|
119
|
+
* Compute a content hash from bundle file hashes. The hash represents code
|
|
120
|
+
* content only — `versionTag` is intentionally excluded so the same code
|
|
121
|
+
* produces the same hash regardless of its promotion level (dev/beta/stable).
|
|
122
|
+
*
|
|
123
|
+
* @deprecated The `versionTag` parameter is accepted for backward compatibility
|
|
124
|
+
* but ignored. It will be removed in a future release.
|
|
92
125
|
*/
|
|
93
|
-
export declare function computePackageVersionHash(version: string,
|
|
126
|
+
export declare function computePackageVersionHash(version: string, _versionTag: string, packageBundleFileHash: string, routesBundleFileHash?: string, uiBundleFileHash?: string): string;
|
|
94
127
|
export declare function isVersionInRange(activeVersion: string, incomingVersion: string, range: "pinned" | "patch" | "minor" | "latest"): boolean;
|
|
95
128
|
/**
|
|
96
129
|
* Returns true if incomingVersion is strictly newer than activeVersion (semver comparison).
|
|
97
130
|
*/
|
|
98
131
|
export declare function isNewerVersion(activeVersion: string, incomingVersion: string): boolean;
|
|
132
|
+
/**
|
|
133
|
+
* Determines whether an incoming version tag matches the device's follow policy.
|
|
134
|
+
*
|
|
135
|
+
* `"dev"` tags are **never** auto-matched unless the device explicitly opts in
|
|
136
|
+
* via `deviceVersionTag: "dev"`. This prevents local development builds from
|
|
137
|
+
* auto-activating on other devices in the group.
|
|
138
|
+
*/
|
|
99
139
|
export declare function doesTagMatch(activeVersionTag: string | undefined, incomingVersionTag: string | undefined, followVersionTags: string | undefined, deviceVersionTag: string | undefined): boolean;
|
|
100
140
|
export {};
|
|
@@ -66,6 +66,15 @@ const schema = zod_1.z.object({
|
|
|
66
66
|
.array()
|
|
67
67
|
.optional()
|
|
68
68
|
.describe("The app navigation items that this version provides"),
|
|
69
|
+
history: zod_1.z
|
|
70
|
+
.array(zod_1.z.object({
|
|
71
|
+
action: zod_1.z.string().describe("created | promoted:beta | promoted:stable | activated"),
|
|
72
|
+
by: zod_1.z.string().describe("Peer ID of the actor"),
|
|
73
|
+
at: zod_1.z.string().describe("ISO 8601 timestamp"),
|
|
74
|
+
signature: zod_1.z.string().describe("Actor's signature over the action"),
|
|
75
|
+
}))
|
|
76
|
+
.optional()
|
|
77
|
+
.describe("Audit trail of lifecycle events for this version"),
|
|
69
78
|
signature: zod_1.z.string().describe("The signed hash of this data excluding the signature itself"),
|
|
70
79
|
createdBy: zod_types_1.zodPeerId.describe("The user who created this version"),
|
|
71
80
|
createdAt: zod_1.z.string().describe("ISO timestamp of when this version was created"),
|
|
@@ -137,16 +146,15 @@ function PackageVersions(dataContext) {
|
|
|
137
146
|
return (0, context_1.getTableContainer)(dataContext).getTable(metaData, schema, PackageVersionsTable);
|
|
138
147
|
}
|
|
139
148
|
/**
|
|
140
|
-
* Compute a hash
|
|
149
|
+
* Compute a content hash from bundle file hashes. The hash represents code
|
|
150
|
+
* content only — `versionTag` is intentionally excluded so the same code
|
|
151
|
+
* produces the same hash regardless of its promotion level (dev/beta/stable).
|
|
152
|
+
*
|
|
153
|
+
* @deprecated The `versionTag` parameter is accepted for backward compatibility
|
|
154
|
+
* but ignored. It will be removed in a future release.
|
|
141
155
|
*/
|
|
142
|
-
function computePackageVersionHash(version,
|
|
143
|
-
return (0, keys_1.hashValue)([
|
|
144
|
-
version,
|
|
145
|
-
versionTag,
|
|
146
|
-
packageBundleFileHash,
|
|
147
|
-
routesBundleFileHash ?? "",
|
|
148
|
-
uiBundleFileHash ?? "",
|
|
149
|
-
].join(":"));
|
|
156
|
+
function computePackageVersionHash(version, _versionTag, packageBundleFileHash, routesBundleFileHash, uiBundleFileHash) {
|
|
157
|
+
return (0, keys_1.hashValue)([version, packageBundleFileHash, routesBundleFileHash ?? "", uiBundleFileHash ?? ""].join(":"));
|
|
150
158
|
}
|
|
151
159
|
function isVersionInRange(activeVersion, incomingVersion, range) {
|
|
152
160
|
if (range === "pinned")
|
|
@@ -173,10 +181,21 @@ function isNewerVersion(activeVersion, incomingVersion) {
|
|
|
173
181
|
return iMin > aMin;
|
|
174
182
|
return iPat > aPat;
|
|
175
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Determines whether an incoming version tag matches the device's follow policy.
|
|
186
|
+
*
|
|
187
|
+
* `"dev"` tags are **never** auto-matched unless the device explicitly opts in
|
|
188
|
+
* via `deviceVersionTag: "dev"`. This prevents local development builds from
|
|
189
|
+
* auto-activating on other devices in the group.
|
|
190
|
+
*/
|
|
176
191
|
function doesTagMatch(activeVersionTag, incomingVersionTag, followVersionTags, deviceVersionTag) {
|
|
177
192
|
const inTag = incomingVersionTag || "stable";
|
|
193
|
+
// Device-level override: only match the exact tag the device requested
|
|
178
194
|
if (deviceVersionTag)
|
|
179
195
|
return inTag === deviceVersionTag;
|
|
196
|
+
// Dev versions never auto-activate unless explicitly followed
|
|
197
|
+
if (inTag === "dev")
|
|
198
|
+
return false;
|
|
180
199
|
if (!followVersionTags) {
|
|
181
200
|
return inTag === (activeVersionTag || "stable");
|
|
182
201
|
}
|
|
@@ -7,6 +7,9 @@ import type { IPackageDefinitionResult } from "../contracts/types";
|
|
|
7
7
|
* Handles packages that export an `IPackageDefinitionResult` from `definePackage()`.
|
|
8
8
|
* Registers contracts in the provided ContractRegistry, saves tools/assistants/workflows
|
|
9
9
|
* with the `packageId` from the definition set, and registers table definitions.
|
|
10
|
+
*
|
|
11
|
+
* When a contract version was previously frozen (promoted to stable), the
|
|
12
|
+
* installer preserves its stable status rather than re-registering it as dev.
|
|
10
13
|
*/
|
|
11
14
|
export declare function installContractPackage(dataContext: DataContext, packageDefinition: IPackageDefinitionResult, opts?: {
|
|
12
15
|
registry?: ContractRegistry;
|
|
@@ -14,6 +17,9 @@ export declare function installContractPackage(dataContext: DataContext, package
|
|
|
14
17
|
/**
|
|
15
18
|
* Attempts to extract an `IPackageDefinitionResult` from a bundle's module exports.
|
|
16
19
|
* Returns undefined if the bundle doesn't export a contract-based package definition.
|
|
20
|
+
*
|
|
21
|
+
* Matches any bundle that exports a `packageDefinition` with a valid `packageId`,
|
|
22
|
+
* including UI-only packages with zero contracts.
|
|
17
23
|
*/
|
|
18
24
|
export declare function extractPackageDefinition(bundleExports: Record<string, unknown>): IPackageDefinitionResult | undefined;
|
|
19
25
|
/**
|
|
@@ -21,3 +27,9 @@ export declare function extractPackageDefinition(bundleExports: Record<string, u
|
|
|
21
27
|
* indicating it should be installed via the new contract-aware path.
|
|
22
28
|
*/
|
|
23
29
|
export declare function hasPackageDefinition(bundleExports: Record<string, unknown>): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Finalize all dev contracts for a package when promoting to stable.
|
|
32
|
+
* Removes `devTag` from each contract in the registry, freezing their shapes.
|
|
33
|
+
* Returns the list of contract keys that were finalized.
|
|
34
|
+
*/
|
|
35
|
+
export declare function finalizeContractsForPromotion(registry: ContractRegistry, packageId: string): string[];
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.installContractPackage = installContractPackage;
|
|
4
4
|
exports.extractPackageDefinition = extractPackageDefinition;
|
|
5
5
|
exports.hasPackageDefinition = hasPackageDefinition;
|
|
6
|
+
exports.finalizeContractsForPromotion = finalizeContractsForPromotion;
|
|
6
7
|
const tools_1 = require("../data/tools");
|
|
7
8
|
const tools_2 = require("../tools");
|
|
8
9
|
/**
|
|
@@ -11,6 +12,9 @@ const tools_2 = require("../tools");
|
|
|
11
12
|
* Handles packages that export an `IPackageDefinitionResult` from `definePackage()`.
|
|
12
13
|
* Registers contracts in the provided ContractRegistry, saves tools/assistants/workflows
|
|
13
14
|
* with the `packageId` from the definition set, and registers table definitions.
|
|
15
|
+
*
|
|
16
|
+
* When a contract version was previously frozen (promoted to stable), the
|
|
17
|
+
* installer preserves its stable status rather than re-registering it as dev.
|
|
14
18
|
*/
|
|
15
19
|
async function installContractPackage(dataContext, packageDefinition, opts) {
|
|
16
20
|
const { packageId } = packageDefinition;
|
|
@@ -20,7 +24,14 @@ async function installContractPackage(dataContext, packageDefinition, opts) {
|
|
|
20
24
|
for (const contract of packageDefinition.contracts) {
|
|
21
25
|
const key = `${contract.contractId}@${contract.version}`;
|
|
22
26
|
const alsoImpl = packageDefinition.alsoImplements.get(key) ?? [];
|
|
23
|
-
|
|
27
|
+
// If the contract is already frozen in the registry, re-register as
|
|
28
|
+
// stable instead of dev to preserve immutability.
|
|
29
|
+
const existing = registry.getDefinition(contract.contractId, contract.version);
|
|
30
|
+
let effectiveContract = contract;
|
|
31
|
+
if (existing && !existing.devTag && contract.devTag === "dev") {
|
|
32
|
+
effectiveContract = { ...contract, devTag: undefined };
|
|
33
|
+
}
|
|
34
|
+
const result = registry.register(packageId, effectiveContract, alsoImpl);
|
|
24
35
|
if (!result.valid) {
|
|
25
36
|
console.error(`[ContractPackageLoader] Contract registration failed for ${contract.name}:`, result.errors);
|
|
26
37
|
}
|
|
@@ -48,10 +59,13 @@ async function installContractPackage(dataContext, packageDefinition, opts) {
|
|
|
48
59
|
/**
|
|
49
60
|
* Attempts to extract an `IPackageDefinitionResult` from a bundle's module exports.
|
|
50
61
|
* Returns undefined if the bundle doesn't export a contract-based package definition.
|
|
62
|
+
*
|
|
63
|
+
* Matches any bundle that exports a `packageDefinition` with a valid `packageId`,
|
|
64
|
+
* including UI-only packages with zero contracts.
|
|
51
65
|
*/
|
|
52
66
|
function extractPackageDefinition(bundleExports) {
|
|
53
67
|
const def = bundleExports?.packageDefinition;
|
|
54
|
-
if (def &&
|
|
68
|
+
if (def && typeof def.packageId === "string" && Array.isArray(def.contracts)) {
|
|
55
69
|
return def;
|
|
56
70
|
}
|
|
57
71
|
return undefined;
|
|
@@ -63,3 +77,23 @@ function extractPackageDefinition(bundleExports) {
|
|
|
63
77
|
function hasPackageDefinition(bundleExports) {
|
|
64
78
|
return extractPackageDefinition(bundleExports) !== undefined;
|
|
65
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Finalize all dev contracts for a package when promoting to stable.
|
|
82
|
+
* Removes `devTag` from each contract in the registry, freezing their shapes.
|
|
83
|
+
* Returns the list of contract keys that were finalized.
|
|
84
|
+
*/
|
|
85
|
+
function finalizeContractsForPromotion(registry, packageId) {
|
|
86
|
+
const finalized = [];
|
|
87
|
+
const contractKeys = registry.getContractKeysForPackage(packageId);
|
|
88
|
+
for (const key of contractKeys) {
|
|
89
|
+
const [contractId, versionStr] = key.split("@");
|
|
90
|
+
const version = Number(versionStr);
|
|
91
|
+
const def = registry.getDefinition(contractId, version);
|
|
92
|
+
if (def?.devTag === "dev") {
|
|
93
|
+
const frozenDef = { ...def, devTag: undefined };
|
|
94
|
+
registry.updateDefinition(contractId, version, frozenDef);
|
|
95
|
+
finalized.push(key);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return finalized;
|
|
99
|
+
}
|
|
@@ -21,7 +21,7 @@ export declare class PackageLoader {
|
|
|
21
21
|
}): Promise<IPeersPackage | undefined>;
|
|
22
22
|
private _readLocalBundle;
|
|
23
23
|
/**
|
|
24
|
-
* Post-correction: if the package definition carries version
|
|
24
|
+
* Post-correction: if the package definition carries a version string,
|
|
25
25
|
* update the active IPackageVersion record to match. This fixes cases where
|
|
26
26
|
* the PV record was created with stale or placeholder values (e.g. "0.0.1")
|
|
27
27
|
* before the bundle was evaluated.
|
|
@@ -99,7 +99,7 @@ class PackageLoader {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
/**
|
|
102
|
-
* Post-correction: if the package definition carries version
|
|
102
|
+
* Post-correction: if the package definition carries a version string,
|
|
103
103
|
* update the active IPackageVersion record to match. This fixes cases where
|
|
104
104
|
* the PV record was created with stale or placeholder values (e.g. "0.0.1")
|
|
105
105
|
* before the bundle was evaluated.
|
|
@@ -107,28 +107,18 @@ class PackageLoader {
|
|
|
107
107
|
correctPackageVersion(pkg, def) {
|
|
108
108
|
if (!pkg.activePackageVersionId)
|
|
109
109
|
return;
|
|
110
|
-
if (!def.version
|
|
110
|
+
if (!def.version)
|
|
111
111
|
return;
|
|
112
112
|
const pvTable = (0, package_versions_1.PackageVersions)(this.dataContext);
|
|
113
113
|
pvTable.get(pkg.activePackageVersionId).then((pv) => {
|
|
114
114
|
if (!pv)
|
|
115
115
|
return;
|
|
116
|
-
let needsUpdate = false;
|
|
117
|
-
const updated = { ...pv };
|
|
118
116
|
if (def.version && pv.version !== def.version) {
|
|
119
|
-
|
|
120
|
-
needsUpdate = true;
|
|
121
|
-
}
|
|
122
|
-
if (def.versionTag && pv.versionTag !== def.versionTag) {
|
|
123
|
-
updated.versionTag = def.versionTag;
|
|
124
|
-
needsUpdate = true;
|
|
125
|
-
}
|
|
126
|
-
if (needsUpdate) {
|
|
127
|
-
pvTable.save(updated);
|
|
117
|
+
pvTable.save({ ...pv, version: def.version });
|
|
128
118
|
}
|
|
129
119
|
});
|
|
130
120
|
}
|
|
131
|
-
_evaluateBundle(pkg, bundleCode) {
|
|
121
|
+
async _evaluateBundle(pkg, bundleCode) {
|
|
132
122
|
// Node.js built-in modules that do not exist in React Native (and would cause
|
|
133
123
|
// a fatal native error if passed through to RN's require). We block them here
|
|
134
124
|
// and throw a descriptive JS Error instead, which the surrounding try/catch
|
|
@@ -199,26 +189,34 @@ class PackageLoader {
|
|
|
199
189
|
if (packageDefinition.packageId !== pkg.packageId) {
|
|
200
190
|
console.warn(`[PackageLoader] packageId mismatch: definition has "${packageDefinition.packageId}" but DB record has "${pkg.packageId}"`);
|
|
201
191
|
}
|
|
202
|
-
(0, contract_package_loader_1.installContractPackage)(this.dataContext, packageDefinition);
|
|
192
|
+
await (0, contract_package_loader_1.installContractPackage)(this.dataContext, packageDefinition);
|
|
203
193
|
this.contractInstalledPackages.add(packageDefinition.packageId);
|
|
204
194
|
this.correctPackageVersion(pkg, packageDefinition);
|
|
195
|
+
// Build a proper IPeersPackage from the contract definition so callers
|
|
196
|
+
// (e.g. package-installer) can read appNavs, assistants, etc.
|
|
197
|
+
const contractPackageInstance = {
|
|
198
|
+
packageId: packageDefinition.packageId,
|
|
199
|
+
appNavs: packageDefinition.appNavs,
|
|
200
|
+
assistants: packageDefinition.assistants,
|
|
201
|
+
toolInstances: packageDefinition.toolInstances,
|
|
202
|
+
tableDefinitions: packageDefinition.tableDefinitions,
|
|
203
|
+
};
|
|
204
|
+
this.packageInstances[pkg.packageId] = contractPackageInstance;
|
|
205
|
+
return contractPackageInstance;
|
|
205
206
|
}
|
|
206
207
|
// Legacy path: extract IPeersPackage for backward compat
|
|
207
208
|
const packageInstance = bundleExports?.exports || bundleExports?.package || bundleExports?.default || bundleExports;
|
|
208
209
|
this.packageInstances[pkg.packageId] = packageInstance;
|
|
209
210
|
if (packageInstance && typeof packageInstance === "object") {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
211
|
+
packageInstance.toolInstances?.forEach((toolInstance) => {
|
|
212
|
+
(0, tools_2.registerTool)(toolInstance);
|
|
213
|
+
(0, tools_1.Tools)(this.dataContext).save(toolInstance.tool);
|
|
214
|
+
});
|
|
215
|
+
packageInstance.tableDefinitions?.forEach((tableDefinition) => {
|
|
216
|
+
this.dataContext.tableContainer.registerTableDefinition(tableDefinition, {
|
|
217
|
+
overwrite: true,
|
|
215
218
|
});
|
|
216
|
-
|
|
217
|
-
this.dataContext.tableContainer.registerTableDefinition(tableDefinition, {
|
|
218
|
-
overwrite: true,
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
}
|
|
219
|
+
});
|
|
222
220
|
return packageInstance;
|
|
223
221
|
}
|
|
224
222
|
return;
|