cultnet-ts: pin null commit fields (the C# server sends nil); published schema allows null

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Libby 2026-11-11 04:54:04 +00:00
commit 612652a832
3 changed files with 28 additions and 5 deletions

View file

@ -13,8 +13,8 @@
"document": { "$ref": "./cultnet.raw-document-record.schema.json" },
"recordKey": { "type": "string", "minLength": 1 },
"schemaId": { "type": "string", "minLength": 1 },
"commitId": { "type": "string", "minLength": 1 },
"commitCount": { "type": "integer", "minimum": 2 }
"commitId": { "type": ["string", "null"], "minLength": 1 },
"commitCount": { "type": ["integer", "null"], "minimum": 2 }
},
"allOf": [
{

View file

@ -425,9 +425,9 @@ export interface CultNetDatabaseChangeRawMessage {
recordKey?: string;
schemaId?: string;
/** Opaque, one subscription: changes sharing a commitId are the changes of one commit. */
commitId?: string;
/** How many changes the commit carries, at least two. */
commitCount?: number;
commitId?: string | null;
/** How many changes the commit carries, at least two. The C# server sends null on a change outside a grouped commit. */
commitCount?: number | null;
}
export interface CultNetSchemaDescriptor {

View file

@ -1712,6 +1712,29 @@ test("database subscription contracts match the C# live document lane", () => {
schemaId: "sample.counter_state.v1",
...extra,
});
// The C# server writes commitId and commitCount as nil on every change outside a grouped commit, so null is absent.
const ungrouped = changeWith({ commitId: null, commitCount: null })() as CultNetDatabaseChangeRawMessage;
assert.equal(ungrouped.commitId, null);
assert.equal(ungrouped.commitCount, null);
assert.doesNotThrow(changeWith({ commitId: null }));
assert.doesNotThrow(changeWith({ commitCount: null }));
assert.doesNotThrow(() => parseCultNetMessage({
schemaVersion: "cultnet.database_change_raw.v0",
messageId: "change-counter-5",
subscriptionId: "counter:browser-1",
changeKind: "added",
document: {
schemaId: "sample.counter_state.v1",
recordKey: "counter:main",
storedAt: "2026-08-17T00:00:00Z",
payloadEncoding: "messagepack",
payload: new Uint8Array([0x81, 0xa5, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x01]),
},
recordKey: null,
schemaId: null,
commitId: null,
commitCount: null,
}));
for (const commitId of ["", 5]) assert.throws(changeWith({ commitId }), /\/commitId/, `commitId ${JSON.stringify(commitId)}`);
for (const commitCount of [1, 0, 2.5, "two", -3]) assert.throws(changeWith({ commitCount }), /\/commitCount/, `commitCount ${commitCount}`);