DocumentPayload is an opaque object type over the same runtime Uint8Array, keyed by a string property, and types CultCacheEnvelope.payload, CultNetRawDocumentRecord.payload and CultMeshRealtimeFrame.payload. decodeDocumentPayload(definition, payload) and encodeDocumentPayload(definition, value) are the only reader and writer; documentPayloadFromBytes and documentPayloadBytes are the two named escapes at the edges where bytes enter and leave. resolveDocumentFormatter leaves the package index. The cache, cultnet replication, the mesh read paths, the provider command (now a CultCache document), and the browser's decodeCultNetPayload (definition required, cultcache-ts/document entry) all go through the owner. Compile-fail fixtures per payload-carrying package pin the type with expect-error lines. check-typescript-tests-wired.mjs fails when a test file is run by no script; cultcache-ts now runs dist-test/test/*.test.js, and the portability workflow runs the check and the cache suite. Builds in every package (tsc, src and tests); the suites run once, at the end of the pass. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
43 lines
2.3 KiB
TypeScript
43 lines
2.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
|
|
import { encode } from "@msgpack/msgpack";
|
|
import { z } from "zod";
|
|
|
|
import { codecs, decodeDocumentPayload, defineDocumentType, documentPayloadBytes, documentPayloadFromBytes, encodeDocumentPayload } from "../src";
|
|
|
|
const payloadOf = (bytes: Uint8Array) => documentPayloadFromBytes(bytes);
|
|
const note = z.object({ id: z.string(), count: z.number() });
|
|
const plain = defineDocumentType({ type: "t.plain", schemaId: "t.plain.v0", schema: note, name: "id" });
|
|
const codec = defineDocumentType({
|
|
type: "t.codec",
|
|
schemaId: "t.codec.v0",
|
|
schema: note,
|
|
name: "id",
|
|
codec: codecs.slots<{ id: string; count: number }>({ 0: ["id", codecs.string], 1: ["count", codecs.int32] }, "T.Note"),
|
|
});
|
|
const formatted = defineDocumentType({
|
|
type: "t.formatted",
|
|
schemaId: "t.formatted.v0",
|
|
schema: note,
|
|
name: "id",
|
|
formatter: { encode: (value) => encode([(value as { id: string }).id]), decode: () => ({ id: "from-formatter", count: 0 }) },
|
|
});
|
|
|
|
test("a payload is read by its definition's formatter, a codec's, or a plain map", () => {
|
|
assert.deepEqual(decodeDocumentPayload(plain, payloadOf(encode({ id: "a", count: 1 }))), { id: "a", count: 1 });
|
|
assert.deepEqual(decodeDocumentPayload(codec, payloadOf(encode(["a", 1]))), { id: "a", count: 1 });
|
|
assert.deepEqual(decodeDocumentPayload(formatted, payloadOf(new Uint8Array([0xc0]))), { id: "from-formatter", count: 0 });
|
|
});
|
|
|
|
test("a definition that declares both a codec and a formatter is refused where its payload is read", () => {
|
|
const both = { ...codec, formatter: formatted.formatter } as typeof codec;
|
|
assert.throws(() => decodeDocumentPayload(both, payloadOf(encode(["a", 1]))), (error: Error) =>
|
|
error.message === 'CultCache document type "t.codec" declares both a codec and a formatter.');
|
|
});
|
|
|
|
test("a payload is written by its definition's codec or formatter, the way it is read", () => {
|
|
assert.deepEqual(documentPayloadBytes(encodeDocumentPayload(codec, { id: "a", count: 1 })), encode(["a", 1]));
|
|
assert.deepEqual(documentPayloadBytes(encodeDocumentPayload(plain, { id: "a", count: 1 })), encode({ id: "a", count: 1 }));
|
|
assert.deepEqual(documentPayloadBytes(encodeDocumentPayload(formatted, { id: "a", count: 1 })), encode(["a"]));
|
|
});
|