space-data-module-sdk 0.5.13 → 0.5.14

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 CHANGED
@@ -14,28 +14,11 @@ This repository is the source of truth for module-level concerns:
14
14
  - the `sds.bundle` single-file container
15
15
  - deployment authorization plus SDS publication records (`REC`, `PNM`, `ENC`)
16
16
  - the first canonical module hostcall/import ABI surface
17
- - shared module-level testing/runtime harnesses, including the WasmEdge
18
- process runner used by package-level validation suites
19
17
 
20
18
  <p align="center">
21
19
  <img src="docs/architecture.svg" alt="Module architecture overview" width="820" />
22
20
  </p>
23
21
 
24
- ## Shared Harness Ownership
25
-
26
- This repo owns the generic module-side runtime harnesses used across the stack:
27
-
28
- - `createModuleHarness(...)` for process and WasmEdge-backed module invocation
29
- - `resolveModuleHarnessLaunchPlan(...)` for portable launch planning
30
- - `buildWasmEdgeEmscriptenPthreadRunner(...)` plus the shared native runner
31
- source and runner-level pthread smoke test
32
-
33
- Flow-specific standalone harnessing lives in `sdn-flow`, which layers flow
34
- enqueue/drain behavior on top of its compiled standalone runtime host. Package-
35
- specific validation suites, including conjunction replay/V&V, are expected to
36
- sit on top of these shared harnesses instead of defining their own runtime
37
- process model.
38
-
39
22
  ## Module Artifact Model
40
23
 
41
24
  A compliant module built with this SDK is always a valid `.wasm` artifact with:
@@ -195,11 +178,6 @@ semantics.
195
178
  This repo now exposes a manifest-driven harness generator from
196
179
  `space-data-module-sdk/testing` and two complementary integration suites:
197
180
 
198
- - shared process-level helpers for command-surface runtimes:
199
- - `createPluginInvokeProcessClient(...)`
200
- - `resolveWasmEdgePluginLaunchPlan(...)`
201
- - `buildWasmEdgeEmscriptenPthreadRunner(...)`
202
-
203
181
  - `npm run test:runtime-matrix`
204
182
  - cross-language runtime smoke across the same WASM in Node.js, Go, Python,
205
183
  Rust, Java, C#, and Swift
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "space-data-module-sdk",
3
- "version": "0.5.13",
3
+ "version": "0.5.14",
4
4
  "description": "Module SDK for building, validating, signing, and deploying WebAssembly modules on the Space Data Network.",
5
5
  "type": "module",
6
6
  "types": "./src/index.d.ts",
@@ -40,9 +40,6 @@
40
40
  "schemas/",
41
41
  "src/"
42
42
  ],
