space-data-module-sdk 0.1.0 → 0.2.0
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 +174 -83
- package/bin/space-data-module.js +24 -0
- package/package.json +8 -3
- package/schemas/ModuleBundle.fbs +108 -0
- package/schemas/PluginManifest.fbs +26 -1
- package/src/bundle/codec.js +244 -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 +189 -41
- package/src/compliance/pluginCompliance.js +334 -0
- package/src/generated/orbpro/manifest/capability-kind.d.ts +27 -2
- package/src/generated/orbpro/manifest/capability-kind.js +26 -1
- package/src/generated/orbpro/manifest/capability-kind.ts +25 -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/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 +880 -0
- package/src/index.js +9 -2
- package/src/manifest/normalize.js +32 -1
- package/src/runtime/constants.js +18 -1
- package/src/transport/pki.js +0 -5
- package/src/utils/encoding.js +9 -1
- package/src/utils/wasmCrypto.js +49 -1
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import { canonicalBytes } from "../auth/canonicalize.js";
|
|
2
|
+
import { decodePluginManifest, encodePluginManifest } from "../manifest/index.js";
|
|
3
|
+
import { toUint8Array } from "../runtime/bufferLike.js";
|
|
4
|
+
import { sha256Bytes } from "../utils/crypto.js";
|
|
5
|
+
import { bytesToHex } from "../utils/encoding.js";
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_MANIFEST_EXPORT_SYMBOL,
|
|
8
|
+
DEFAULT_MANIFEST_SIZE_SYMBOL,
|
|
9
|
+
SDS_BUNDLE_SECTION_NAME,
|
|
10
|
+
SDS_CUSTOM_SECTION_PREFIX,
|
|
11
|
+
} from "./constants.js";
|
|
12
|
+
import {
|
|
13
|
+
decodeModuleBundle,
|
|
14
|
+
decodeModuleBundleEntryPayload,
|
|
15
|
+
encodeModuleBundle,
|
|
16
|
+
findModuleBundleEntry,
|
|
17
|
+
moduleBundleEncodingToName,
|
|
18
|
+
moduleBundleRoleToName,
|
|
19
|
+
} from "./codec.js";
|
|
20
|
+
|
|
21
|
+
const textDecoder = new TextDecoder();
|
|
22
|
+
const textEncoder = new TextEncoder();
|
|
23
|
+
const WASM_MAGIC = [0x00, 0x61, 0x73, 0x6d];
|
|
24
|
+
const WASM_VERSION_1 = [0x01, 0x00, 0x00, 0x00];
|
|
25
|
+
|
|
26
|
+
function assertSafeNonNegativeInteger(value, label) {
|
|
27
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
28
|
+
throw new TypeError(`${label} must be a non-negative safe integer.`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeBytes(value, label) {
|
|
33
|
+
const bytes = toUint8Array(value);
|
|
34
|
+
if (bytes) {
|
|
35
|
+
return bytes;
|
|
36
|
+
}
|
|
37
|
+
throw new TypeError(`${label} must be a Uint8Array, ArrayBufferView, or ArrayBuffer.`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function concatBytes(chunks) {
|
|
41
|
+
const totalLength = chunks.reduce((total, chunk) => total + chunk.length, 0);
|
|
42
|
+
const out = new Uint8Array(totalLength);
|
|
43
|
+
let offset = 0;
|
|
44
|
+
for (const chunk of chunks) {
|
|
45
|
+
out.set(chunk, offset);
|
|
46
|
+
offset += chunk.length;
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function encodeUnsignedLeb128(value) {
|
|
52
|
+
assertSafeNonNegativeInteger(value, "ULEB128 value");
|
|
53
|
+
let remaining = value;
|
|
54
|
+
const out = [];
|
|
55
|
+
do {
|
|
56
|
+
let byte = remaining & 0x7f;
|
|
57
|
+
remaining = Math.floor(remaining / 128);
|
|
58
|
+
if (remaining > 0) {
|
|
59
|
+
byte |= 0x80;
|
|
60
|
+
}
|
|
61
|
+
out.push(byte);
|
|
62
|
+
} while (remaining > 0);
|
|
63
|
+
return Uint8Array.from(out);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function decodeUnsignedLeb128(bytes, offset = 0) {
|
|
67
|
+
const view = normalizeBytes(bytes, "bytes");
|
|
68
|
+
let result = 0;
|
|
69
|
+
let shift = 0;
|
|
70
|
+
let cursor = offset;
|
|
71
|
+
while (cursor < view.length) {
|
|
72
|
+
const byte = view[cursor++];
|
|
73
|
+
result += (byte & 0x7f) * 2 ** shift;
|
|
74
|
+
if ((byte & 0x80) === 0) {
|
|
75
|
+
return { value: result, nextOffset: cursor };
|
|
76
|
+
}
|
|
77
|
+
shift += 7;
|
|
78
|
+
if (shift > 49) {
|
|
79
|
+
throw new Error("ULEB128 value exceeds supported integer range.");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw new Error("Unexpected end of data while decoding ULEB128.");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function parseWasmModuleSections(bytes) {
|
|
86
|
+
const wasmBytes = normalizeBytes(bytes, "wasm bytes");
|
|
87
|
+
if (wasmBytes.length < 8) {
|
|
88
|
+
throw new Error("WASM module is truncated.");
|
|
89
|
+
}
|
|
90
|
+
for (let index = 0; index < 4; index += 1) {
|
|
91
|
+
if (wasmBytes[index] !== WASM_MAGIC[index]) {
|
|
92
|
+
throw new Error("WASM magic header mismatch.");
|
|
93
|
+
}
|
|
94
|
+
if (wasmBytes[index + 4] !== WASM_VERSION_1[index]) {
|
|
95
|
+
throw new Error("Unsupported WASM version header.");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const sections = [];
|
|
99
|
+
let offset = 8;
|
|
100
|
+
while (offset < wasmBytes.length) {
|
|
101
|
+
const start = offset;
|
|
102
|
+
const id = wasmBytes[offset++];
|
|
103
|
+
const sizeInfo = decodeUnsignedLeb128(wasmBytes, offset);
|
|
104
|
+
const payloadStart = sizeInfo.nextOffset;
|
|
105
|
+
const payloadEnd = payloadStart + sizeInfo.value;
|
|
106
|
+
if (payloadEnd > wasmBytes.length) {
|
|
107
|
+
throw new Error("WASM section extends past end of file.");
|
|
108
|
+
}
|
|
109
|
+
const section = {
|
|
110
|
+
id,
|
|
111
|
+
start,
|
|
112
|
+
end: payloadEnd,
|
|
113
|
+
payloadStart,
|
|
114
|
+
payloadEnd,
|
|
115
|
+
size: sizeInfo.value,
|
|
116
|
+
rawBytes: wasmBytes.subarray(start, payloadEnd),
|
|
117
|
+
};
|
|
118
|
+
if (id === 0) {
|
|
119
|
+
const nameInfo = decodeUnsignedLeb128(wasmBytes, payloadStart);
|
|
120
|
+
const nameStart = nameInfo.nextOffset;
|
|
121
|
+
const nameEnd = nameStart + nameInfo.value;
|
|
122
|
+
if (nameEnd > payloadEnd) {
|
|
123
|
+
throw new Error("WASM custom section name extends past payload.");
|
|
124
|
+
}
|
|
125
|
+
Object.assign(section, {
|
|
126
|
+
name: textDecoder.decode(wasmBytes.subarray(nameStart, nameEnd)),
|
|
127
|
+
nameStart,
|
|
128
|
+
nameEnd,
|
|
129
|
+
dataStart: nameEnd,
|
|
130
|
+
dataEnd: payloadEnd,
|
|
131
|
+
dataBytes: wasmBytes.subarray(nameEnd, payloadEnd),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
sections.push(section);
|
|
135
|
+
offset = payloadEnd;
|
|
136
|
+
}
|
|
137
|
+
if (offset !== wasmBytes.length) {
|
|
138
|
+
throw new Error("WASM parser ended on a non-terminal offset.");
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
bytes: wasmBytes,
|
|
142
|
+
headerBytes: wasmBytes.subarray(0, 8),
|
|
143
|
+
sections,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function listWasmCustomSections(bytes) {
|
|
148
|
+
return parseWasmModuleSections(bytes).sections
|
|
149
|
+
.filter((section) => section.id === 0)
|
|
150
|
+
.map((section) => ({
|
|
151
|
+
name: section.name,
|
|
152
|
+
dataBytes: new Uint8Array(section.dataBytes),
|
|
153
|
+
start: section.start,
|
|
154
|
+
end: section.end,
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function getWasmCustomSections(bytes, name) {
|
|
159
|
+
return listWasmCustomSections(bytes)
|
|
160
|
+
.filter((section) => section.name === name)
|
|
161
|
+
.map((section) => section.dataBytes);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function encodeWasmCustomSection(name, payload) {
|
|
165
|
+
const normalizedName = String(name ?? "").trim();
|
|
166
|
+
if (!normalizedName) {
|
|
167
|
+
throw new Error("Custom section name is required.");
|
|
168
|
+
}
|
|
169
|
+
const payloadBytes = normalizeBytes(payload, "custom section payload");
|
|
170
|
+
const nameBytes = textEncoder.encode(normalizedName);
|
|
171
|
+
const nameLengthBytes = encodeUnsignedLeb128(nameBytes.length);
|
|
172
|
+
const sectionSize =
|
|
173
|
+
nameLengthBytes.length + nameBytes.length + payloadBytes.length;
|
|
174
|
+
const sectionSizeBytes = encodeUnsignedLeb128(sectionSize);
|
|
175
|
+
return concatBytes([
|
|
176
|
+
Uint8Array.of(0),
|
|
177
|
+
sectionSizeBytes,
|
|
178
|
+
nameLengthBytes,
|
|
179
|
+
nameBytes,
|
|
180
|
+
payloadBytes,
|
|
181
|
+
]);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function stripWasmCustomSections(bytes, predicate = () => false) {
|
|
185
|
+
const parsed = parseWasmModuleSections(bytes);
|
|
186
|
+
const chunks = [parsed.headerBytes];
|
|
187
|
+
for (const section of parsed.sections) {
|
|
188
|
+
if (section.id === 0 && predicate(section)) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
chunks.push(parsed.bytes.subarray(section.start, section.end));
|
|
192
|
+
}
|
|
193
|
+
return concatBytes(chunks);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function appendWasmCustomSection(bytes, name, payload) {
|
|
197
|
+
return concatBytes([
|
|
198
|
+
normalizeBytes(bytes, "wasm bytes"),
|
|
199
|
+
encodeWasmCustomSection(name, payload),
|
|
200
|
+
]);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function normalizeJsonPayload(value) {
|
|
204
|
+
if (value === undefined || value === null) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const bytes = toUint8Array(value);
|
|
208
|
+
if (bytes) {
|
|
209
|
+
return new Uint8Array(bytes);
|
|
210
|
+
}
|
|
211
|
+
if (typeof value === "string") {
|
|
212
|
+
return textEncoder.encode(value);
|
|
213
|
+
}
|
|
214
|
+
return new Uint8Array(canonicalBytes(value));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function normalizeManifestEntry(manifest, manifestBytes) {
|
|
218
|
+
if (!manifest && !manifestBytes) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
const payload = manifestBytes ?? encodePluginManifest(manifest);
|
|
222
|
+
return {
|
|
223
|
+
entryId: "manifest",
|
|
224
|
+
role: "manifest",
|
|
225
|
+
sectionName: "sds.manifest",
|
|
226
|
+
payloadEncoding: "flatbuffer",
|
|
227
|
+
typeRef: {
|
|
228
|
+
schemaName: "PluginManifest.fbs",
|
|
229
|
+
fileIdentifier: "PMAN",
|
|
230
|
+
},
|
|
231
|
+
payload,
|
|
232
|
+
description: "Canonical plugin manifest.",
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function normalizeStandardEntries(options = {}) {
|
|
237
|
+
const entries = [];
|
|
238
|
+
if (options.authorization !== undefined) {
|
|
239
|
+
entries.push({
|
|
240
|
+
entryId: "authorization",
|
|
241
|
+
role: "authorization",
|
|
242
|
+
sectionName: "sds.authorization",
|
|
243
|
+
payloadEncoding: "json-utf8",
|
|
244
|
+
mediaType: "application/json",
|
|
245
|
+
payload: normalizeJsonPayload(options.authorization),
|
|
246
|
+
description: "Deployment authorization envelope.",
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
if (options.signature !== undefined) {
|
|
250
|
+
entries.push({
|
|
251
|
+
entryId: "signature",
|
|
252
|
+
role: "signature",
|
|
253
|
+
sectionName: "sds.signature",
|
|
254
|
+
payloadEncoding: "json-utf8",
|
|
255
|
+
mediaType: "application/json",
|
|
256
|
+
payload: normalizeJsonPayload(options.signature),
|
|
257
|
+
description: "Detached signature payload.",
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
if (options.transportEnvelope !== undefined) {
|
|
261
|
+
entries.push({
|
|
262
|
+
entryId: "transport",
|
|
263
|
+
role: "transport",
|
|
264
|
+
sectionName: "sds.transport",
|
|
265
|
+
payloadEncoding: "json-utf8",
|
|
266
|
+
mediaType: "application/json",
|
|
267
|
+
payload: normalizeJsonPayload(options.transportEnvelope),
|
|
268
|
+
description: "Transport envelope metadata.",
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
return entries.filter((entry) => entry.payload !== null);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function normalizeAdditionalEntries(entries = []) {
|
|
275
|
+
if (!Array.isArray(entries)) {
|
|
276
|
+
return [];
|
|
277
|
+
}
|
|
278
|
+
return entries.map((entry, index) => ({
|
|
279
|
+
entryId: entry.entryId ?? `entry-${index + 1}`,
|
|
280
|
+
...entry,
|
|
281
|
+
}));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function withSha256(entry) {
|
|
285
|
+
const payloadBytes = normalizeBytes(entry.payload, `entry "${entry.entryId}" payload`);
|
|
286
|
+
return {
|
|
287
|
+
...entry,
|
|
288
|
+
payload: payloadBytes,
|
|
289
|
+
sha256: await sha256Bytes(payloadBytes),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function buildParsedEntries(bundle) {
|
|
294
|
+
return (Array.isArray(bundle?.entries) ? bundle.entries : []).map((entry) => {
|
|
295
|
+
const payloadBytes = new Uint8Array(entry.payload ?? []);
|
|
296
|
+
const parsedEntry = {
|
|
297
|
+
...entry,
|
|
298
|
+
roleName: moduleBundleRoleToName(entry.role),
|
|
299
|
+
payloadEncodingName: moduleBundleEncodingToName(entry.payloadEncoding),
|
|
300
|
+
payloadBytes,
|
|
301
|
+
sha256Bytes: new Uint8Array(entry.sha256 ?? []),
|
|
302
|
+
};
|
|
303
|
+
try {
|
|
304
|
+
parsedEntry.decodedPayload = decodeModuleBundleEntryPayload(entry);
|
|
305
|
+
} catch {
|
|
306
|
+
parsedEntry.decodedPayload = payloadBytes;
|
|
307
|
+
}
|
|
308
|
+
if (
|
|
309
|
+
parsedEntry.roleName === "manifest" &&
|
|
310
|
+
parsedEntry.payloadEncodingName === "flatbuffer"
|
|
311
|
+
) {
|
|
312
|
+
try {
|
|
313
|
+
parsedEntry.decodedManifest = decodePluginManifest(payloadBytes);
|
|
314
|
+
} catch {
|
|
315
|
+
parsedEntry.decodedManifest = null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return parsedEntry;
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export async function computeCanonicalModuleHash(
|
|
323
|
+
bytes,
|
|
324
|
+
options = {},
|
|
325
|
+
) {
|
|
326
|
+
const prefix = String(
|
|
327
|
+
options.customSectionPrefix ?? SDS_CUSTOM_SECTION_PREFIX,
|
|
328
|
+
);
|
|
329
|
+
const canonicalWasmBytes = stripWasmCustomSections(bytes, (section) =>
|
|
330
|
+
section.name.startsWith(prefix),
|
|
331
|
+
);
|
|
332
|
+
const hashBytes = await sha256Bytes(canonicalWasmBytes);
|
|
333
|
+
return {
|
|
334
|
+
canonicalWasmBytes,
|
|
335
|
+
hashBytes,
|
|
336
|
+
hashHex: bytesToHex(hashBytes),
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export async function createSingleFileBundle(options = {}) {
|
|
341
|
+
const wasmBytes = normalizeBytes(options.wasmBytes, "wasmBytes");
|
|
342
|
+
const manifestBytes =
|
|
343
|
+
options.manifestBytes !== undefined
|
|
344
|
+
? normalizeBytes(options.manifestBytes, "manifestBytes")
|
|
345
|
+
: options.manifest
|
|
346
|
+
? encodePluginManifest(options.manifest)
|
|
347
|
+
: null;
|
|
348
|
+
const manifestEntry = normalizeManifestEntry(options.manifest, manifestBytes);
|
|
349
|
+
const rawEntries = [
|
|
350
|
+
...(manifestEntry ? [manifestEntry] : []),
|
|
351
|
+
...normalizeStandardEntries(options),
|
|
352
|
+
...normalizeAdditionalEntries(options.entries),
|
|
353
|
+
];
|
|
354
|
+
const entries = [];
|
|
355
|
+
for (const entry of rawEntries) {
|
|
356
|
+
entries.push(await withSha256(entry));
|
|
357
|
+
}
|
|
358
|
+
const canonicalization = {
|
|
359
|
+
version: 1,
|
|
360
|
+
strippedCustomSectionPrefix:
|
|
361
|
+
options.customSectionPrefix ?? SDS_CUSTOM_SECTION_PREFIX,
|
|
362
|
+
bundleSectionName: options.bundleSectionName ?? SDS_BUNDLE_SECTION_NAME,
|
|
363
|
+
hashAlgorithm: "sha256",
|
|
364
|
+
};
|
|
365
|
+
const canonical = await computeCanonicalModuleHash(wasmBytes, {
|
|
366
|
+
customSectionPrefix: canonicalization.strippedCustomSectionPrefix,
|
|
367
|
+
});
|
|
368
|
+
const manifestHash = manifestBytes ? await sha256Bytes(manifestBytes) : [];
|
|
369
|
+
const bundle = {
|
|
370
|
+
bundleVersion: Number(options.bundleVersion ?? 1),
|
|
371
|
+
moduleFormat: options.moduleFormat ?? "space-data-module",
|
|
372
|
+
canonicalization,
|
|
373
|
+
canonicalModuleHash: canonical.hashBytes,
|
|
374
|
+
manifestHash,
|
|
375
|
+
manifestExportSymbol:
|
|
376
|
+
options.manifestExportSymbol ?? DEFAULT_MANIFEST_EXPORT_SYMBOL,
|
|
377
|
+
manifestSizeSymbol:
|
|
378
|
+
options.manifestSizeSymbol ?? DEFAULT_MANIFEST_SIZE_SYMBOL,
|
|
379
|
+
entries,
|
|
380
|
+
};
|
|
381
|
+
const bundleBytes = encodeModuleBundle(bundle);
|
|
382
|
+
const baseWasmBytes = stripWasmCustomSections(wasmBytes, (section) =>
|
|
383
|
+
section.name.startsWith(canonicalization.strippedCustomSectionPrefix),
|
|
384
|
+
);
|
|
385
|
+
const outputWasmBytes = appendWasmCustomSection(
|
|
386
|
+
baseWasmBytes,
|
|
387
|
+
canonicalization.bundleSectionName,
|
|
388
|
+
bundleBytes,
|
|
389
|
+
);
|
|
390
|
+
return {
|
|
391
|
+
bundle,
|
|
392
|
+
bundleBytes,
|
|
393
|
+
canonicalWasmBytes: canonical.canonicalWasmBytes,
|
|
394
|
+
canonicalModuleHash: canonical.hashBytes,
|
|
395
|
+
canonicalModuleHashHex: canonical.hashHex,
|
|
396
|
+
manifestHash,
|
|
397
|
+
manifestHashHex: bytesToHex(manifestHash),
|
|
398
|
+
wasmBytes: outputWasmBytes,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export async function parseSingleFileBundle(bytes, options = {}) {
|
|
403
|
+
const wasmBytes = normalizeBytes(bytes, "wasm bytes");
|
|
404
|
+
const customSections = listWasmCustomSections(wasmBytes);
|
|
405
|
+
const bundleSectionName = String(
|
|
406
|
+
options.bundleSectionName ?? SDS_BUNDLE_SECTION_NAME,
|
|
407
|
+
);
|
|
408
|
+
const bundleSections = customSections.filter(
|
|
409
|
+
(section) => section.name === bundleSectionName,
|
|
410
|
+
);
|
|
411
|
+
if (bundleSections.length === 0) {
|
|
412
|
+
throw new Error(`Missing required custom section "${bundleSectionName}".`);
|
|
413
|
+
}
|
|
414
|
+
if (bundleSections.length > 1) {
|
|
415
|
+
throw new Error(`Expected one "${bundleSectionName}" section, found ${bundleSections.length}.`);
|
|
416
|
+
}
|
|
417
|
+
const bundleBytes = bundleSections[0].dataBytes;
|
|
418
|
+
const bundle = decodeModuleBundle(bundleBytes);
|
|
419
|
+
const prefix =
|
|
420
|
+
bundle.canonicalization?.strippedCustomSectionPrefix ??
|
|
421
|
+
SDS_CUSTOM_SECTION_PREFIX;
|
|
422
|
+
const canonical = await computeCanonicalModuleHash(wasmBytes, {
|
|
423
|
+
customSectionPrefix: prefix,
|
|
424
|
+
});
|
|
425
|
+
const parsedEntries = buildParsedEntries(bundle);
|
|
426
|
+
const manifestEntry = findModuleBundleEntry(bundle, "manifest");
|
|
427
|
+
let manifest = null;
|
|
428
|
+
if (manifestEntry) {
|
|
429
|
+
try {
|
|
430
|
+
manifest = decodePluginManifest(new Uint8Array(manifestEntry.payload ?? []));
|
|
431
|
+
} catch {
|
|
432
|
+
manifest = null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
wasmBytes,
|
|
437
|
+
bundleBytes,
|
|
438
|
+
bundle,
|
|
439
|
+
entries: parsedEntries,
|
|
440
|
+
manifest,
|
|
441
|
+
customSections,
|
|
442
|
+
canonicalWasmBytes: canonical.canonicalWasmBytes,
|
|
443
|
+
canonicalModuleHash: canonical.hashBytes,
|
|
444
|
+
canonicalModuleHashHex: canonical.hashHex,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|