@peers-app/peers-sdk 0.26.6 → 0.27.1
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/README.md +3 -1
- package/bin/peers-package.js +13 -0
- package/define/package.json +4 -0
- package/dist/contracts/__tests__/action-dispatch.test.js +56 -76
- package/dist/contracts/__tests__/also-implements.test.d.ts +1 -0
- package/dist/contracts/__tests__/also-implements.test.js +110 -0
- package/dist/contracts/__tests__/builder.test.js +45 -0
- package/dist/contracts/__tests__/contract-resolvers.test.js +134 -4
- package/dist/contracts/__tests__/frozen-contracts.test.d.ts +1 -0
- package/dist/contracts/__tests__/frozen-contracts.test.js +191 -0
- package/dist/contracts/__tests__/generic-tool-dispatch.test.js +13 -56
- package/dist/contracts/__tests__/host-contract-client.test.d.ts +1 -0
- package/dist/contracts/__tests__/host-contract-client.test.js +793 -0
- package/dist/contracts/__tests__/host-contract-transport.test.js +15 -0
- package/dist/contracts/__tests__/registry.test.js +68 -0
- package/dist/contracts/__tests__/target-dispatch.d.ts +11 -0
- package/dist/contracts/__tests__/target-dispatch.js +24 -0
- package/dist/contracts/__tests__/tool-catalog-service.test.js +37 -1
- package/dist/contracts/__tests__/tool-catalog.test.js +12 -3
- package/dist/contracts/__tests__/validate.test.js +177 -7
- package/dist/contracts/__tests__/workflow-context.test.js +0 -11
- package/dist/contracts/builder.d.ts +16 -2
- package/dist/contracts/builder.js +44 -3
- package/dist/contracts/contract-definition-schema.d.ts +251 -0
- package/dist/contracts/contract-definition-schema.js +48 -0
- package/dist/contracts/contract-provider-router.d.ts +2 -0
- package/dist/contracts/contract-provider-router.js +5 -2
- package/dist/contracts/contract-proxy.d.ts +52 -18
- package/dist/contracts/contract-proxy.js +144 -203
- package/dist/contracts/contract-resolvers.d.ts +6 -4
- package/dist/contracts/contract-resolvers.js +32 -9
- package/dist/contracts/frozen-contracts.d.ts +59 -0
- package/dist/contracts/frozen-contracts.js +86 -0
- package/dist/contracts/host-contract-client.d.ts +134 -0
- package/dist/contracts/host-contract-client.js +396 -0
- package/dist/contracts/host-contract-transport.d.ts +2 -0
- package/dist/contracts/host-contract-transport.js +8 -0
- package/dist/contracts/index.d.ts +6 -4
- package/dist/contracts/index.js +11 -5
- package/dist/contracts/registry.d.ts +31 -1
- package/dist/contracts/registry.js +100 -6
- package/dist/contracts/system/contract-catalog-contract.d.ts +61 -6
- package/dist/contracts/system/contract-catalog-contract.js +17 -5
- package/dist/contracts/tool-catalog-service.js +26 -6
- package/dist/contracts/tool-catalog.d.ts +1 -1
- package/dist/contracts/tool-catalog.js +4 -1
- package/dist/contracts/types.d.ts +32 -2
- package/dist/contracts/validate.d.ts +16 -1
- package/dist/contracts/validate.js +139 -25
- package/dist/data/change-tracking.d.ts +17 -5
- package/dist/data/change-tracking.js +22 -6
- package/dist/data/change-tracking.test.d.ts +1 -0
- package/dist/data/change-tracking.test.js +62 -0
- package/dist/data/create-group.js +1 -1
- package/dist/data/group-members.d.ts +14 -0
- package/dist/data/group-members.js +36 -0
- package/dist/data/group-members.test.js +49 -0
- package/dist/data/orm/sql.data-source.d.ts +9 -0
- package/dist/data/orm/sql.data-source.js +47 -2
- package/dist/data/orm/sql.data-source.test.js +142 -0
- package/dist/data/orm/table-container.js +9 -9
- package/dist/data/orm/table-container.test.js +44 -0
- package/dist/data/orm/types.d.ts +41 -1
- package/dist/data/orm/types.js +159 -28
- package/dist/data/orm/types.test.js +129 -0
- package/dist/data/peer-types.d.ts +8 -62
- package/dist/data/persistent-vars.d.ts +4 -1
- package/dist/data/persistent-vars.js +7 -2
- package/dist/data/tools.d.ts +29 -221
- package/dist/define/also-implements.test.d.ts +1 -0
- package/dist/define/also-implements.test.js +123 -0
- package/dist/define/context.d.ts +77 -0
- package/dist/define/context.js +2 -0
- package/dist/define/contract-ref.d.ts +177 -0
- package/dist/define/contract-ref.js +67 -0
- package/dist/define/declare-contract.d.ts +167 -0
- package/dist/define/declare-contract.js +220 -0
- package/dist/define/declare-contract.test.d.ts +1 -0
- package/dist/define/declare-contract.test.js +573 -0
- package/dist/define/define-package.d.ts +203 -0
- package/dist/define/define-package.js +289 -0
- package/dist/define/define.test.d.ts +1 -0
- package/dist/define/define.test.js +1010 -0
- package/dist/define/empty-fields.d.ts +15 -0
- package/dist/define/empty-fields.js +134 -0
- package/dist/define/empty-fields.test.d.ts +1 -0
- package/dist/define/empty-fields.test.js +55 -0
- package/dist/define/events-observables.test.d.ts +1 -0
- package/dist/define/events-observables.test.js +1013 -0
- package/dist/define/events.d.ts +81 -0
- package/dist/define/events.js +81 -0
- package/dist/define/generated-field.d.ts +20 -0
- package/dist/define/generated-field.js +25 -0
- package/dist/define/index.d.ts +28 -0
- package/dist/define/index.js +62 -0
- package/dist/define/lazy-members.d.ts +8 -0
- package/dist/define/lazy-members.js +38 -0
- package/dist/define/manifest.d.ts +63 -0
- package/dist/define/manifest.js +653 -0
- package/dist/define/observables.d.ts +146 -0
- package/dist/define/observables.js +187 -0
- package/dist/define/serve-members.d.ts +21 -0
- package/dist/define/serve-members.js +152 -0
- package/dist/define/serve-members.test.d.ts +1 -0
- package/dist/define/serve-members.test.js +630 -0
- package/dist/define/serve.d.ts +22 -0
- package/dist/define/serve.js +58 -0
- package/dist/define/system-contracts.d.ts +41 -0
- package/dist/define/system-contracts.js +21 -0
- package/dist/define/table-handle.d.ts +13 -0
- package/dist/define/table-handle.js +36 -0
- package/dist/define/table.d.ts +128 -0
- package/dist/define/table.js +122 -0
- package/dist/define/tool.d.ts +129 -0
- package/dist/define/tool.js +91 -0
- package/dist/define/vars.d.ts +82 -0
- package/dist/define/vars.js +256 -0
- package/dist/define/vars.test.d.ts +1 -0
- package/dist/define/vars.test.js +39 -0
- package/dist/define/wire-value.d.ts +12 -0
- package/dist/define/wire-value.js +76 -0
- package/dist/define/worker-state.d.ts +15 -0
- package/dist/define/worker-state.js +45 -0
- package/dist/device/tx-encoding.test.js +10 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -1
- package/dist/invites/group-approval.js +4 -2
- package/dist/invites/group-approval.test.js +2 -0
- package/dist/observable.d.ts +42 -2
- package/dist/observable.js +25 -7
- package/dist/package-build/build-package.d.ts +60 -0
- package/dist/package-build/build-package.js +312 -0
- package/dist/package-build/build-package.test.d.ts +1 -0
- package/dist/package-build/build-package.test.js +204 -0
- package/dist/package-build/cli.d.ts +7 -0
- package/dist/package-build/cli.js +218 -0
- package/dist/package-build/contract-add.d.ts +35 -0
- package/dist/package-build/contract-add.js +97 -0
- package/dist/package-build/contract-add.test.d.ts +1 -0
- package/dist/package-build/contract-add.test.js +467 -0
- package/dist/package-build/contract-source.d.ts +59 -0
- package/dist/package-build/contract-source.js +471 -0
- package/dist/package-build/freeze-contracts.d.ts +56 -0
- package/dist/package-build/freeze-contracts.js +198 -0
- package/dist/package-build/freeze-contracts.test.d.ts +1 -0
- package/dist/package-build/freeze-contracts.test.js +355 -0
- package/dist/package-installer/package-creator.d.ts +4 -0
- package/dist/package-installer/package-creator.js +57 -16
- package/dist/package-installer/package-creator.test.js +86 -0
- package/dist/package-loader/contract-members.d.ts +53 -0
- package/dist/package-loader/contract-members.js +31 -0
- package/dist/package-loader/contract-package-loader.js +5 -1
- package/dist/package-loader/contract-package-loader.test.js +11 -0
- package/dist/package-loader/host-authority-loading.test.js +9 -5
- package/dist/package-loader/index.d.ts +1 -0
- package/dist/package-loader/index.js +1 -0
- package/dist/package-loader/isolated-artifact.d.ts +80 -17
- package/dist/package-loader/isolated-artifact.js +257 -51
- package/dist/package-loader/isolated-artifact.test.js +294 -21
- package/dist/package-loader/isolated-runtime.d.ts +22 -7
- package/dist/package-loader/isolated-table-gateway.js +30 -11
- package/dist/package-loader/isolated-table-gateway.test.js +243 -0
- package/dist/package-loader/package-loader.d.ts +8 -1
- package/dist/package-loader/package-loader.js +39 -20
- package/dist/package-loader/package-loader.test.js +47 -0
- package/dist/peer-id.d.ts +25 -0
- package/dist/peer-id.js +99 -0
- package/dist/serial-json.compat.test.d.ts +1 -0
- package/dist/serial-json.compat.test.js +369 -0
- package/dist/serial-json.d.ts +41 -5
- package/dist/serial-json.js +216 -157
- package/dist/serial-json.oracle.d.ts +21 -0
- package/dist/serial-json.oracle.js +222 -0
- package/dist/serial-json.test.js +315 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +8 -0
- package/dist/testing/mock-host-contract.d.ts +123 -0
- package/dist/testing/mock-host-contract.js +434 -0
- package/dist/testing/mock-host-contract.test.d.ts +1 -0
- package/dist/testing/mock-host-contract.test.js +782 -0
- package/dist/types/field-type.d.ts +35 -18
- package/dist/types/field-type.js +4 -1
- package/dist/types/zod-types.d.ts +9 -0
- package/dist/types/zod-types.js +8 -2
- package/dist/utils.d.ts +22 -25
- package/dist/utils.js +45 -92
- package/dist/utils.test.js +36 -0
- package/package.json +9 -2
- package/testing/package.json +4 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ISOLATED_EVENT_HOST_MEMBER = exports.ISOLATED_PERSISTENT_VAR_HOST_MEMBER = exports.ISOLATED_TABLE_HOST_MEMBER = exports.ISOLATED_CONSUME_HOST_MEMBER = exports.ISOLATED_TOOL_TIMEOUT_MAX_MS = exports.ISOLATED_PACKAGE_MAGIC = void 0;
|
|
3
|
+
exports.ISOLATED_MAX_FIELD_DEPTH = exports.ISOLATED_OBSERVABLE_HOST_MEMBER = exports.ISOLATED_EVENT_HOST_MEMBER = exports.ISOLATED_PERSISTENT_VAR_HOST_MEMBER = exports.ISOLATED_TABLE_HOST_MEMBER = exports.ISOLATED_CONSUME_HOST_MEMBER = exports.ISOLATED_TOOL_TIMEOUT_MAX_MS = exports.ISOLATED_GUEST_API_VERSION = exports.ISOLATED_PACKAGE_MAGIC = void 0;
|
|
4
|
+
exports.servesContractMembers = servesContractMembers;
|
|
5
|
+
exports.enumValueFits = enumValueFits;
|
|
4
6
|
exports.looksLikeIsolatedPackageArtifact = looksLikeIsolatedPackageArtifact;
|
|
5
7
|
exports.validateIsolatedPackageArtifact = validateIsolatedPackageArtifact;
|
|
6
8
|
exports.parseIsolatedPackageArtifact = parseIsolatedPackageArtifact;
|
|
@@ -15,6 +17,7 @@ exports.packageProvidesEvent = packageProvidesEvent;
|
|
|
15
17
|
exports.packageProvidesTable = packageProvidesTable;
|
|
16
18
|
exports.findPackageTable = findPackageTable;
|
|
17
19
|
exports.toConsumedContracts = toConsumedContracts;
|
|
20
|
+
const contract_definition_schema_1 = require("../contracts/contract-definition-schema");
|
|
18
21
|
const access_level_1 = require("../data/access-level");
|
|
19
22
|
const persistent_var_identity_1 = require("../data/persistent-var-identity");
|
|
20
23
|
const field_type_1 = require("../types/field-type");
|
|
@@ -25,6 +28,30 @@ const utils_1 = require("../utils");
|
|
|
25
28
|
* opt-in format without evaluating source.
|
|
26
29
|
*/
|
|
27
30
|
exports.ISOLATED_PACKAGE_MAGIC = "PEERS_ISOLATED_PACKAGE_V1";
|
|
31
|
+
/**
|
|
32
|
+
* The latest guest API version an isolated manifest targets.
|
|
33
|
+
*
|
|
34
|
+
* - `3`: as `2`, and the package serves its own contract members. The host sends consumers'
|
|
35
|
+
* table calls, observable reads and writes, and subscriptions to the provider's worker
|
|
36
|
+
* (`contractMemberHandlerName`), and passes every table change, event, and observable
|
|
37
|
+
* value bound for a subscriber through it first (`contractDeliveryHandlerName`).
|
|
38
|
+
* - `2`: handlers receive `(args, ctx)` and calls run concurrently; the package orders any
|
|
39
|
+
* state it shares between calls itself. The host serves contract tables and stored
|
|
40
|
+
* observables from storage, without the worker.
|
|
41
|
+
* - Omitted: the legacy global helpers, with one call at a time.
|
|
42
|
+
*
|
|
43
|
+
* Hosts that predate a version reject it.
|
|
44
|
+
*/
|
|
45
|
+
exports.ISOLATED_GUEST_API_VERSION = 3;
|
|
46
|
+
/**
|
|
47
|
+
* True when the package serves its contract members itself (guest API 3): every consumer call
|
|
48
|
+
* and outgoing message goes through its worker.
|
|
49
|
+
*
|
|
50
|
+
* @param manifest - The package's manifest.
|
|
51
|
+
*/
|
|
52
|
+
function servesContractMembers(manifest) {
|
|
53
|
+
return manifest.guestApi === 3;
|
|
54
|
+
}
|
|
28
55
|
/** Longest per-tool invoke deadline an isolated manifest may declare. */
|
|
29
56
|
exports.ISOLATED_TOOL_TIMEOUT_MAX_MS = 120_000;
|
|
30
57
|
/** Host-contract member guests use to request a consumed-contract tool call. */
|
|
@@ -35,6 +62,8 @@ exports.ISOLATED_TABLE_HOST_MEMBER = "package.table";
|
|
|
35
62
|
exports.ISOLATED_PERSISTENT_VAR_HOST_MEMBER = "package.pvar";
|
|
36
63
|
/** Host-contract member guests use to emit a provided-contract event. */
|
|
37
64
|
exports.ISOLATED_EVENT_HOST_MEMBER = "contract.emit";
|
|
65
|
+
/** Host-contract member guests use to publish an in-memory provided-contract observable. */
|
|
66
|
+
exports.ISOLATED_OBSERVABLE_HOST_MEMBER = "package.observable";
|
|
38
67
|
const FIELD_TYPES = new Set(Object.values(field_type_1.FieldType));
|
|
39
68
|
const OBSERVABLE_FIELD_TYPES = new Set([
|
|
40
69
|
field_type_1.FieldType.id,
|
|
@@ -62,35 +91,139 @@ function assertContractVersion(version, label) {
|
|
|
62
91
|
throw new Error(`${label} must be a positive integer, got ${String(version)}`);
|
|
63
92
|
}
|
|
64
93
|
}
|
|
65
|
-
|
|
94
|
+
/**
|
|
95
|
+
* How deep field lists may nest through object `fields`: a tool's inputs, say, are level 1.
|
|
96
|
+
* Deeper nesting is refused, so a hostile manifest can't exhaust the validator's stack.
|
|
97
|
+
*/
|
|
98
|
+
exports.ISOLATED_MAX_FIELD_DEPTH = 16;
|
|
99
|
+
/** Field keys this SDK knows. Hosts drop any other key; the build refuses it (likely a typo). */
|
|
100
|
+
const FIELD_KEYS = new Set([
|
|
101
|
+
"name",
|
|
102
|
+
"description",
|
|
103
|
+
"type",
|
|
104
|
+
"defaultValue",
|
|
105
|
+
"optional",
|
|
106
|
+
"isArray",
|
|
107
|
+
"subType",
|
|
108
|
+
"fields",
|
|
109
|
+
"enumValues",
|
|
110
|
+
]);
|
|
111
|
+
/**
|
|
112
|
+
* True when `value` may appear in `enumValues` on a field of `type`: a string, number, or boolean
|
|
113
|
+
* of that type, or any of them on `any`. Shared with the manifest builder; not a public SDK export.
|
|
114
|
+
*
|
|
115
|
+
* @internal
|
|
116
|
+
* @param value - A candidate enum value.
|
|
117
|
+
* @param type - The field's type.
|
|
118
|
+
*/
|
|
119
|
+
function enumValueFits(value, type) {
|
|
120
|
+
switch (type) {
|
|
121
|
+
case field_type_1.FieldType.string:
|
|
122
|
+
return typeof value === "string";
|
|
123
|
+
case field_type_1.FieldType.number:
|
|
124
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
125
|
+
case field_type_1.FieldType.boolean:
|
|
126
|
+
return typeof value === "boolean";
|
|
127
|
+
case field_type_1.FieldType.any:
|
|
128
|
+
return (typeof value === "string" ||
|
|
129
|
+
typeof value === "boolean" ||
|
|
130
|
+
(typeof value === "number" && Number.isFinite(value)));
|
|
131
|
+
default:
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Validate one field and rebuild it from the keys this SDK knows. A key it doesn't know can only
|
|
137
|
+
* describe the value further (a key the host must honor comes with a new guest API version, which
|
|
138
|
+
* older hosts refuse), so it is dropped, or refused when `strict`.
|
|
139
|
+
*/
|
|
140
|
+
function validateField(field, label, strict, depth, named = true) {
|
|
141
|
+
if (!field || typeof field !== "object" || Array.isArray(field)) {
|
|
142
|
+
throw new Error(`${label} must be an object`);
|
|
143
|
+
}
|
|
144
|
+
const record = field;
|
|
145
|
+
if (strict) {
|
|
146
|
+
const unsupported = Object.keys(record).find((key) => !FIELD_KEYS.has(key) || (!named && key === "name"));
|
|
147
|
+
if (unsupported) {
|
|
148
|
+
throw new Error(`${label}.${unsupported} is not supported by isolated package artifacts`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (named && (typeof record.name !== "string" || record.name.trim() === "")) {
|
|
152
|
+
throw new Error(`${label}.name is required`);
|
|
153
|
+
}
|
|
154
|
+
if (typeof record.type !== "string" || !FIELD_TYPES.has(record.type)) {
|
|
155
|
+
throw new Error(`${label}.type must be a FieldType`);
|
|
156
|
+
}
|
|
157
|
+
const type = record.type;
|
|
158
|
+
if (record.enumValues !== undefined) {
|
|
159
|
+
if (!Array.isArray(record.enumValues) || record.enumValues.length === 0) {
|
|
160
|
+
throw new Error(`${label}.enumValues must be a non-empty array`);
|
|
161
|
+
}
|
|
162
|
+
if (!record.enumValues.every((value) => enumValueFits(value, type))) {
|
|
163
|
+
throw new Error(`${label}.enumValues must all be ${type === field_type_1.FieldType.any ? "strings, numbers, or booleans" : `${type} values`}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
let fields;
|
|
167
|
+
if (record.fields !== undefined) {
|
|
168
|
+
if (type !== field_type_1.FieldType.object) {
|
|
169
|
+
throw new Error(`${label}.fields is only allowed on object fields`);
|
|
170
|
+
}
|
|
171
|
+
if (depth >= exports.ISOLATED_MAX_FIELD_DEPTH) {
|
|
172
|
+
throw new Error(`${label}.fields nests more than ${exports.ISOLATED_MAX_FIELD_DEPTH} levels deep`);
|
|
173
|
+
}
|
|
174
|
+
fields = validateFields(record.fields, `${label}.fields`, strict, depth + 1);
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
...(named ? { name: record.name } : {}),
|
|
178
|
+
type,
|
|
179
|
+
...(typeof record.description === "string" ? { description: record.description } : {}),
|
|
180
|
+
...(record.optional === true ? { optional: true } : {}),
|
|
181
|
+
...(record.isArray === true ? { isArray: true } : {}),
|
|
182
|
+
...(typeof record.subType === "string" ? { subType: record.subType } : {}),
|
|
183
|
+
...(record.defaultValue !== undefined ? { defaultValue: record.defaultValue } : {}),
|
|
184
|
+
...(record.enumValues !== undefined ? { enumValues: [...record.enumValues] } : {}),
|
|
185
|
+
...(fields ? { fields } : {}),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function validateFields(fields, label, strict, depth = 1) {
|
|
66
189
|
if (fields === undefined) {
|
|
67
190
|
return [];
|
|
68
191
|
}
|
|
69
192
|
if (!Array.isArray(fields)) {
|
|
70
193
|
throw new Error(`${label} must be an array`);
|
|
71
194
|
}
|
|
72
|
-
return fields.map((field, index) => {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
195
|
+
return fields.map((field, index) => validateField(field, `${label}[${index}]`, strict, depth));
|
|
196
|
+
}
|
|
197
|
+
/** Check carried fields before the recursive schema parse, including the host's depth bound. */
|
|
198
|
+
function validateCarriedFields(definition, label, strict) {
|
|
199
|
+
if (!definition || typeof definition !== "object" || Array.isArray(definition))
|
|
200
|
+
return;
|
|
201
|
+
const record = definition;
|
|
202
|
+
for (const [kind, fieldLists] of [
|
|
203
|
+
["tables", ["fields"]],
|
|
204
|
+
["tools", ["inputFields", "outputFields"]],
|
|
205
|
+
["events", ["payloadFields"]],
|
|
206
|
+
["observables", ["value"]],
|
|
207
|
+
]) {
|
|
208
|
+
const members = record[kind];
|
|
209
|
+
if (!Array.isArray(members))
|
|
210
|
+
continue;
|
|
211
|
+
for (const [index, member] of members.entries()) {
|
|
212
|
+
if (!member || typeof member !== "object" || Array.isArray(member))
|
|
213
|
+
continue;
|
|
214
|
+
for (const fields of fieldLists) {
|
|
215
|
+
const value = member[fields];
|
|
216
|
+
const fieldLabel = `${label}.${kind}[${index}].${fields}`;
|
|
217
|
+
if (fields === "value") {
|
|
218
|
+
if (value !== undefined)
|
|
219
|
+
validateField(value, fieldLabel, strict, 1, false);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
validateFields(value, fieldLabel, strict);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
83
225
|
}
|
|
84
|
-
|
|
85
|
-
name: record.name,
|
|
86
|
-
type: record.type,
|
|
87
|
-
...(typeof record.description === "string" ? { description: record.description } : {}),
|
|
88
|
-
...(record.optional === true ? { optional: true } : {}),
|
|
89
|
-
...(record.isArray === true ? { isArray: true } : {}),
|
|
90
|
-
...(typeof record.subType === "string" ? { subType: record.subType } : {}),
|
|
91
|
-
...(record.defaultValue !== undefined ? { defaultValue: record.defaultValue } : {}),
|
|
92
|
-
};
|
|
93
|
-
});
|
|
226
|
+
}
|
|
94
227
|
}
|
|
95
228
|
function assertJsonCompatible(value, label, seen = new WeakSet()) {
|
|
96
229
|
if (value === null ||
|
|
@@ -142,7 +275,7 @@ function validateObservableDefault(value, type, label) {
|
|
|
142
275
|
throw new Error(`${label} must match observable valueType ${type}`);
|
|
143
276
|
}
|
|
144
277
|
}
|
|
145
|
-
function validatePackageTables(manifest) {
|
|
278
|
+
function validatePackageTables(manifest, strict) {
|
|
146
279
|
if (manifest.tables === undefined) {
|
|
147
280
|
manifest.tables = [];
|
|
148
281
|
return;
|
|
@@ -167,13 +300,16 @@ function validatePackageTables(manifest) {
|
|
|
167
300
|
if (forbiddenIdentity) {
|
|
168
301
|
throw new Error(`${tableLabel} must not declare ${forbiddenIdentity}`);
|
|
169
302
|
}
|
|
170
|
-
assertOnlyKeys(table, ["name", "description", "primaryKeyName", "fields", "versionNumber"], tableLabel);
|
|
303
|
+
assertOnlyKeys(table, ["name", "description", "primaryKeyName", "fields", "versionNumber", "localOnly"], tableLabel);
|
|
171
304
|
if (typeof table.name !== "string" || table.name.trim() === "") {
|
|
172
305
|
throw new Error(`${tableLabel}.name is required`);
|
|
173
306
|
}
|
|
174
307
|
if (table.description !== undefined && typeof table.description !== "string") {
|
|
175
308
|
throw new Error(`${tableLabel}.description must be a string`);
|
|
176
309
|
}
|
|
310
|
+
if (table.localOnly !== undefined && typeof table.localOnly !== "boolean") {
|
|
311
|
+
throw new Error(`${tableLabel}.localOnly must be a boolean`);
|
|
312
|
+
}
|
|
177
313
|
if (tableNames.has(table.name)) {
|
|
178
314
|
throw new Error(`manifest declares duplicate table "${table.name}"`);
|
|
179
315
|
}
|
|
@@ -181,7 +317,7 @@ function validatePackageTables(manifest) {
|
|
|
181
317
|
if (typeof table.primaryKeyName !== "string" || table.primaryKeyName.trim() === "") {
|
|
182
318
|
throw new Error(`${tableLabel}.primaryKeyName is required`);
|
|
183
319
|
}
|
|
184
|
-
table.fields = validateFields(table.fields, `${tableLabel}.fields
|
|
320
|
+
table.fields = validateFields(table.fields, `${tableLabel}.fields`, strict);
|
|
185
321
|
if (table.fields.length === 0) {
|
|
186
322
|
throw new Error(`${tableLabel}.fields must not be empty`);
|
|
187
323
|
}
|
|
@@ -202,7 +338,7 @@ function validatePackageTables(manifest) {
|
|
|
202
338
|
}
|
|
203
339
|
}
|
|
204
340
|
}
|
|
205
|
-
function validateProvidedContract(contract, index, packageTables) {
|
|
341
|
+
function validateProvidedContract(contract, index, packageTables, guestApi, strict) {
|
|
206
342
|
const label = `provides[${index}]`;
|
|
207
343
|
if (!contract || typeof contract !== "object" || Array.isArray(contract)) {
|
|
208
344
|
throw new Error(`${label} must be an object`);
|
|
@@ -217,6 +353,7 @@ function validateProvidedContract(contract, index, packageTables) {
|
|
|
217
353
|
"observables",
|
|
218
354
|
"events",
|
|
219
355
|
"alsoImplements",
|
|
356
|
+
"exposes",
|
|
220
357
|
], label);
|
|
221
358
|
assertPeerId(contract.contractId, `${label}.contractId`);
|
|
222
359
|
assertContractVersion(contract.version, `${label}.version`);
|
|
@@ -256,8 +393,8 @@ function validateProvidedContract(contract, index, packageTables) {
|
|
|
256
393
|
if (typeof tool.usageDescription !== "string") {
|
|
257
394
|
throw new Error(`${toolLabel}.usageDescription is required`);
|
|
258
395
|
}
|
|
259
|
-
tool.inputFields = validateFields(tool.inputFields, `${toolLabel}.inputFields
|
|
260
|
-
tool.outputFields = validateFields(tool.outputFields, `${toolLabel}.outputFields
|
|
396
|
+
tool.inputFields = validateFields(tool.inputFields, `${toolLabel}.inputFields`, strict);
|
|
397
|
+
tool.outputFields = validateFields(tool.outputFields, `${toolLabel}.outputFields`, strict);
|
|
261
398
|
if (tool.authorityPropagation !== undefined &&
|
|
262
399
|
tool.authorityPropagation !== "cap" &&
|
|
263
400
|
tool.authorityPropagation !== "preserve")
|
|
@@ -326,7 +463,7 @@ function validateProvidedContract(contract, index, packageTables) {
|
|
|
326
463
|
if (!observable || typeof observable !== "object" || Array.isArray(observable)) {
|
|
327
464
|
throw new Error(`${observableLabel} must be an object`);
|
|
328
465
|
}
|
|
329
|
-
assertOnlyKeys(observable, ["name", "description", "valueType", "writable", "persistentVar"], observableLabel);
|
|
466
|
+
assertOnlyKeys(observable, ["name", "description", "valueType", "value", "writable", "persistentVar"], observableLabel);
|
|
330
467
|
if (typeof observable.name !== "string" || observable.name.trim() === "") {
|
|
331
468
|
throw new Error(`${observableLabel}.name is required`);
|
|
332
469
|
}
|
|
@@ -341,12 +478,23 @@ function validateProvidedContract(contract, index, packageTables) {
|
|
|
341
478
|
!OBSERVABLE_FIELD_TYPES.has(observable.valueType)) {
|
|
342
479
|
throw new Error(`${observableLabel}.valueType must be a JSON-compatible FieldType`);
|
|
343
480
|
}
|
|
481
|
+
if (observable.value !== undefined) {
|
|
482
|
+
const { name: _name, ...value } = validateField(observable.value, `${observableLabel}.value`, strict, 1, false);
|
|
483
|
+
observable.value = value;
|
|
484
|
+
}
|
|
344
485
|
if (typeof observable.writable !== "boolean") {
|
|
345
486
|
throw new Error(`${observableLabel}.writable must be a boolean`);
|
|
346
487
|
}
|
|
347
488
|
const binding = observable.persistentVar;
|
|
489
|
+
if (binding === undefined) {
|
|
490
|
+
// Held by the worker, which publishes it: that needs guest API 2 or later.
|
|
491
|
+
if (guestApi === undefined) {
|
|
492
|
+
throw new Error(`${observableLabel}.persistentVar is required unless the package uses guestApi 2 or later`);
|
|
493
|
+
}
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
348
496
|
if (!binding || typeof binding !== "object" || Array.isArray(binding)) {
|
|
349
|
-
throw new Error(`${observableLabel}.persistentVar
|
|
497
|
+
throw new Error(`${observableLabel}.persistentVar must be an object`);
|
|
350
498
|
}
|
|
351
499
|
assertOnlyKeys(binding, ["scope", "name", "defaultValue"], `${observableLabel}.persistentVar`);
|
|
352
500
|
if (typeof binding.scope !== "string" || !PERSISTENT_VAR_SCOPE_SET.has(binding.scope)) {
|
|
@@ -385,7 +533,7 @@ function validateProvidedContract(contract, index, packageTables) {
|
|
|
385
533
|
if (typeof event.description !== "string") {
|
|
386
534
|
throw new Error(`${eventLabel}.description is required`);
|
|
387
535
|
}
|
|
388
|
-
event.payloadFields = validateFields(event.payloadFields, `${eventLabel}.payloadFields
|
|
536
|
+
event.payloadFields = validateFields(event.payloadFields, `${eventLabel}.payloadFields`, strict);
|
|
389
537
|
}
|
|
390
538
|
if (contract.tools.length +
|
|
391
539
|
contract.tables.length +
|
|
@@ -405,8 +553,23 @@ function validateProvidedContract(contract, index, packageTables) {
|
|
|
405
553
|
if (!declaration || typeof declaration !== "object" || Array.isArray(declaration)) {
|
|
406
554
|
throw new Error(`${declarationLabel} must be an object`);
|
|
407
555
|
}
|
|
408
|
-
assertOnlyKeys(declaration, ["contractId", "version"], declarationLabel);
|
|
556
|
+
assertOnlyKeys(declaration, ["contractId", "version", "definition"], declarationLabel);
|
|
409
557
|
assertPeerId(declaration.contractId, `${declarationLabel}.contractId`);
|
|
558
|
+
if (declaration.definition !== undefined) {
|
|
559
|
+
// The target's definition, carried so the host can register it on a cold start.
|
|
560
|
+
if (typeof declaration.version !== "number") {
|
|
561
|
+
throw new Error(`${declarationLabel}.definition needs a single version`);
|
|
562
|
+
}
|
|
563
|
+
validateCarriedFields(declaration.definition, `${declarationLabel}.definition`, strict);
|
|
564
|
+
const checked = contract_definition_schema_1.contractDefinitionSchema.safeParse(declaration.definition);
|
|
565
|
+
if (!checked.success) {
|
|
566
|
+
throw new Error(`${declarationLabel}.definition is invalid: ${checked.error.message}`);
|
|
567
|
+
}
|
|
568
|
+
if (declaration.definition.contractId !== declaration.contractId ||
|
|
569
|
+
declaration.definition.version !== declaration.version) {
|
|
570
|
+
throw new Error(`${declarationLabel}.definition must be the declared contract version`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
410
573
|
if (typeof declaration.version === "number") {
|
|
411
574
|
assertContractVersion(declaration.version, `${declarationLabel}.version`);
|
|
412
575
|
continue;
|
|
@@ -423,6 +586,34 @@ function validateProvidedContract(contract, index, packageTables) {
|
|
|
423
586
|
throw new Error(`${declarationLabel}.version.from must not exceed .to`);
|
|
424
587
|
}
|
|
425
588
|
}
|
|
589
|
+
validateExposes(contract, label);
|
|
590
|
+
}
|
|
591
|
+
/** Check a provided contract's `exposes` list; the manifest checks that it names provided contracts. */
|
|
592
|
+
function validateExposes(contract, label) {
|
|
593
|
+
if (contract.exposes === undefined) {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
if (!Array.isArray(contract.exposes)) {
|
|
597
|
+
throw new Error(`${label}.exposes must be an array`);
|
|
598
|
+
}
|
|
599
|
+
const seen = new Set();
|
|
600
|
+
for (const [index, exposed] of contract.exposes.entries()) {
|
|
601
|
+
const exposedLabel = `${label}.exposes[${index}]`;
|
|
602
|
+
if (!exposed || typeof exposed !== "object" || Array.isArray(exposed)) {
|
|
603
|
+
throw new Error(`${exposedLabel} must be an object`);
|
|
604
|
+
}
|
|
605
|
+
assertOnlyKeys(exposed, ["contractId", "version"], exposedLabel);
|
|
606
|
+
assertPeerId(exposed.contractId, `${exposedLabel}.contractId`);
|
|
607
|
+
assertContractVersion(exposed.version, `${exposedLabel}.version`);
|
|
608
|
+
const key = `${exposed.contractId}@${exposed.version}`;
|
|
609
|
+
if (key === `${contract.contractId}@${contract.version}`) {
|
|
610
|
+
throw new Error(`${exposedLabel}: a contract can't expose itself`);
|
|
611
|
+
}
|
|
612
|
+
if (seen.has(key)) {
|
|
613
|
+
throw new Error(`${exposedLabel}: ${key} is exposed twice`);
|
|
614
|
+
}
|
|
615
|
+
seen.add(key);
|
|
616
|
+
}
|
|
426
617
|
}
|
|
427
618
|
function validateConsumedContract(dep, index) {
|
|
428
619
|
const label = `consumes[${index}]`;
|
|
@@ -565,11 +756,17 @@ function looksLikeIsolatedPackageArtifact(bundle) {
|
|
|
565
756
|
* Validate a wire-safe isolated package artifact. Does not evaluate
|
|
566
757
|
* `providerSource`.
|
|
567
758
|
*
|
|
759
|
+
* Fields keep only the keys this SDK knows: a host drops any other field key, since a newer
|
|
760
|
+
* SDK's keys only describe values further.
|
|
761
|
+
*
|
|
568
762
|
* @param artifact - Parsed artifact object.
|
|
763
|
+
* @param options - `strict` refuses field keys this SDK doesn't know instead; the package build
|
|
764
|
+
* uses it, where an unknown key is a mistake.
|
|
569
765
|
* @returns The same manifest after in-place field normalization.
|
|
570
766
|
* @throws If identity, contracts, tools, or source are invalid.
|
|
571
767
|
*/
|
|
572
|
-
function validateIsolatedPackageArtifact(artifact) {
|
|
768
|
+
function validateIsolatedPackageArtifact(artifact, options = {}) {
|
|
769
|
+
const strict = options.strict === true;
|
|
573
770
|
if (!artifact || typeof artifact !== "object" || Array.isArray(artifact)) {
|
|
574
771
|
throw new Error("Package artifact must be an object");
|
|
575
772
|
}
|
|
@@ -586,8 +783,10 @@ function validateIsolatedPackageArtifact(artifact) {
|
|
|
586
783
|
"tables",
|
|
587
784
|
"network",
|
|
588
785
|
"appNavs",
|
|
786
|
+
// Voice Skill v1's retired action aliases: an older manifest may carry them; ignored.
|
|
589
787
|
"actionAliases",
|
|
590
788
|
"executionMode",
|
|
789
|
+
"guestApi",
|
|
591
790
|
], "manifest");
|
|
592
791
|
if (typeof providerSource !== "string" || providerSource.trim() === "") {
|
|
593
792
|
throw new Error("Package artifact.providerSource must be a non-empty string");
|
|
@@ -597,6 +796,9 @@ function validateIsolatedPackageArtifact(artifact) {
|
|
|
597
796
|
manifest.executionMode !== "privileged") {
|
|
598
797
|
throw new Error('manifest.executionMode must be "sandbox" or "privileged"');
|
|
599
798
|
}
|
|
799
|
+
if (manifest.guestApi !== undefined && manifest.guestApi !== 2 && manifest.guestApi !== 3) {
|
|
800
|
+
throw new Error("manifest.guestApi must be 2 or 3 when set");
|
|
801
|
+
}
|
|
600
802
|
assertPeerId(manifest.packageId, "packageId");
|
|
601
803
|
if (typeof manifest.version !== "string" || manifest.version.trim() === "") {
|
|
602
804
|
throw new Error("package version is required");
|
|
@@ -607,36 +809,39 @@ function validateIsolatedPackageArtifact(artifact) {
|
|
|
607
809
|
if (!Array.isArray(manifest.consumes)) {
|
|
608
810
|
throw new Error("package.consumes must be an array");
|
|
609
811
|
}
|
|
610
|
-
validatePackageTables(manifest);
|
|
812
|
+
validatePackageTables(manifest, strict);
|
|
611
813
|
const packageTables = manifest.tables ?? [];
|
|
612
814
|
const providedKeys = new Set();
|
|
613
815
|
for (const [index, contract] of manifest.provides.entries()) {
|
|
614
|
-
validateProvidedContract(contract, index, packageTables);
|
|
816
|
+
validateProvidedContract(contract, index, packageTables, manifest.guestApi, strict);
|
|
615
817
|
const key = `${contract.contractId}@${contract.version}`;
|
|
616
818
|
if (providedKeys.has(key)) {
|
|
617
819
|
throw new Error(`Duplicate provided contract ${key}`);
|
|
618
820
|
}
|
|
619
821
|
providedKeys.add(key);
|
|
620
822
|
}
|
|
621
|
-
for (const [index,
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
if (
|
|
630
|
-
throw new Error(
|
|
823
|
+
for (const [index, contract] of manifest.provides.entries()) {
|
|
824
|
+
for (const target of contract.alsoImplements ?? []) {
|
|
825
|
+
// Legacy ranges may cover an explicitly provided version. Its own handlers take
|
|
826
|
+
// precedence in the registry; retain that behavior for existing hand-written manifests.
|
|
827
|
+
// A carried definition already requires a single version during contract validation.
|
|
828
|
+
if (typeof target.version !== "number")
|
|
829
|
+
continue;
|
|
830
|
+
const key = `${target.contractId}@${target.version}`;
|
|
831
|
+
if (providedKeys.has(key)) {
|
|
832
|
+
throw new Error(`provides[${index}] also implements ${key}, which the package provides explicitly`);
|
|
631
833
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
throw new Error(`
|
|
834
|
+
}
|
|
835
|
+
for (const exposed of contract.exposes ?? []) {
|
|
836
|
+
const key = `${exposed.contractId}@${exposed.version}`;
|
|
837
|
+
if (!providedKeys.has(key)) {
|
|
838
|
+
throw new Error(`provides[${index}] exposes ${key}, which the package doesn't provide`);
|
|
637
839
|
}
|
|
638
840
|
}
|
|
639
841
|
}
|
|
842
|
+
for (const [index, dep] of manifest.consumes.entries()) {
|
|
843
|
+
validateConsumedContract(dep, index);
|
|
844
|
+
}
|
|
640
845
|
manifest.appNavs = validateAppNavs(manifest.appNavs);
|
|
641
846
|
validatePackageNetwork(manifest);
|
|
642
847
|
return manifest;
|
|
@@ -716,6 +921,7 @@ function isolatedContractToDefinition(contract, packageTables) {
|
|
|
716
921
|
name: observable.name,
|
|
717
922
|
description: observable.description,
|
|
718
923
|
valueType: observable.valueType,
|
|
924
|
+
...(observable.value ? { value: observable.value } : {}),
|
|
719
925
|
writable: observable.writable,
|
|
720
926
|
}));
|
|
721
927
|
const events = (contract.events ?? []).map((event) => ({
|