43
- "publishConfig": {
44
- "access": "public"
45
- },
46
43
  "scripts": {
47
44
  "test": "node --test",
48
45
  "test:host-surfaces": "node --test test/node-host.test.js test/host-abi.test.js",
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { readFile, readdir, stat } from "node:fs/promises";
2
+ import { readFile } from "node:fs/promises";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
5
5
  import { FlatcRunner } from "flatc-wasm";
@@ -18,48 +18,6 @@ function loadFlatcRunner() {
18
18
  return flatcRunnerPromise;
19
19
  }
20
20
 
21
- const FLATBUFFERS_INCLUDE_ROOT_CANDIDATES = [
22
- process.env.FLATBUFFERS_INCLUDE_DIR,
23
- "/opt/homebrew/include",
24
- "/usr/local/include",
25
- "/usr/include",
26
- ].filter(Boolean);
27
-
28
- async function findFlatbuffersIncludeRoot() {
29
- for (const candidate of FLATBUFFERS_INCLUDE_ROOT_CANDIDATES) {
30
- const headerPath = path.join(candidate, "flatbuffers", "flatbuffers.h");
31
- try {
32
- const headerStat = await stat(headerPath);
33
- if (headerStat.isFile()) {
34
- return candidate;
35
- }
36
- } catch {
37
- // Try the next candidate.
38
- }
39
- }
40
- throw new Error(
41
- "Unable to locate the installed flatbuffers C++ headers. Set FLATBUFFERS_INCLUDE_DIR to the directory containing flatbuffers/flatbuffers.h.",
42
- );
43
- }
44
-
45
- async function readDirectoryTree(rootDir, currentDir = rootDir) {
46
- const entries = await readdir(currentDir, { withFileTypes: true });
47
- const files = [];
48
- for (const entry of entries) {
49
- const fullPath = path.join(currentDir, entry.name);
50
- if (entry.isDirectory()) {
51
- files.push(...(await readDirectoryTree(rootDir, fullPath)));
52
- continue;
53
- }
54
- if (!entry.isFile()) {
55
- continue;
56
- }
57
- const relativePath = path.relative(rootDir, fullPath).split(path.sep).join("/");
58
- files.push([`/${relativePath}`, await readFile(fullPath, "utf8")]);
59
- }
60
- return files;
61
- }
62
-
63
21
  async function loadInvokeSchemaFiles() {
64
22
  const filenames = [
65
23
  "TypedArenaBuffer.fbs",
@@ -78,10 +36,8 @@ async function loadInvokeSchemaFiles() {
78
36
  export async function getFlatbuffersCppRuntimeHeaders() {
79
37
  if (!flatbuffersCppRuntimeHeadersPromise) {
80
38
  flatbuffersCppRuntimeHeadersPromise = (async () => {
81
- const includeRoot = await findFlatbuffersIncludeRoot();
82
- return Object.fromEntries(
83
- await readDirectoryTree(includeRoot, path.join(includeRoot, "flatbuffers")),
84
- );
39
+ const flatc = await loadFlatcRunner();
40
+ return flatc.getEmbeddedRuntime("cpp");
85
41
  })();
86
42
  }
87
43
  return flatbuffersCppRuntimeHeadersPromise;
@@ -167,6 +167,134 @@ function normalizePayloadWireFormatName(value) {
167
167
  return null;
168
168
  }
169
169
 
170
+ function normalizedPluginFamilyName(value) {
171
+ if (!isNonEmptyString(value)) {
172
+ return null;
173
+ }
174
+ return value
175
+ .trim()
176
+ .toLowerCase()
177
+ .replace(/-/g, "_");
178
+ }
179
+
180
+ function typeSetHasDualWireFormats(typeSet) {
181
+ if (!Array.isArray(typeSet?.allowedTypes) || typeSet.allowedTypes.length === 0) {
182
+ return false;
183
+ }
184
+ let hasFlatbuffer = false;
185
+ let hasAlignedBinary = false;
186
+ for (const allowedType of typeSet.allowedTypes) {
187
+ const wireFormat = normalizePayloadWireFormatName(allowedType?.wireFormat);
188
+ if (wireFormat === "flatbuffer") {
189
+ hasFlatbuffer = true;
190
+ } else if (wireFormat === "aligned-binary") {
191
+ hasAlignedBinary = true;
192
+ }
193
+ }
194
+ return hasFlatbuffer && hasAlignedBinary;
195
+ }
196
+
197
+ function validateCanonicalDualFormatPort(port, issues, location, methodId, portId) {
198
+ if (!port) {
199
+ pushIssue(
200
+ issues,
201
+ "error",
202
+ "propagator-missing-canonical-port",
203
+ `Canonical propagator method "${methodId}" must declare port "${portId}".`,
204
+ location,
205
+ );
206
+ return;
207
+ }
208
+ if (!Array.isArray(port.acceptedTypeSets) || port.acceptedTypeSets.length === 0) {
209
+ pushIssue(
210
+ issues,
211
+ "error",
212
+ "propagator-missing-canonical-port",
213
+ `Canonical propagator method "${methodId}" port "${portId}" must declare acceptedTypeSets.`,
214
+ `${location}.acceptedTypeSets`,
215
+ );
216
+ return;
217
+ }
218
+ port.acceptedTypeSets.forEach((typeSet, index) => {
219
+ if (!typeSetHasDualWireFormats(typeSet)) {
220
+ pushIssue(
221
+ issues,
222
+ "error",
223
+ "canonical-port-missing-dual-wire-format",
224
+ `Canonical propagator method "${methodId}" port "${portId}" acceptedTypeSet "${typeSet?.setId ?? index}" must accept both regular flatbuffer and aligned-binary payloads.`,
225
+ `${location}.acceptedTypeSets[${index}]`,
226
+ );
227
+ }
228
+ });
229
+ }
230
+
231
+ function validateCanonicalPropagatorContract(manifest, issues, sourceName) {
232
+ if (normalizedPluginFamilyName(manifest?.pluginFamily) !== "propagator") {
233
+ return;
234
+ }
235
+
236
+ const methods = Array.isArray(manifest?.methods) ? manifest.methods : [];
237
+ const methodRequirements = [
238
+ {
239
+ methodId: "ingest_omm",
240
+ inputPorts: ["omm"],
241
+ outputPorts: [],
242
+ },
243
+ {
244
+ methodId: "describe_sources_batch",
245
+ inputPorts: ["request"],
246
+ outputPorts: ["result"],
247
+ },
248
+ {
249
+ methodId: "propagate_state",
250
+ inputPorts: ["request"],
251
+ outputPorts: ["state"],
252
+ },
253
+ ];
254
+
255
+ for (const requirement of methodRequirements) {
256
+ const methodIndex = methods.findIndex(
257
+ (method) => method?.methodId === requirement.methodId,
258
+ );
259
+ if (methodIndex === -1) {
260
+ pushIssue(
261
+ issues,
262
+ "error",
263
+ "propagator-missing-canonical-method",
264
+ `Propagator plugins must declare canonical method "${requirement.methodId}".`,
265
+ `${sourceName}.methods`,
266
+ );
267
+ continue;
268
+ }
269
+ const method = methods[methodIndex];
270
+ const location = `${sourceName}.methods[${methodIndex}]`;
271
+ for (const portId of requirement.inputPorts) {
272
+ const port = Array.isArray(method.inputPorts)
273
+ ? method.inputPorts.find((entry) => entry?.portId === portId)
274
+ : null;
275
+ validateCanonicalDualFormatPort(
276
+ port,
277
+ issues,
278
+ `${location}.inputPorts`,
279
+ requirement.methodId,
280
+ portId,
281
+ );
282
+ }
283
+ for (const portId of requirement.outputPorts) {
284
+ const port = Array.isArray(method.outputPorts)
285
+ ? method.outputPorts.find((entry) => entry?.portId === portId)
286
+ : null;
287
+ validateCanonicalDualFormatPort(
288
+ port,
289
+ issues,
290
+ `${location}.outputPorts`,
291
+ requirement.methodId,
292
+ portId,
293
+ );
294
+ }
295
+ }
296
+ }
297
+
170
298
  function validateStringField(issues, value, location, label) {
171
299
  if (!isNonEmptyString(value)) {
172
300
  pushIssue(issues, "error", "missing-string", `${label} must be a non-empty string.`, location);
@@ -1259,6 +1387,8 @@ export function validatePluginManifest(manifest, options = {}) {
1259
1387
  }
1260
1388
  });
1261
1389
 
1390
+ validateCanonicalPropagatorContract(manifest, issues, sourceName);
1391
+
1262
1392
  if (manifest.timers !== undefined && !Array.isArray(manifest.timers)) {
1263
1393
  pushIssue(
1264
1394
  issues,
package/src/index.d.ts CHANGED
@@ -11,7 +11,7 @@ export type ProtocolRoleName = "handle" | "dial" | "both";
11
11
 
12
12
  export interface PayloadTypeRef {
13
13
  schemaName?: string;
14
- fileIdentifier?: string;
14
+ fileIdentifier?: string | null;
15
15
  schemaHash?: string | number[] | Uint8Array;
16
16
  acceptsAnyFlatbuffer?: boolean;
17
17
  wireFormat?: PayloadWireFormat;
@@ -662,25 +662,12 @@ export type {
662
662
  HarnessInvokeScenario,
663
663
  HarnessRawScenario,
664
664
  ManifestHarnessPlan,
665
- ModuleHarness,
666
- ModuleHarnessRuntimeDescriptor,
667
- PluginInvokeProcessClient,
668
- PluginInvokeProcessLaunchPlan,
669
- WasmEdgeRunnerBuildPlan,
670
665
  } from "./testing/index.js";
671
666
 
672
667
  export {
673
- buildWasmEdgeEmscriptenPthreadRunner,
674
- buildWasmEdgeSpawnEnv,
675
- createModuleHarness,
676
- createPluginInvokeProcessClient,
677
668
  describeCapabilityRuntimeSurface,
678
669
  generateManifestHarnessPlan,
679
670
  materializeHarnessScenario,
680
- resolveModuleHarnessLaunchPlan,
681
- resolveWasmEdgeRunnerBuildPlan,
682
- resolveWasmEdgeRunnerSourcePath,
683
- resolveWasmEdgePluginLaunchPlan,
684
671
  serializeHarnessPlan,
685
672
  } from "./testing/index.js";
686
673
 
@@ -12,7 +12,7 @@ import { InvokeSurface } from "../generated/orbpro/manifest/invoke-surface.js";
12
12
  import { BufferMutability } from "../generated/orbpro/stream/buffer-mutability.js";
13
13
  import { BufferOwnership } from "../generated/orbpro/stream/buffer-ownership.js";
14
14
  import { FlatBufferTypeRefT } from "../generated/orbpro/stream/flat-buffer-type-ref.js";
15
- import { TypedArenaBufferT } from "../generated/orbpro/stream/typed-arena-buffer.js";
15
+ import { TypedArenaBuffer, TypedArenaBufferT } from "../generated/orbpro/stream/typed-arena-buffer.js";
16
16
  import { toUint8Array } from "../runtime/bufferLike.js";
17
17
 
18
18
  function toByteBuffer(data) {
@@ -149,22 +149,20 @@ function normalizeArenaFrame(frame = {}, offset) {
149
149
 
150
150
  function packArenaFrames(frames = []) {
151
151
  const packedFrames = [];
152
- const normalizedFrames = [];
152
+ const arena = [];
153
153
  let offset = 0;
154
154
  for (const frame of frames) {
155
155
  const normalized = normalizeArenaFrame(frame, offset);
156
+ for (let index = 0; index < normalized.padding; index += 1) {
157
+ arena.push(0);
158
+ }
159
+ arena.push(...normalized.payload);
156
160
  offset = normalized.buffer.offset + normalized.buffer.size;
157
161
  packedFrames.push(normalized.buffer);
158
- normalizedFrames.push(normalized);
159
- }
160
-
161
- const arena = new Uint8Array(offset);
162
- for (const normalized of normalizedFrames) {
163
- arena.set(normalized.payload, normalized.buffer.offset);
164
162
  }
165
163
  return {
166
164
  frames: packedFrames,
167
- arena,
165
+ arena: Uint8Array.from(arena),
168
166
  };
169
167
  }
170
168
 
@@ -57,69 +57,6 @@ export interface ManifestHarnessPlan {
57
57
  scenarios: Array<HarnessInvokeScenario | HarnessRawScenario>;
58
58
  }
59
59
 
60
- export interface PluginInvokeProcessLaunchPlan {
61
- command: string;
62
- args: string[];
63
- env?: Record<string, string | undefined>;
64
- cwd?: string;
65
- wasmPath?: string;
66
- }
67
-
68
- export interface PluginInvokeProcessClient {
69
- launchPlan: PluginInvokeProcessLaunchPlan;
70
- invokeRaw(requestBytes: Uint8Array | ArrayBuffer | ArrayBufferView): Promise<Uint8Array>;
71
- invoke(request: {
72
- methodId?: string | null;
73
- inputs?: HarnessInputFrame[];
74
- }): Promise<{
75
- statusCode: number;
76
- errorCode?: string | null;
77
- errorMessage?: string | null;
78
- outputs: HarnessInputFrame[];
79
- }>;
80
- destroy(): Promise<void>;
81
- }
82
-
83
- export interface ModuleHarnessRuntimeDescriptor {
84
- kind?: "process" | "wasmedge";
85
- launchPlan?: PluginInvokeProcessLaunchPlan;
86
- command?: string;
87
- args?: string[];
88
- env?: Record<string, string | undefined>;
89
- cwd?: string;
90
- wasmPath?: string;
91
- wasmEdgeBinary?: string;
92
- wasmEdgeRunnerBinary?: string;
93
- enableThreads?: boolean;
94
- }
95
-
96
- export interface ModuleHarness {
97
- runtime: ModuleHarnessRuntimeDescriptor & { kind: "process" | "wasmedge" };
98
- launchPlan: PluginInvokeProcessLaunchPlan;
99
- invokeRaw(requestBytes: Uint8Array | ArrayBuffer | ArrayBufferView): Promise<Uint8Array>;
100
- invoke(request: {
101
- methodId?: string | null;
102
- inputs?: HarnessInputFrame[];
103
- }): Promise<{
104
- statusCode: number;
105
- errorCode?: string | null;
106
- errorMessage?: string | null;
107
- outputs: HarnessInputFrame[];
108
- }>;
109
- destroy(): Promise<void>;
110
- }
111
-
112
- export interface WasmEdgeRunnerBuildPlan {
113
- runnerSourcePath: string;
114
- requestedIncludeDir: string;
115
- wasmedgeIncludeDir: string;
116
- wasmedgeLibDir: string;
117
- wasmedgeSharedLibraryPath: string;
118
- outputPath: string;
119
- compilerCommand: string;
120
- compilerArgs: string[];
121
- }
122
-
123
60
  export function describeCapabilityRuntimeSurface(
124
61
  capability: string,
125
62
  ): CapabilityRuntimeSurface;
@@ -147,49 +84,3 @@ export function materializeHarnessScenario(
147
84
  };
148
85
 
149
86
  export function serializeHarnessPlan(plan: ManifestHarnessPlan): unknown;
150
-
151
- export function buildWasmEdgeSpawnEnv(
152
- baseEnv?: Record<string, string | undefined>,
153
- ): Record<string, string | undefined>;
154
-
155
- export function resolveWasmEdgePluginLaunchPlan(options: {
156
- wasmPath: string;
157
- wasmEdgeBinary?: string;
158
- wasmEdgeRunnerBinary?: string;
159
- enableThreads?: boolean;
160
- invokeArgs?: string[];
161
- env?: Record<string, string | undefined>;
162
- }): PluginInvokeProcessLaunchPlan;
163
-
164
- export function createPluginInvokeProcessClient(options: {
165
- launchPlan?: PluginInvokeProcessLaunchPlan;
166
- command?: string;
167
- args?: string[];
168
- env?: Record<string, string | undefined>;
169
- cwd?: string;
170
- }): Promise<PluginInvokeProcessClient>;
171
-
172
- export function resolveModuleHarnessLaunchPlan(options: {
173
- runtime?: ModuleHarnessRuntimeDescriptor;
174
- } | ModuleHarnessRuntimeDescriptor): PluginInvokeProcessLaunchPlan;
175
-
176
- export function createModuleHarness(options: {
177
- runtime?: ModuleHarnessRuntimeDescriptor;
178
- } | ModuleHarnessRuntimeDescriptor): Promise<ModuleHarness>;
179
-
180
- export function resolveWasmEdgeRunnerSourcePath(): string;
181
-
182
- export function resolveWasmEdgeRunnerBuildPlan(options: {
183
- outputPath: string;
184
- wasmedgeIncludeDir?: string;
185
- wasmedgeLibDir?: string;
186
- output?: string;
187
- }): WasmEdgeRunnerBuildPlan;
188
-
189
- export function buildWasmEdgeEmscriptenPthreadRunner(options: {
190
- outputPath: string;
191
- wasmedgeIncludeDir?: string;
192
- wasmedgeLibDir?: string;
193
- output?: string;
194
- cwd?: string;
195
- }): Promise<string>;
@@ -1,22 +1,6 @@
1
- import { Buffer } from "node:buffer";
2
-
3
1
  import { encodePluginInvokeRequest } from "../invoke/codec.js";
4
2
  import { normalizeInvokeSurfaces } from "../invoke/index.js";
5
3
  import { selectPreferredPayloadTypeRef } from "../manifest/typeRefs.js";
6
- export {
7
- buildWasmEdgeSpawnEnv,
8
- createPluginInvokeProcessClient,
9
- resolveWasmEdgePluginLaunchPlan,
10
- } from "./processInvoke.js";
11
- export {
12
- buildWasmEdgeEmscriptenPthreadRunner,
13
- resolveWasmEdgeRunnerBuildPlan,
14
- resolveWasmEdgeRunnerSourcePath,
15
- } from "./buildWasmEdgeRunner.js";
16
- export {
17
- createModuleHarness,
18
- resolveModuleHarnessLaunchPlan,
19
- } from "./moduleHarness.js";
20
4
 
21
5
  const CapabilitySurfaceMatrix = Object.freeze({
22
6
  logging: Object.freeze({
@@ -277,27 +277,7 @@ function validateEncTable(table, buffer, label) {
277
277
  maxLength: 32,
278
278
  });
279
279
  assertOptionalStringField(buffer, tableMeta, 22, `${label} root type`);
280
- const timestamp = table.TIMESTAMP();
281
- const record = {
282
- version: Number(table.VERSION()),
283
- keyExchange:
284
- KEY_EXCHANGE_NAME_BY_VALUE[table.KEY_EXCHANGE()] ??
285
- String(table.KEY_EXCHANGE()),
286
- symmetric:
287
- SYMMETRIC_ALGO_NAME_BY_VALUE[table.SYMMETRIC()] ??
288
- String(table.SYMMETRIC()),
289
- keyDerivation:
290
- KDF_NAME_BY_VALUE[table.KEY_DERIVATION()] ??
291
- String(table.KEY_DERIVATION()),
292
- ephemeralPublicKey: normalizeByteField(table.ephemeralPublicKeyArray()),
293
- nonceStart: normalizeByteField(table.nonceStartArray()),
294
- recipientKeyId: normalizeByteField(table.recipientKeyIdArray()),
295
- context: normalizeStringField(table.CONTEXT()),
296
- schemaHash: normalizeByteField(table.schemaHashArray()),
297
- rootType: normalizeStringField(table.ROOT_TYPE()),
298
- timestamp:
299
- timestamp === undefined || timestamp === null ? 0 : Number(timestamp),
300
- };
280
+ const record = normalizeEncTable(table.unpack());
301
281
  if (!record.ephemeralPublicKey?.length) {
302
282
  throw new Error(`${label} is missing the ephemeral public key.`);
303
283
  }
@@ -336,17 +316,7 @@ function validatePnmTable(table, buffer, label) {
336
316
  assertOptionalStringField(buffer, tableMeta, 16, `${label} timestamp signature`);
337
317
  assertOptionalStringField(buffer, tableMeta, 18, `${label} signature type`);
338
318
  assertOptionalStringField(buffer, tableMeta, 20, `${label} timestamp signature type`);
339
- const record = {
340
- multiformatAddress: normalizeStringField(table.MULTIFORMAT_ADDRESS()),
341
- publishTimestamp: normalizeStringField(table.PUBLISH_TIMESTAMP()),
342
- cid: normalizeStringField(table.CID()),
343
- fileName: normalizeStringField(table.FILE_NAME()),
344
- fileId: normalizeStringField(table.FILE_ID()),
345
- signature: normalizeStringField(table.SIGNATURE()),
346
- timestampSignature: normalizeStringField(table.TIMESTAMP_SIGNATURE()),
347
- signatureType: normalizeStringField(table.SIGNATURE_TYPE()),
348
- timestampSignatureType: normalizeStringField(table.TIMESTAMP_SIGNATURE_TYPE()),
349
- };
319
+ const record = normalizePnmTable(table.unpack());
350
320
  if (
351
321
  !record.multiformatAddress &&
352
322
  !record.publishTimestamp &&
@@ -456,6 +426,48 @@ function pnmTableFromObject(record = {}) {
456
426
  );
457
427
  }
458
428
 
429
+ function normalizeEncTable(table) {
430
+ if (!table) {
431
+ return null;
432
+ }
433
+ return {
434
+ version: Number(table.VERSION ?? 1),
435
+ keyExchange:
436
+ KEY_EXCHANGE_NAME_BY_VALUE[table.KEY_EXCHANGE] ?? String(table.KEY_EXCHANGE),
437
+ symmetric:
438
+ SYMMETRIC_ALGO_NAME_BY_VALUE[table.SYMMETRIC] ?? String(table.SYMMETRIC),
439
+ keyDerivation:
440
+ KDF_NAME_BY_VALUE[table.KEY_DERIVATION] ?? String(table.KEY_DERIVATION),
441
+ ephemeralPublicKey: normalizeByteField(table.EPHEMERAL_PUBLIC_KEY),
442
+ nonceStart: normalizeByteField(table.NONCE_START),
443
+ recipientKeyId: normalizeByteField(table.RECIPIENT_KEY_ID),
444
+ context: normalizeStringField(table.CONTEXT),
445
+ schemaHash: normalizeByteField(table.SCHEMA_HASH),
446
+ rootType: normalizeStringField(table.ROOT_TYPE),
447
+ timestamp:
448
+ table.TIMESTAMP === undefined || table.TIMESTAMP === null
449
+ ? 0
450
+ : Number(table.TIMESTAMP),
451
+ };
452
+ }
453
+
454
+ function normalizePnmTable(table) {
455
+ if (!table) {
456
+ return null;
457
+ }
458
+ return {
459
+ multiformatAddress: normalizeStringField(table.MULTIFORMAT_ADDRESS),
460
+ publishTimestamp: normalizeStringField(table.PUBLISH_TIMESTAMP),
461
+ cid: normalizeStringField(table.CID),
462
+ fileName: normalizeStringField(table.FILE_NAME),
463
+ fileId: normalizeStringField(table.FILE_ID),
464
+ signature: normalizeStringField(table.SIGNATURE),
465
+ timestampSignature: normalizeStringField(table.TIMESTAMP_SIGNATURE),
466
+ signatureType: normalizeStringField(table.SIGNATURE_TYPE),
467
+ timestampSignatureType: normalizeStringField(table.TIMESTAMP_SIGNATURE_TYPE),
468
+ };
469
+ }
470
+
459
471
  function readFooterLength(bytes) {
460
472
  const view = toUint8Array(bytes);
461
473
  if (view.length < TRAILER_FOOTER_LENGTH) {
@@ -589,6 +601,7 @@ export function decodePublicationRecordCollection(bytes) {
589
601
  if (recordTables.length === 0) {
590
602
  throw new Error("REC trailer does not contain any records.");
591
603
  }
604
+ const collection = collectionTable.unpack();
592
605
  const records = [];
593
606
  let enc = null;
594
607
  let pnm = null;
@@ -609,18 +622,10 @@ export function decodePublicationRecordCollection(bytes) {
609
622
  8,
610
623
  `REC trailer record ${index} standard`,
611
624
  );
612
- const recordType =
613
- typeof recordTable.valueType === "function"
614
- ? recordTable.valueType()
615
- : typeof recordTable.value_type === "function"
616
- ? recordTable.value_type()
617
- : (() => {
618
- throw new TypeError(
619
- "REC Record wrapper is missing both valueType() and value_type() accessors.",
620
- );
621
- })();
625
+ const unpackedRecord = collection.RECORDS[index];
626
+ const recordType = recordTable.value_type();
622
627
  const standard =
623
- normalizeStringField(recordTable.standard()) ??
628
+ normalizeStringField(unpackedRecord?.standard) ??
624
629
  STANDARD_BY_RECORD_TYPE[recordType] ??
625
630
  null;
626
631
  const expectedStandard =
@@ -639,7 +644,10 @@ export function decodePublicationRecordCollection(bytes) {
639
644
  if (!valueMeta) {
640
645
  throw new Error(`REC trailer record ${index} is missing a value.`);
641
646
  }
642
- let value = null;
647
+ let value =
648
+ standard === "ENC" || standard === "PNM"
649
+ ? null
650
+ : unpackedRecord?.value ?? null;
643
651
  if (standard === "ENC") {
644
652
  const encTable = recordTable.value(new ENC());
645
653
  if (!encTable) {
@@ -676,9 +684,7 @@ export function decodePublicationRecordCollection(bytes) {
676
684
  });
677
685
  }
678
686
  return {
679
- version:
680
- normalizeStringField(collectionTable.version()) ??
681
- DEFAULT_RECORD_COLLECTION_VERSION,
687
+ version: normalizeStringField(collection.version) ?? DEFAULT_RECORD_COLLECTION_VERSION,
682
688
  records,
683
689
  enc,
684
690
  pnm,