space-data-module-sdk 0.1.0 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +236 -73
- package/bin/space-data-module.js +24 -0
- package/package.json +16 -4
- package/schemas/ModuleBundle.fbs +108 -0
- package/schemas/PluginInvokeRequest.fbs +18 -0
- package/schemas/PluginInvokeResponse.fbs +30 -0
- package/schemas/PluginManifest.fbs +33 -1
- package/schemas/TypedArenaBuffer.fbs +23 -2
- package/src/bundle/codec.js +268 -0
- package/src/bundle/constants.js +8 -0
- package/src/bundle/index.js +3 -0
- package/src/bundle/wasm.js +447 -0
- package/src/compiler/compileModule.js +353 -37
- package/src/compiler/emceptionNode.js +217 -0
- package/src/compiler/flatcSupport.js +66 -0
- package/src/compiler/invokeGlue.js +884 -0
- package/src/compliance/pluginCompliance.js +575 -1
- package/src/generated/orbpro/invoke/plugin-invoke-request.d.ts +51 -0
- package/src/generated/orbpro/invoke/plugin-invoke-request.d.ts.map +1 -0
- package/src/generated/orbpro/invoke/plugin-invoke-request.js +131 -0
- package/src/generated/orbpro/invoke/plugin-invoke-request.js.map +1 -0
- package/src/generated/orbpro/invoke/plugin-invoke-request.ts +173 -0
- package/src/generated/orbpro/invoke/plugin-invoke-response.d.ts +76 -0
- package/src/generated/orbpro/invoke/plugin-invoke-response.d.ts.map +1 -0
- package/src/generated/orbpro/invoke/plugin-invoke-response.js +184 -0
- package/src/generated/orbpro/invoke/plugin-invoke-response.js.map +1 -0
- package/src/generated/orbpro/invoke/plugin-invoke-response.ts +243 -0
- package/src/generated/orbpro/invoke.d.ts +3 -0
- package/src/generated/orbpro/invoke.d.ts.map +1 -0
- package/src/generated/orbpro/invoke.js +5 -0
- package/src/generated/orbpro/invoke.js.map +1 -0
- package/src/generated/orbpro/invoke.ts +6 -0
- package/src/generated/orbpro/manifest/accepted-type-set.d.ts +4 -4
- package/src/generated/orbpro/manifest/accepted-type-set.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/accepted-type-set.js +18 -11
- package/src/generated/orbpro/manifest/accepted-type-set.js.map +1 -1
- package/src/generated/orbpro/manifest/build-artifact.d.ts +1 -1
- package/src/generated/orbpro/manifest/build-artifact.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/build-artifact.js +28 -15
- package/src/generated/orbpro/manifest/build-artifact.js.map +1 -1
- package/src/generated/orbpro/manifest/capability-kind.d.ts +26 -1
- package/src/generated/orbpro/manifest/capability-kind.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/capability-kind.js +25 -0
- package/src/generated/orbpro/manifest/capability-kind.js.map +1 -1
- package/src/generated/orbpro/manifest/capability-kind.ts +25 -0
- package/src/generated/orbpro/manifest/drain-policy.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/drain-policy.js.map +1 -1
- package/src/generated/orbpro/manifest/host-capability.d.ts +2 -2
- package/src/generated/orbpro/manifest/host-capability.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/host-capability.js +19 -11
- package/src/generated/orbpro/manifest/host-capability.js.map +1 -1
- package/src/generated/orbpro/manifest/invoke-surface.d.ts +8 -0
- package/src/generated/orbpro/manifest/invoke-surface.d.ts.map +1 -0
- package/src/generated/orbpro/manifest/invoke-surface.js +11 -0
- package/src/generated/orbpro/manifest/invoke-surface.js.map +1 -0
- package/src/generated/orbpro/manifest/invoke-surface.ts +11 -0
- package/src/generated/orbpro/manifest/method-manifest.d.ts +6 -6
- package/src/generated/orbpro/manifest/method-manifest.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/method-manifest.js +33 -16
- package/src/generated/orbpro/manifest/method-manifest.js.map +1 -1
- package/src/generated/orbpro/manifest/plugin-family.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/plugin-family.js.map +1 -1
- package/src/generated/orbpro/manifest/plugin-manifest.d.ts +10 -2
- package/src/generated/orbpro/manifest/plugin-manifest.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/plugin-manifest.js +48 -9
- package/src/generated/orbpro/manifest/plugin-manifest.js.map +1 -1
- package/src/generated/orbpro/manifest/plugin-manifest.ts +322 -491
- package/src/generated/orbpro/manifest/port-manifest.d.ts +4 -4
- package/src/generated/orbpro/manifest/port-manifest.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/port-manifest.js +26 -13
- package/src/generated/orbpro/manifest/port-manifest.js.map +1 -1
- package/src/generated/orbpro/manifest/protocol-spec.d.ts +1 -1
- package/src/generated/orbpro/manifest/protocol-spec.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/protocol-spec.js +28 -15
- package/src/generated/orbpro/manifest/protocol-spec.js.map +1 -1
- package/src/generated/orbpro/manifest/timer-spec.d.ts +1 -1
- package/src/generated/orbpro/manifest/timer-spec.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/timer-spec.js +27 -16
- package/src/generated/orbpro/manifest/timer-spec.js.map +1 -1
- package/src/generated/orbpro/manifest.d.ts +13 -0
- package/src/generated/orbpro/manifest.d.ts.map +1 -0
- package/src/generated/orbpro/manifest.js +1 -0
- package/src/generated/orbpro/manifest.js.map +1 -0
- package/src/generated/orbpro/manifest.ts +16 -0
- package/src/generated/orbpro/module/canonicalization-rule.d.ts +48 -0
- package/src/generated/orbpro/module/canonicalization-rule.js +95 -0
- package/src/generated/orbpro/module/canonicalization-rule.ts +142 -0
- package/src/generated/orbpro/module/module-bundle-entry-role.d.ts +11 -0
- package/src/generated/orbpro/module/module-bundle-entry-role.js +14 -0
- package/src/generated/orbpro/module/module-bundle-entry-role.ts +15 -0
- package/src/generated/orbpro/module/module-bundle-entry.d.ts +97 -0
- package/src/generated/orbpro/module/module-bundle-entry.js +219 -0
- package/src/generated/orbpro/module/module-bundle-entry.ts +287 -0
- package/src/generated/orbpro/module/module-bundle.d.ts +86 -0
- package/src/generated/orbpro/module/module-bundle.js +213 -0
- package/src/generated/orbpro/module/module-bundle.ts +277 -0
- package/src/generated/orbpro/module/module-payload-encoding.d.ts +9 -0
- package/src/generated/orbpro/module/module-payload-encoding.js +12 -0
- package/src/generated/orbpro/module/module-payload-encoding.ts +13 -0
- package/src/generated/orbpro/module.d.ts +5 -0
- package/src/generated/orbpro/module.js +7 -0
- package/src/generated/orbpro/module.ts +9 -0
- package/src/generated/orbpro/stream/buffer-mutability.d.ts.map +1 -1
- package/src/generated/orbpro/stream/buffer-mutability.js.map +1 -1
- package/src/generated/orbpro/stream/buffer-ownership.d.ts.map +1 -1
- package/src/generated/orbpro/stream/buffer-ownership.js.map +1 -1
- package/src/generated/orbpro/stream/flat-buffer-type-ref.d.ts +22 -5
- package/src/generated/orbpro/stream/flat-buffer-type-ref.d.ts.map +1 -1
- package/src/generated/orbpro/stream/flat-buffer-type-ref.js +107 -17
- package/src/generated/orbpro/stream/flat-buffer-type-ref.js.map +1 -1
- package/src/generated/orbpro/stream/flat-buffer-type-ref.ts +126 -2
- package/src/generated/orbpro/stream/payload-wire-format.d.ts +8 -0
- package/src/generated/orbpro/stream/payload-wire-format.d.ts.map +1 -0
- package/src/generated/orbpro/stream/payload-wire-format.js +11 -0
- package/src/generated/orbpro/stream/payload-wire-format.js.map +1 -0
- package/src/generated/orbpro/stream/payload-wire-format.ts +11 -0
- package/src/generated/orbpro/stream/typed-arena-buffer.d.ts +4 -4
- package/src/generated/orbpro/stream/typed-arena-buffer.d.ts.map +1 -1
- package/src/generated/orbpro/stream/typed-arena-buffer.js +42 -24
- package/src/generated/orbpro/stream/typed-arena-buffer.js.map +1 -1
- package/src/host/abi.js +282 -0
- package/src/host/cron.js +247 -0
- package/src/host/index.js +3 -0
- package/src/host/nodeHost.js +2165 -0
- package/src/index.d.ts +958 -0
- package/src/index.js +12 -2
- package/src/invoke/codec.js +278 -0
- package/src/invoke/index.js +9 -0
- package/src/manifest/codec.js +10 -2
- package/src/manifest/index.js +5 -2
- package/src/manifest/normalize.js +90 -1
- package/src/runtime/constants.js +29 -0
- package/src/transport/pki.js +0 -5
- package/src/utils/encoding.js +9 -1
- package/src/utils/wasmCrypto.js +49 -1
|
@@ -2,17 +2,27 @@ import { access, readFile, readdir } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
+
DefaultInvokeExports,
|
|
5
6
|
DefaultManifestExports,
|
|
6
7
|
DrainPolicy,
|
|
7
8
|
ExternalInterfaceDirection,
|
|
8
9
|
ExternalInterfaceKind,
|
|
10
|
+
InvokeSurface,
|
|
11
|
+
RuntimeTarget,
|
|
9
12
|
} from "../runtime/constants.js";
|
|
10
13
|
|
|
11
14
|
export const RecommendedCapabilityIds = Object.freeze([
|
|
12
15
|
"clock",
|
|
13
16
|
"random",
|
|
17
|
+
"logging",
|
|
14
18
|
"timers",
|
|
19
|
+
"schedule_cron",
|
|
15
20
|
"http",
|
|
21
|
+
"tls",
|
|
22
|
+
"websocket",
|
|
23
|
+
"mqtt",
|
|
24
|
+
"tcp",
|
|
25
|
+
"udp",
|
|
16
26
|
"network",
|
|
17
27
|
"filesystem",
|
|
18
28
|
"pipe",
|
|
@@ -23,18 +33,52 @@ export const RecommendedCapabilityIds = Object.freeze([
|
|
|
23
33
|
"storage_adapter",
|
|
24
34
|
"storage_query",
|
|
25
35
|
"storage_write",
|
|
36
|
+
"context_read",
|
|
37
|
+
"context_write",
|
|
38
|
+
"process_exec",
|
|
39
|
+
"crypto_hash",
|
|
40
|
+
"crypto_sign",
|
|
41
|
+
"crypto_verify",
|
|
42
|
+
"crypto_encrypt",
|
|
43
|
+
"crypto_decrypt",
|
|
44
|
+
"crypto_key_agreement",
|
|
45
|
+
"crypto_kdf",
|
|
26
46
|
"wallet_sign",
|
|
27
47
|
"ipfs",
|
|
28
48
|
"scene_access",
|
|
49
|
+
"entity_access",
|
|
29
50
|
"render_hooks",
|
|
30
51
|
]);
|
|
31
52
|
|
|
32
53
|
const RecommendedCapabilitySet = new Set(RecommendedCapabilityIds);
|
|
54
|
+
const RecommendedRuntimeTargets = Object.freeze(Object.values(RuntimeTarget));
|
|
55
|
+
const RecommendedRuntimeTargetSet = new Set(RecommendedRuntimeTargets);
|
|
33
56
|
const DrainPolicySet = new Set(Object.values(DrainPolicy));
|
|
57
|
+
const InvokeSurfaceSet = new Set(Object.values(InvokeSurface));
|
|
34
58
|
const ExternalInterfaceDirectionSet = new Set(
|
|
35
59
|
Object.values(ExternalInterfaceDirection),
|
|
36
60
|
);
|
|
37
61
|
const ExternalInterfaceKindSet = new Set(Object.values(ExternalInterfaceKind));
|
|
62
|
+
const BrowserIncompatibleCapabilitySet = new Set([
|
|
63
|
+
"filesystem",
|
|
64
|
+
"pipe",
|
|
65
|
+
"network",
|
|
66
|
+
"tcp",
|
|
67
|
+
"udp",
|
|
68
|
+
"mqtt",
|
|
69
|
+
"tls",
|
|
70
|
+
"database",
|
|
71
|
+
"storage_adapter",
|
|
72
|
+
"storage_write",
|
|
73
|
+
"protocol_dial",
|
|
74
|
+
"protocol_handle",
|
|
75
|
+
"process_exec",
|
|
76
|
+
"wallet_sign",
|
|
77
|
+
"ipfs",
|
|
78
|
+
"scene_access",
|
|
79
|
+
"entity_access",
|
|
80
|
+
"render_hooks",
|
|
81
|
+
]);
|
|
38
82
|
const IgnoredDirectoryNames = new Set([
|
|
39
83
|
".git",
|
|
40
84
|
".hg",
|
|
@@ -63,6 +107,42 @@ function isNonEmptyString(value) {
|
|
|
63
107
|
return typeof value === "string" && value.trim().length > 0;
|
|
64
108
|
}
|
|
65
109
|
|
|
110
|
+
function hasNonEmptyByteSequence(value) {
|
|
111
|
+
if (typeof value === "string") {
|
|
112
|
+
return value.trim().length > 0;
|
|
113
|
+
}
|
|
114
|
+
if (Array.isArray(value)) {
|
|
115
|
+
return value.length > 0;
|
|
116
|
+
}
|
|
117
|
+
if (ArrayBuffer.isView(value)) {
|
|
118
|
+
return value.byteLength > 0;
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizePayloadWireFormatName(value) {
|
|
124
|
+
if (value === undefined || value === null || value === "") {
|
|
125
|
+
return "flatbuffer";
|
|
126
|
+
}
|
|
127
|
+
if (value === 0) {
|
|
128
|
+
return "flatbuffer";
|
|
129
|
+
}
|
|
130
|
+
if (value === 1) {
|
|
131
|
+
return "aligned-binary";
|
|
132
|
+
}
|
|
133
|
+
const normalized = String(value)
|
|
134
|
+
.trim()
|
|
135
|
+
.toLowerCase()
|
|
136
|
+
.replace(/_/g, "-");
|
|
137
|
+
if (normalized === "flatbuffer") {
|
|
138
|
+
return "flatbuffer";
|
|
139
|
+
}
|
|
140
|
+
if (normalized === "aligned-binary") {
|
|
141
|
+
return "aligned-binary";
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
66
146
|
function validateStringField(issues, value, location, label) {
|
|
67
147
|
if (!isNonEmptyString(value)) {
|
|
68
148
|
pushIssue(issues, "error", "missing-string", `${label} must be a non-empty string.`, location);
|
|
@@ -89,18 +169,93 @@ function validateIntegerField(issues, value, location, label, { min = null } = {
|
|
|
89
169
|
return true;
|
|
90
170
|
}
|
|
91
171
|
|
|
172
|
+
function validateOptionalIntegerField(
|
|
173
|
+
issues,
|
|
174
|
+
value,
|
|
175
|
+
location,
|
|
176
|
+
label,
|
|
177
|
+
options = {},
|
|
178
|
+
) {
|
|
179
|
+
if (value === undefined || value === null) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
return validateIntegerField(issues, value, location, label, options);
|
|
183
|
+
}
|
|
184
|
+
|
|
92
185
|
function validateAllowedType(type, issues, location) {
|
|
93
186
|
if (!type || typeof type !== "object" || Array.isArray(type)) {
|
|
94
187
|
pushIssue(issues, "error", "invalid-type-record", "Allowed type entries must be objects.", location);
|
|
95
188
|
return;
|
|
96
189
|
}
|
|
190
|
+
const wireFormat = normalizePayloadWireFormatName(type.wireFormat);
|
|
191
|
+
if (wireFormat === null) {
|
|
192
|
+
pushIssue(
|
|
193
|
+
issues,
|
|
194
|
+
"error",
|
|
195
|
+
"invalid-wire-format",
|
|
196
|
+
'Allowed type wireFormat must be "flatbuffer" or "aligned-binary".',
|
|
197
|
+
`${location}.wireFormat`,
|
|
198
|
+
);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (wireFormat === "aligned-binary") {
|
|
202
|
+
if (type.acceptsAnyFlatbuffer === true) {
|
|
203
|
+
pushIssue(
|
|
204
|
+
issues,
|
|
205
|
+
"error",
|
|
206
|
+
"accepts-any-flatbuffer-format-conflict",
|
|
207
|
+
"acceptsAnyFlatbuffer can only be used with flatbuffer wireFormat.",
|
|
208
|
+
`${location}.acceptsAnyFlatbuffer`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
if (!isNonEmptyString(type.schemaName)) {
|
|
212
|
+
pushIssue(
|
|
213
|
+
issues,
|
|
214
|
+
"error",
|
|
215
|
+
"missing-aligned-schema-name",
|
|
216
|
+
"Aligned-binary allowed types must declare schemaName.",
|
|
217
|
+
`${location}.schemaName`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
if (!isNonEmptyString(type.rootTypeName)) {
|
|
221
|
+
pushIssue(
|
|
222
|
+
issues,
|
|
223
|
+
"error",
|
|
224
|
+
"missing-aligned-root-type-name",
|
|
225
|
+
"Aligned-binary allowed types must declare rootTypeName.",
|
|
226
|
+
`${location}.rootTypeName`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
validateOptionalIntegerField(
|
|
230
|
+
issues,
|
|
231
|
+
type.fixedStringLength,
|
|
232
|
+
`${location}.fixedStringLength`,
|
|
233
|
+
"Allowed type fixedStringLength",
|
|
234
|
+
{ min: 0 },
|
|
235
|
+
);
|
|
236
|
+
validateIntegerField(
|
|
237
|
+
issues,
|
|
238
|
+
type.byteLength,
|
|
239
|
+
`${location}.byteLength`,
|
|
240
|
+
"Aligned-binary allowed type byteLength",
|
|
241
|
+
{ min: 1 },
|
|
242
|
+
);
|
|
243
|
+
validateIntegerField(
|
|
244
|
+
issues,
|
|
245
|
+
type.requiredAlignment,
|
|
246
|
+
`${location}.requiredAlignment`,
|
|
247
|
+
"Aligned-binary allowed type requiredAlignment",
|
|
248
|
+
{ min: 1 },
|
|
249
|
+
);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
97
252
|
if (type.acceptsAnyFlatbuffer === true) {
|
|
98
253
|
return;
|
|
99
254
|
}
|
|
100
255
|
if (
|
|
101
256
|
!isNonEmptyString(type.schemaName) &&
|
|
102
257
|
!isNonEmptyString(type.fileIdentifier) &&
|
|
103
|
-
!
|
|
258
|
+
!hasNonEmptyByteSequence(type.schemaHash)
|
|
104
259
|
) {
|
|
105
260
|
pushIssue(
|
|
106
261
|
issues,
|
|
@@ -110,6 +265,27 @@ function validateAllowedType(type, issues, location) {
|
|
|
110
265
|
location,
|
|
111
266
|
);
|
|
112
267
|
}
|
|
268
|
+
validateOptionalIntegerField(
|
|
269
|
+
issues,
|
|
270
|
+
type.fixedStringLength,
|
|
271
|
+
`${location}.fixedStringLength`,
|
|
272
|
+
"Allowed type fixedStringLength",
|
|
273
|
+
{ min: 0 },
|
|
274
|
+
);
|
|
275
|
+
validateOptionalIntegerField(
|
|
276
|
+
issues,
|
|
277
|
+
type.byteLength,
|
|
278
|
+
`${location}.byteLength`,
|
|
279
|
+
"Allowed type byteLength",
|
|
280
|
+
{ min: 0 },
|
|
281
|
+
);
|
|
282
|
+
validateOptionalIntegerField(
|
|
283
|
+
issues,
|
|
284
|
+
type.requiredAlignment,
|
|
285
|
+
`${location}.requiredAlignment`,
|
|
286
|
+
"Allowed type requiredAlignment",
|
|
287
|
+
{ min: 0 },
|
|
288
|
+
);
|
|
113
289
|
}
|
|
114
290
|
|
|
115
291
|
function validateAcceptedTypeSet(typeSet, issues, location) {
|
|
@@ -244,6 +420,298 @@ function validateExternalInterface(externalInterface, issues, location, declared
|
|
|
244
420
|
}
|
|
245
421
|
}
|
|
246
422
|
|
|
423
|
+
function validateTimer(timer, issues, location, methodLookup, declaredCapabilities) {
|
|
424
|
+
if (!timer || typeof timer !== "object" || Array.isArray(timer)) {
|
|
425
|
+
pushIssue(issues, "error", "invalid-timer", "Timer entries must be objects.", location);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const timerIdValid = validateStringField(
|
|
429
|
+
issues,
|
|
430
|
+
timer.timerId,
|
|
431
|
+
`${location}.timerId`,
|
|
432
|
+
"Timer timerId",
|
|
433
|
+
);
|
|
434
|
+
const methodIdValid = validateStringField(
|
|
435
|
+
issues,
|
|
436
|
+
timer.methodId,
|
|
437
|
+
`${location}.methodId`,
|
|
438
|
+
"Timer methodId",
|
|
439
|
+
);
|
|
440
|
+
let method = null;
|
|
441
|
+
if (methodIdValid) {
|
|
442
|
+
method = methodLookup.get(timer.methodId) ?? null;
|
|
443
|
+
if (!method) {
|
|
444
|
+
pushIssue(
|
|
445
|
+
issues,
|
|
446
|
+
"error",
|
|
447
|
+
"unknown-timer-method",
|
|
448
|
+
`Timer "${timer.timerId ?? "timer"}" references unknown method "${timer.methodId}".`,
|
|
449
|
+
`${location}.methodId`,
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (timer.inputPortId !== undefined && timer.inputPortId !== null) {
|
|
454
|
+
if (!isNonEmptyString(timer.inputPortId)) {
|
|
455
|
+
pushIssue(
|
|
456
|
+
issues,
|
|
457
|
+
"error",
|
|
458
|
+
"invalid-timer-input-port",
|
|
459
|
+
"Timer inputPortId must be a non-empty string when present.",
|
|
460
|
+
`${location}.inputPortId`,
|
|
461
|
+
);
|
|
462
|
+
} else if (
|
|
463
|
+
method &&
|
|
464
|
+
!method.inputPorts.some((port) => port?.portId === timer.inputPortId)
|
|
465
|
+
) {
|
|
466
|
+
pushIssue(
|
|
467
|
+
issues,
|
|
468
|
+
"error",
|
|
469
|
+
"unknown-timer-input-port",
|
|
470
|
+
`Timer "${timer.timerId ?? "timer"}" references unknown input port "${timer.inputPortId}" on method "${timer.methodId}".`,
|
|
471
|
+
`${location}.inputPortId`,
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (timer.defaultIntervalMs !== undefined) {
|
|
476
|
+
validateIntegerField(
|
|
477
|
+
issues,
|
|
478
|
+
timer.defaultIntervalMs,
|
|
479
|
+
`${location}.defaultIntervalMs`,
|
|
480
|
+
"Timer defaultIntervalMs",
|
|
481
|
+
{ min: 0 },
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
if (
|
|
485
|
+
timerIdValid &&
|
|
486
|
+
Array.isArray(declaredCapabilities) &&
|
|
487
|
+
!declaredCapabilities.includes("timers")
|
|
488
|
+
) {
|
|
489
|
+
pushIssue(
|
|
490
|
+
issues,
|
|
491
|
+
"error",
|
|
492
|
+
"undeclared-timer-capability",
|
|
493
|
+
`Timer "${timer.timerId}" requires the "timers" capability to be declared in manifest.capabilities.`,
|
|
494
|
+
location,
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function validateProtocol(protocol, issues, location, methodLookup, declaredCapabilities) {
|
|
500
|
+
if (!protocol || typeof protocol !== "object" || Array.isArray(protocol)) {
|
|
501
|
+
pushIssue(
|
|
502
|
+
issues,
|
|
503
|
+
"error",
|
|
504
|
+
"invalid-protocol",
|
|
505
|
+
"Protocol entries must be objects.",
|
|
506
|
+
location,
|
|
507
|
+
);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const protocolIdValid = validateStringField(
|
|
511
|
+
issues,
|
|
512
|
+
protocol.protocolId,
|
|
513
|
+
`${location}.protocolId`,
|
|
514
|
+
"Protocol protocolId",
|
|
515
|
+
);
|
|
516
|
+
const methodIdValid = validateStringField(
|
|
517
|
+
issues,
|
|
518
|
+
protocol.methodId,
|
|
519
|
+
`${location}.methodId`,
|
|
520
|
+
"Protocol methodId",
|
|
521
|
+
);
|
|
522
|
+
let method = null;
|
|
523
|
+
if (methodIdValid) {
|
|
524
|
+
method = methodLookup.get(protocol.methodId) ?? null;
|
|
525
|
+
if (!method) {
|
|
526
|
+
pushIssue(
|
|
527
|
+
issues,
|
|
528
|
+
"error",
|
|
529
|
+
"unknown-protocol-method",
|
|
530
|
+
`Protocol "${protocol.protocolId ?? "protocol"}" references unknown method "${protocol.methodId}".`,
|
|
531
|
+
`${location}.methodId`,
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (protocol.inputPortId !== undefined && protocol.inputPortId !== null) {
|
|
536
|
+
if (!isNonEmptyString(protocol.inputPortId)) {
|
|
537
|
+
pushIssue(
|
|
538
|
+
issues,
|
|
539
|
+
"error",
|
|
540
|
+
"invalid-protocol-input-port",
|
|
541
|
+
"Protocol inputPortId must be a non-empty string when present.",
|
|
542
|
+
`${location}.inputPortId`,
|
|
543
|
+
);
|
|
544
|
+
} else if (
|
|
545
|
+
method &&
|
|
546
|
+
!method.inputPorts.some((port) => port?.portId === protocol.inputPortId)
|
|
547
|
+
) {
|
|
548
|
+
pushIssue(
|
|
549
|
+
issues,
|
|
550
|
+
"error",
|
|
551
|
+
"unknown-protocol-input-port",
|
|
552
|
+
`Protocol "${protocol.protocolId ?? "protocol"}" references unknown input port "${protocol.inputPortId}" on method "${protocol.methodId}".`,
|
|
553
|
+
`${location}.inputPortId`,
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (protocol.outputPortId !== undefined && protocol.outputPortId !== null) {
|
|
558
|
+
if (!isNonEmptyString(protocol.outputPortId)) {
|
|
559
|
+
pushIssue(
|
|
560
|
+
issues,
|
|
561
|
+
"error",
|
|
562
|
+
"invalid-protocol-output-port",
|
|
563
|
+
"Protocol outputPortId must be a non-empty string when present.",
|
|
564
|
+
`${location}.outputPortId`,
|
|
565
|
+
);
|
|
566
|
+
} else if (
|
|
567
|
+
method &&
|
|
568
|
+
!method.outputPorts.some((port) => port?.portId === protocol.outputPortId)
|
|
569
|
+
) {
|
|
570
|
+
pushIssue(
|
|
571
|
+
issues,
|
|
572
|
+
"error",
|
|
573
|
+
"unknown-protocol-output-port",
|
|
574
|
+
`Protocol "${protocol.protocolId ?? "protocol"}" references unknown output port "${protocol.outputPortId}" on method "${protocol.methodId}".`,
|
|
575
|
+
`${location}.outputPortId`,
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (
|
|
580
|
+
protocolIdValid &&
|
|
581
|
+
Array.isArray(declaredCapabilities) &&
|
|
582
|
+
!declaredCapabilities.includes("protocol_handle") &&
|
|
583
|
+
!declaredCapabilities.includes("protocol_dial")
|
|
584
|
+
) {
|
|
585
|
+
pushIssue(
|
|
586
|
+
issues,
|
|
587
|
+
"error",
|
|
588
|
+
"undeclared-protocol-capability",
|
|
589
|
+
`Protocol "${protocol.protocolId}" requires "protocol_handle" or "protocol_dial" to be declared in manifest.capabilities.`,
|
|
590
|
+
location,
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function validateRuntimeTargets(runtimeTargets, declaredCapabilities, issues, sourceName) {
|
|
596
|
+
if (runtimeTargets === undefined) {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (!Array.isArray(runtimeTargets)) {
|
|
600
|
+
pushIssue(
|
|
601
|
+
issues,
|
|
602
|
+
"error",
|
|
603
|
+
"invalid-runtime-targets",
|
|
604
|
+
"manifest.runtimeTargets must be an array of non-empty strings when present.",
|
|
605
|
+
`${sourceName}.runtimeTargets`,
|
|
606
|
+
);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const seenTargets = new Set();
|
|
610
|
+
for (const target of runtimeTargets) {
|
|
611
|
+
if (!isNonEmptyString(target)) {
|
|
612
|
+
pushIssue(
|
|
613
|
+
issues,
|
|
614
|
+
"error",
|
|
615
|
+
"invalid-runtime-target",
|
|
616
|
+
"Runtime target entries must be non-empty strings.",
|
|
617
|
+
`${sourceName}.runtimeTargets`,
|
|
618
|
+
);
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
if (seenTargets.has(target)) {
|
|
622
|
+
pushIssue(
|
|
623
|
+
issues,
|
|
624
|
+
"warning",
|
|
625
|
+
"duplicate-runtime-target",
|
|
626
|
+
`Runtime target "${target}" is declared more than once.`,
|
|
627
|
+
`${sourceName}.runtimeTargets`,
|
|
628
|
+
);
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
seenTargets.add(target);
|
|
632
|
+
if (!RecommendedRuntimeTargetSet.has(target)) {
|
|
633
|
+
pushIssue(
|
|
634
|
+
issues,
|
|
635
|
+
"warning",
|
|
636
|
+
"noncanonical-runtime-target",
|
|
637
|
+
`Runtime target "${target}" is not in the current canonical runtime target set.`,
|
|
638
|
+
`${sourceName}.runtimeTargets`,
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (
|
|
643
|
+
seenTargets.has(RuntimeTarget.BROWSER) &&
|
|
644
|
+
Array.isArray(declaredCapabilities)
|
|
645
|
+
) {
|
|
646
|
+
for (const capability of declaredCapabilities) {
|
|
647
|
+
if (!BrowserIncompatibleCapabilitySet.has(capability)) {
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
pushIssue(
|
|
651
|
+
issues,
|
|
652
|
+
"error",
|
|
653
|
+
"capability-runtime-conflict",
|
|
654
|
+
`Capability "${capability}" is not available in the canonical browser runtime target.`,
|
|
655
|
+
`${sourceName}.capabilities`,
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function validateInvokeSurfaces(invokeSurfaces, issues, sourceName) {
|
|
662
|
+
if (invokeSurfaces === undefined) {
|
|
663
|
+
return [];
|
|
664
|
+
}
|
|
665
|
+
if (!Array.isArray(invokeSurfaces)) {
|
|
666
|
+
pushIssue(
|
|
667
|
+
issues,
|
|
668
|
+
"error",
|
|
669
|
+
"invalid-invoke-surfaces",
|
|
670
|
+
"manifest.invokeSurfaces must be an array of non-empty strings when present.",
|
|
671
|
+
`${sourceName}.invokeSurfaces`,
|
|
672
|
+
);
|
|
673
|
+
return [];
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const seen = new Set();
|
|
677
|
+
const normalized = [];
|
|
678
|
+
for (const surface of invokeSurfaces) {
|
|
679
|
+
if (!isNonEmptyString(surface)) {
|
|
680
|
+
pushIssue(
|
|
681
|
+
issues,
|
|
682
|
+
"error",
|
|
683
|
+
"invalid-invoke-surface",
|
|
684
|
+
"Invoke surface entries must be non-empty strings.",
|
|
685
|
+
`${sourceName}.invokeSurfaces`,
|
|
686
|
+
);
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
if (!InvokeSurfaceSet.has(surface)) {
|
|
690
|
+
pushIssue(
|
|
691
|
+
issues,
|
|
692
|
+
"error",
|
|
693
|
+
"unknown-invoke-surface",
|
|
694
|
+
`Invoke surface "${surface}" is invalid.`,
|
|
695
|
+
`${sourceName}.invokeSurfaces`,
|
|
696
|
+
);
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
if (seen.has(surface)) {
|
|
700
|
+
pushIssue(
|
|
701
|
+
issues,
|
|
702
|
+
"warning",
|
|
703
|
+
"duplicate-invoke-surface",
|
|
704
|
+
`Invoke surface "${surface}" is declared more than once.`,
|
|
705
|
+
`${sourceName}.invokeSurfaces`,
|
|
706
|
+
);
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
seen.add(surface);
|
|
710
|
+
normalized.push(surface);
|
|
711
|
+
}
|
|
712
|
+
return normalized;
|
|
713
|
+
}
|
|
714
|
+
|
|
247
715
|
export function validatePluginManifest(manifest, options = {}) {
|
|
248
716
|
const { sourceName = "manifest" } = options;
|
|
249
717
|
const issues = [];
|
|
@@ -307,6 +775,13 @@ export function validatePluginManifest(manifest, options = {}) {
|
|
|
307
775
|
}
|
|
308
776
|
}
|
|
309
777
|
}
|
|
778
|
+
validateRuntimeTargets(
|
|
779
|
+
manifest.runtimeTargets,
|
|
780
|
+
declaredCapabilities,
|
|
781
|
+
issues,
|
|
782
|
+
sourceName,
|
|
783
|
+
);
|
|
784
|
+
validateInvokeSurfaces(manifest.invokeSurfaces, issues, sourceName);
|
|
310
785
|
|
|
311
786
|
if (!Array.isArray(manifest.externalInterfaces)) {
|
|
312
787
|
pushIssue(
|
|
@@ -337,6 +812,7 @@ export function validatePluginManifest(manifest, options = {}) {
|
|
|
337
812
|
);
|
|
338
813
|
} else {
|
|
339
814
|
const seenMethodIds = new Set();
|
|
815
|
+
const methodLookup = new Map();
|
|
340
816
|
manifest.methods.forEach((method, index) => {
|
|
341
817
|
const location = `${sourceName}.methods[${index}]`;
|
|
342
818
|
if (!method || typeof method !== "object" || Array.isArray(method)) {
|
|
@@ -355,6 +831,7 @@ export function validatePluginManifest(manifest, options = {}) {
|
|
|
355
831
|
);
|
|
356
832
|
}
|
|
357
833
|
seenMethodIds.add(method.methodId);
|
|
834
|
+
methodLookup.set(method.methodId, method);
|
|
358
835
|
}
|
|
359
836
|
if (!Array.isArray(method.inputPorts) || method.inputPorts.length === 0) {
|
|
360
837
|
pushIssue(
|
|
@@ -403,6 +880,64 @@ export function validatePluginManifest(manifest, options = {}) {
|
|
|
403
880
|
);
|
|
404
881
|
}
|
|
405
882
|
});
|
|
883
|
+
|
|
884
|
+
if (manifest.timers !== undefined && !Array.isArray(manifest.timers)) {
|
|
885
|
+
pushIssue(
|
|
886
|
+
issues,
|
|
887
|
+
"error",
|
|
888
|
+
"invalid-timers-array",
|
|
889
|
+
"manifest.timers must be an array when present.",
|
|
890
|
+
`${sourceName}.timers`,
|
|
891
|
+
);
|
|
892
|
+
} else if (Array.isArray(manifest.timers)) {
|
|
893
|
+
manifest.timers.forEach((timer, index) => {
|
|
894
|
+
validateTimer(
|
|
895
|
+
timer,
|
|
896
|
+
issues,
|
|
897
|
+
`${sourceName}.timers[${index}]`,
|
|
898
|
+
methodLookup,
|
|
899
|
+
declaredCapabilities,
|
|
900
|
+
);
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
if (manifest.protocols !== undefined && !Array.isArray(manifest.protocols)) {
|
|
905
|
+
pushIssue(
|
|
906
|
+
issues,
|
|
907
|
+
"error",
|
|
908
|
+
"invalid-protocols-array",
|
|
909
|
+
"manifest.protocols must be an array when present.",
|
|
910
|
+
`${sourceName}.protocols`,
|
|
911
|
+
);
|
|
912
|
+
} else if (Array.isArray(manifest.protocols)) {
|
|
913
|
+
manifest.protocols.forEach((protocol, index) => {
|
|
914
|
+
validateProtocol(
|
|
915
|
+
protocol,
|
|
916
|
+
issues,
|
|
917
|
+
`${sourceName}.protocols[${index}]`,
|
|
918
|
+
methodLookup,
|
|
919
|
+
declaredCapabilities,
|
|
920
|
+
);
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
if (manifest.schemasUsed !== undefined && !Array.isArray(manifest.schemasUsed)) {
|
|
926
|
+
pushIssue(
|
|
927
|
+
issues,
|
|
928
|
+
"error",
|
|
929
|
+
"invalid-schemas-used-array",
|
|
930
|
+
"manifest.schemasUsed must be an array when present.",
|
|
931
|
+
`${sourceName}.schemasUsed`,
|
|
932
|
+
);
|
|
933
|
+
} else if (Array.isArray(manifest.schemasUsed)) {
|
|
934
|
+
manifest.schemasUsed.forEach((typeRef, index) => {
|
|
935
|
+
validateAllowedType(
|
|
936
|
+
typeRef,
|
|
937
|
+
issues,
|
|
938
|
+
`${sourceName}.schemasUsed[${index}]`,
|
|
939
|
+
);
|
|
940
|
+
});
|
|
406
941
|
}
|
|
407
942
|
|
|
408
943
|
return buildComplianceReport({
|
|
@@ -414,8 +949,15 @@ export function validatePluginManifest(manifest, options = {}) {
|
|
|
414
949
|
});
|
|
415
950
|
}
|
|
416
951
|
|
|
952
|
+
const MAX_MANIFEST_BYTES = 4 * 1024 * 1024;
|
|
953
|
+
|
|
417
954
|
export async function loadManifestFromFile(manifestPath) {
|
|
418
955
|
const contents = await readFile(manifestPath, "utf8");
|
|
956
|
+
if (contents.length > MAX_MANIFEST_BYTES) {
|
|
957
|
+
throw new Error(
|
|
958
|
+
`Manifest at ${manifestPath} exceeds ${MAX_MANIFEST_BYTES} byte limit.`,
|
|
959
|
+
);
|
|
960
|
+
}
|
|
419
961
|
return JSON.parse(contents);
|
|
420
962
|
}
|
|
421
963
|
|
|
@@ -441,6 +983,9 @@ export async function validatePluginArtifact(options) {
|
|
|
441
983
|
const issues = [...report.issues];
|
|
442
984
|
let resolvedExportNames = [];
|
|
443
985
|
let checkedArtifact = false;
|
|
986
|
+
const declaredInvokeSurfaces = Array.isArray(manifest?.invokeSurfaces)
|
|
987
|
+
? manifest.invokeSurfaces.filter((surface) => InvokeSurfaceSet.has(surface))
|
|
988
|
+
: [];
|
|
444
989
|
|
|
445
990
|
if (Array.isArray(exportNames)) {
|
|
446
991
|
resolvedExportNames = [...exportNames];
|
|
@@ -465,6 +1010,35 @@ export async function validatePluginArtifact(options) {
|
|
|
465
1010
|
);
|
|
466
1011
|
}
|
|
467
1012
|
}
|
|
1013
|
+
if (declaredInvokeSurfaces.includes(InvokeSurface.DIRECT)) {
|
|
1014
|
+
for (const symbol of [
|
|
1015
|
+
DefaultInvokeExports.invokeSymbol,
|
|
1016
|
+
DefaultInvokeExports.allocSymbol,
|
|
1017
|
+
DefaultInvokeExports.freeSymbol,
|
|
1018
|
+
]) {
|
|
1019
|
+
if (!resolvedExportNames.includes(symbol)) {
|
|
1020
|
+
pushIssue(
|
|
1021
|
+
issues,
|
|
1022
|
+
"error",
|
|
1023
|
+
"missing-plugin-invoke-export",
|
|
1024
|
+
`Plugin artifact is missing required direct invoke export "${symbol}".`,
|
|
1025
|
+
wasmPath ?? sourceName,
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
if (
|
|
1031
|
+
declaredInvokeSurfaces.includes(InvokeSurface.COMMAND) &&
|
|
1032
|
+
!resolvedExportNames.includes(DefaultInvokeExports.commandSymbol)
|
|
1033
|
+
) {
|
|
1034
|
+
pushIssue(
|
|
1035
|
+
issues,
|
|
1036
|
+
"error",
|
|
1037
|
+
"missing-plugin-command-export",
|
|
1038
|
+
`Plugin artifact is missing required command export "${DefaultInvokeExports.commandSymbol}".`,
|
|
1039
|
+
wasmPath ?? sourceName,
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
468
1042
|
} else {
|
|
469
1043
|
pushIssue(
|
|
470
1044
|
issues,
|