cultcache-ts: resolveDocumentFormatter moves out of the ./document entry into a module no export names
The packed-consumer smoke now fails if the document entry exports it, at run time and at compile time. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c85edf90bb
commit
12a0fd7744
4 changed files with 40 additions and 20 deletions
|
|
@ -3,9 +3,9 @@ import {
|
|||
documentPayloadBytes,
|
||||
documentPayloadFromBytes,
|
||||
encodeDocumentPayload,
|
||||
resolveDocumentFormatter,
|
||||
resolveDocumentMembers,
|
||||
} from "./document";
|
||||
import { resolveDocumentFormatter } from "./document-formatter";
|
||||
import type { DocumentPayload } from "./document";
|
||||
import type {
|
||||
AnyCultCacheDocumentDefinition,
|
||||
|
|
|
|||
20
packages/cultcache-ts/src/document-formatter.ts
Normal file
20
packages/cultcache-ts/src/document-formatter.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { decode, encode } from "@msgpack/msgpack";
|
||||
|
||||
import { codecFormatter } from "./codec";
|
||||
import type { AnyCultCacheDocumentDefinition, CultCacheDocumentFormatter } from "./types";
|
||||
|
||||
// The one place that decides how a document is encoded: its own formatter, or its codec's, or a plain map. A codec and a
|
||||
// formatter are two answers to one question, so a definition that carries both is refused wherever it enters (the
|
||||
// builder resolves through here as well). Package-internal: no entry of the package's exports map names this module, so
|
||||
// a consumer cannot import the formatter and read a payload around the schema.
|
||||
export function resolveDocumentFormatter(definition: AnyCultCacheDocumentDefinition): CultCacheDocumentFormatter<unknown> {
|
||||
if (definition.codec && definition.formatter) {
|
||||
throw new Error(`CultCache document type "${definition.type}" declares both a codec and a formatter.`);
|
||||
}
|
||||
return definition.formatter ?? (definition.codec ? codecFormatter(definition.codec) : plainDocumentFormatter);
|
||||
}
|
||||
|
||||
const plainDocumentFormatter: CultCacheDocumentFormatter<unknown> = {
|
||||
encode: (value: unknown) => encode(value),
|
||||
decode: (payload: Uint8Array) => decode(payload),
|
||||
};
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
import { decode, encode } from "@msgpack/msgpack";
|
||||
|
||||
import { codecFormatter } from "./codec";
|
||||
import { resolveDocumentFormatter } from "./document-formatter";
|
||||
import type {
|
||||
AnyCultCacheDocumentDefinition,
|
||||
CultCacheDocumentFormatter,
|
||||
CultCacheSchemaCatalogMember,
|
||||
CultCacheDocumentDefinition,
|
||||
CultCacheDocumentRegistry,
|
||||
|
|
@ -76,21 +73,6 @@ export function defineDocumentType<TSchema extends CultCacheSchema>(
|
|||
return Object.freeze(definition.codec ? { ...definition, members: resolveDocumentMembers(definition) } : { ...definition });
|
||||
}
|
||||
|
||||
// The one place that decides how a document is encoded: its own formatter, or its codec's, or a plain map. A codec and a
|
||||
// formatter are two answers to one question, so a definition that carries both is refused wherever it enters (the
|
||||
// builder resolves through here as well). Package-internal: not exported from the package index.
|
||||
export function resolveDocumentFormatter(definition: AnyCultCacheDocumentDefinition): CultCacheDocumentFormatter<unknown> {
|
||||
if (definition.codec && definition.formatter) {
|
||||
throw new Error(`CultCache document type "${definition.type}" declares both a codec and a formatter.`);
|
||||
}
|
||||
return definition.formatter ?? (definition.codec ? codecFormatter(definition.codec) : plainDocumentFormatter);
|
||||
}
|
||||
|
||||
const plainDocumentFormatter: CultCacheDocumentFormatter<unknown> = {
|
||||
encode: (value: unknown) => encode(value),
|
||||
decode: (payload: Uint8Array) => decode(payload),
|
||||
};
|
||||
|
||||
/**
|
||||
* The bytes of one document, as they travel in a store envelope, a CultNet record or a realtime frame.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ assert.equal(typeof cache.defineDocumentType, "function");
|
|||
// The document owner is also an entry of its own, which the browser client imports without the Node-only cache.
|
||||
assert.equal(documentEntry.decodeDocumentPayload, cache.decodeDocumentPayload);
|
||||
assert.equal("resolveDocumentFormatter" in cache, false);
|
||||
assert.equal("resolveDocumentFormatter" in documentEntry, false);
|
||||
assert.equal(typeof net.CultNetPeer, "function");
|
||||
assert.equal(typeof mesh.CultMesh, "function");
|
||||
assert.equal(typeof mesh.CultMeshProviderSession, "function");
|
||||
|
|
@ -184,6 +185,23 @@ void decodeOwner;
|
|||
"browser-types-smoke.mts",
|
||||
], { cwd: consumerRoot, stdio: "inherit" });
|
||||
|
||||
// The formatter that reads a payload around its schema is in no entry: importing it from the document entry does not compile.
|
||||
writeFileSync(join(consumerRoot, "formatter-refused.mts"), `
|
||||
// @ts-expect-error resolveDocumentFormatter is not exported from the document entry
|
||||
import { resolveDocumentFormatter } from "@gamecult/cultcache-ts/document";
|
||||
void resolveDocumentFormatter;
|
||||
`);
|
||||
execFileSync(process.execPath, [
|
||||
tsc,
|
||||
"--noEmit",
|
||||
"--strict",
|
||||
"--target", "ES2022",
|
||||
"--module", "NodeNext",
|
||||
"--moduleResolution", "NodeNext",
|
||||
"--skipLibCheck",
|
||||
"formatter-refused.mts",
|
||||
], { cwd: consumerRoot, stdio: "inherit" });
|
||||
|
||||
console.log("TypeScript package closure smoke: passed");
|
||||
for (const tarball of tarballs) console.log(`Packed: ${tarball}`);
|
||||
} finally {
|
||||
|
|
|
|||
Loading…
Reference in a new issue