Skip to content

fix(partykit): preserve delete tombstones#284

Merged
jamesgpearce merged 2 commits intotinyplex:mainfrom
clentfort:fix/issue-281-partykit-delete
Feb 10, 2026
Merged

fix(partykit): preserve delete tombstones#284
jamesgpearce merged 2 commits intotinyplex:mainfrom
clentfort:fix/issue-281-partykit-delete

Conversation

@clentfort
Copy link
Contributor

fix(partykit): preserve delete tombstones

Encode PartyKit messages with undefined-aware JSON and decode them without using a reviver so tombstone keys are preserved. This keeps delete changes intact across transport instead of dropping row and cell deletions during parse.

Fixes: #281

@clentfort
Copy link
Contributor Author

Small REPL proof for why a reviver cannot preserve delete tombstones:

const UNDEFINED = '\uFFFC';

const jsonParseWithUndefinedOld = (str) =>
  JSON.parse(str, (_key, value) => (value === UNDEFINED ? undefined : value));

const replaceUndefinedString = (value) => {
  if (value === UNDEFINED) {
    return undefined;
  }
  if (Array.isArray(value)) {
    return value.map(replaceUndefinedString);
  }
  if (value && typeof value === 'object') {
    for (const key of Object.keys(value)) {
      value[key] = replaceUndefinedString(value[key]);
    }
  }
  return value;
};

const jsonParseWithUndefinedNew = (str) =>
  replaceUndefinedString(JSON.parse(str));

const serializedChanges =
  '{"feeding-sessions":{"1767445910545":"\\uFFFC"}}';

const parsedOld = jsonParseWithUndefinedOld(serializedChanges);
const parsedNew = jsonParseWithUndefinedNew(serializedChanges);

console.log('parsedOld:', parsedOld);
console.log(
  'old keeps row key:',
  '1767445910545' in parsedOld['feeding-sessions'],
);

console.log('parsedNew:', parsedNew);
console.log(
  'new keeps row key:',
  '1767445910545' in parsedNew['feeding-sessions'],
);
console.log(
  'new row value is undefined:',
  parsedNew['feeding-sessions']['1767445910545'] === undefined,
);

Expected output:

  • old keeps row key: false
  • new keeps row key: true
  • new row value is undefined: true

Add a PartyKit client/server integration test harness that verifies add and update persistence, including custom path and prefix settings. Keep a delete assertion that currently fails to capture issue tinyplex#281 behavior in server storage.
Encode PartyKit messages with undefined-aware JSON and decode them without using a reviver so tombstone keys are preserved. This keeps delete changes intact across transport instead of dropping row and cell deletions during parse.

Fixes: tinyplex#281
@clentfort clentfort force-pushed the fix/issue-281-partykit-delete branch from 9a05423 to 774c607 Compare February 7, 2026 14:33
@jamesgpearce
Copy link
Contributor

Amazing - thank you!

@jamesgpearce jamesgpearce merged commit f2e8106 into tinyplex:main Feb 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can not delete items from a partykit persisted store

2 participants