dsh-ferry 0.1.2 → 0.1.3

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/lib/state.js CHANGED
@@ -15,7 +15,14 @@ import { createHash } from 'node:crypto';
15
15
  * move when a log is renumbered, so they are compared as "a reference exists"
16
16
  * rather than by value.
17
17
  */
18
- const SEQ_REFERENCE_KEYS = new Set(['headerSeq', 'sourceEventSeq', 'shadowedSeqs', 'shadowedRange']);
18
+ const SEQ_REFERENCE_KEYS = new Set([
19
+ 'headerSeq',
20
+ 'sourceEventSeq',
21
+ 'sourceEventSeqs',
22
+ 'messageSeqs',
23
+ 'shadowedSeqs',
24
+ 'shadowedRange',
25
+ ]);
19
26
 
20
27
  /** Number of trailing events that make up a tail fingerprint. */
21
28
  export const TAIL_EVENTS = 8;
package/lib/transfer.js CHANGED
@@ -771,6 +771,8 @@ export function createTransfer({ ctx, config, rules, log = () => {} }) {
771
771
  const seq = nextSeq + index;
772
772
  return seq === event.seq ? event : renumberEvent(event, seq, delta, shared);
773
773
  });
774
+ // A mismatch here would write a log the receiver can never open again.
775
+ assertTailReferences([...local.events, ...tail], nextSeq);
774
776
  let handle;
775
777
  try {
776
778
  handle = await ctx.sessionPersistence.open(sessionId, 'write', { signal });
@@ -1014,6 +1016,44 @@ async function* once(buffer) {
1014
1016
  if (buffer !== undefined && buffer.length > 0) yield buffer;
1015
1017
  }
1016
1018
 
1019
+ /**
1020
+ * Check that the references inside a renumbered tail still resolve.
1021
+ *
1022
+ * The stored format validates a few of them on open (`headerSeq` must point at
1023
+ * a request header, `messageSeqs` at human messages). A field this plugin does
1024
+ * not know about cannot be checked, so the known ones are verified before the
1025
+ * write rather than after it.
1026
+ * @param {readonly object[]} events - the receiver's log with the tail appended.
1027
+ * @param {number} from - index of the first appended event.
1028
+ * @throws {HttpError} when a reference would not resolve on the receiver.
1029
+ */
1030
+ function assertTailReferences(events, from) {
1031
+ for (let index = from; index < events.length; index += 1) {
1032
+ const data = events[index].data;
1033
+ if (data === null || typeof data !== 'object') continue;
1034
+ const headerSeq = data.headerSeq;
1035
+ if (Number.isSafeInteger(headerSeq) && events[headerSeq]?.type !== 'request/header') {
1036
+ throw new HttpError(
1037
+ 409,
1038
+ 'session-reference-mismatch',
1039
+ `Cannot renumber this session: event ${events[index].seq} references request header ${headerSeq}, which would not exist`,
1040
+ );
1041
+ }
1042
+ const messageSeqs = data.messageSeqs;
1043
+ if (Array.isArray(messageSeqs)) {
1044
+ for (const seq of messageSeqs) {
1045
+ if (events[seq]?.type !== 'user/message') {
1046
+ throw new HttpError(
1047
+ 409,
1048
+ 'session-reference-mismatch',
1049
+ `Cannot renumber this session: event ${events[index].seq} references message ${seq}, which would not be a user message`,
1050
+ );
1051
+ }
1052
+ }
1053
+ }
1054
+ }
1055
+ }
1056
+
1017
1057
  /**
1018
1058
  * Rewrite one event for a log that numbers the same content differently.
1019
1059
  * @param {object} event - source event.
@@ -1046,7 +1086,7 @@ function shiftSeqRefs(value, delta, from) {
1046
1086
  out[key] = item + delta;
1047
1087
  continue;
1048
1088
  }
1049
- if (key === 'shadowedSeqs' && Array.isArray(item)) {
1089
+ if ((key === 'shadowedSeqs' || key === 'messageSeqs' || key === 'sourceEventSeqs') && Array.isArray(item)) {
1050
1090
  out[key] = item.map((entry) => (Number.isSafeInteger(entry) && entry >= from ? entry + delta : entry));
1051
1091
  continue;
1052
1092
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ferry",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "Move Sessions and their Workspace between DSH instances over any reachable network.",
6
6
  "license": "MIT",