@artenx/dshb 0.1.21 → 0.1.23

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.
Files changed (36) hide show
  1. package/README.md +1 -1
  2. package/lib/auth/index.js +22 -11
  3. package/lib/auth/index.js.map +1 -1
  4. package/lib/client.js +1 -1
  5. package/lib/client.js.map +1 -1
  6. package/lib/core/index.js +2 -2
  7. package/lib/core/index.js.map +1 -1
  8. package/lib/core/sync/coordinator.d.ts +2 -2
  9. package/lib/core/sync/coordinator.js +121 -15
  10. package/lib/core/sync/coordinator.js.map +1 -1
  11. package/lib/core/sync/internal-routes.d.ts +1 -1
  12. package/lib/core/sync/internal-routes.js +58 -9
  13. package/lib/core/sync/internal-routes.js.map +1 -1
  14. package/lib/core/sync/offline-archive.d.ts +1 -1
  15. package/lib/core/sync/offline-archive.js +1 -1
  16. package/lib/core/sync/offline-routes.js +1 -1
  17. package/lib/core/sync/protocol.d.ts +2 -2
  18. package/lib/core/sync/protocol.js +4 -1
  19. package/lib/core/sync/protocol.js.map +1 -1
  20. package/lib/core/sync/remote-helper.js +10 -4
  21. package/lib/core/sync/remote-helper.js.map +1 -1
  22. package/lib/core/sync/routes.d.ts +1 -1
  23. package/lib/core/sync/session-routes.d.ts +1 -1
  24. package/lib/core/sync/session-routes.js +1 -1
  25. package/lib/core/sync/session-sync.d.ts +2 -2
  26. package/lib/core/sync/session-sync.js +2 -2
  27. package/lib/core/sync/ssh-transport.d.ts +2 -2
  28. package/lib/core/sync/ssh-transport.js +97 -104
  29. package/lib/core/sync/ssh-transport.js.map +1 -1
  30. package/lib/{protocol-CV7LGE44.d.ts → protocol-KaWZArY8.d.ts} +5 -2
  31. package/lib/{session-sync-EdfwUON5.js → session-sync-C0uoizyB.js} +834 -98
  32. package/lib/session-sync-C0uoizyB.js.map +1 -0
  33. package/lib/{session-sync-g6qKZzjV.d.ts → session-sync-C2j2rk9K.d.ts} +56 -4
  34. package/lib/{ssh-transport-BcIiLxsX.d.ts → ssh-transport-DGkQh-HA.d.ts} +14 -5
  35. package/package.json +1 -1
  36. package/lib/session-sync-EdfwUON5.js.map +0 -1
@@ -1,6 +1,9 @@
1
1
  import { createHash, timingSafeEqual } from "node:crypto";
2
2
  //#region src/core/sync/protocol.ts
3
3
  const SYNC_PROTOCOL_VERSION = 1;
4
+ const SYNC_SESSION_MANIFEST_CAPABILITY = "session-manifest-v1";
5
+ const SYNC_SESSION_EVENT_TAIL_CAPABILITY = "session-event-tail-delta-v1";
6
+ const SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY = "session-bundle-objects-v1";
4
7
  const MAX_SYNC_CONTROL_FRAME_BYTES = 65536;
5
8
  const MAX_SYNC_DATA_CHUNK_BYTES = 1048576;
6
9
  const MAX_SYNC_TRANSFER_BYTES = 107374182400;
@@ -119,6 +122,6 @@ var SyncFrameDecoder = class {
119
122
  }
120
123
  };
121
124
  //#endregion
122
- export { MAX_SYNC_CONTROL_FRAME_BYTES, MAX_SYNC_DATA_CHUNK_BYTES, MAX_SYNC_TRANSFER_BYTES, SYNC_PROTOCOL_VERSION, SyncFrameDecoder, SyncProtocolError, encodeSyncControlFrame, encodeSyncDataFrame };
125
+ export { MAX_SYNC_CONTROL_FRAME_BYTES, MAX_SYNC_DATA_CHUNK_BYTES, MAX_SYNC_TRANSFER_BYTES, SYNC_PROTOCOL_VERSION, SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY, SYNC_SESSION_EVENT_TAIL_CAPABILITY, SYNC_SESSION_MANIFEST_CAPABILITY, SyncFrameDecoder, SyncProtocolError, encodeSyncControlFrame, encodeSyncDataFrame };
123
126
 
124
127
  //# sourceMappingURL=protocol.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","names":[],"sources":["../../../src/core/sync/protocol.ts"],"sourcesContent":["import { createHash, timingSafeEqual } from 'node:crypto'\n\nexport const SYNC_PROTOCOL_VERSION = 1\nexport const MAX_SYNC_CONTROL_FRAME_BYTES = 64 * 1024\nexport const MAX_SYNC_DATA_CHUNK_BYTES = 1024 * 1024\nexport const MAX_SYNC_TRANSFER_BYTES = 100 * 1024 * 1024 * 1024\n\nconst FRAME_HEADER_BYTES = 5\nconst CONTROL_FRAME = 0\nconst DATA_FRAME = 1\nconst DATA_METADATA_BYTES = 36\n\nexport type SyncFrame =\n | { kind: 'control'; value: Record<string, unknown> }\n | { kind: 'data'; sequence: number; digest: string; data: Buffer }\n\nexport class SyncProtocolError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'SyncProtocolError'\n }\n}\n\nfunction makeHeader(type: number, payloadLength: number): Buffer {\n const header = Buffer.allocUnsafe(FRAME_HEADER_BYTES)\n header[0] = type\n header.writeUInt32BE(payloadLength, 1)\n return header\n}\n\nexport function encodeSyncControlFrame(value: Record<string, unknown>): Buffer {\n const payload = Buffer.from(`${JSON.stringify(value)}\\n`, 'utf8')\n if (payload.length > MAX_SYNC_CONTROL_FRAME_BYTES) throw new SyncProtocolError('control frame exceeds size limit')\n return Buffer.concat([makeHeader(CONTROL_FRAME, payload.length), payload])\n}\n\nexport function encodeSyncDataFrame(sequence: number, data: Buffer): Buffer {\n if (!Number.isSafeInteger(sequence) || sequence < 0 || sequence > 0xffffffff) {\n throw new SyncProtocolError('data sequence is invalid')\n }\n if (data.length > MAX_SYNC_DATA_CHUNK_BYTES) throw new SyncProtocolError('data chunk exceeds size limit')\n const metadata = Buffer.allocUnsafe(DATA_METADATA_BYTES)\n metadata.writeUInt32BE(sequence, 0)\n createHash('sha256').update(data).digest().copy(metadata, 4)\n return Buffer.concat([makeHeader(DATA_FRAME, metadata.length + data.length), metadata, data])\n}\n\nexport class SyncFrameDecoder {\n private readonly header = Buffer.allocUnsafe(FRAME_HEADER_BYTES)\n private headerOffset = 0\n private frameType = -1\n private payload: Buffer | undefined\n private payloadOffset = 0\n private nextDataSequence = 0\n private ended = false\n\n push(chunk: Buffer): SyncFrame[] {\n if (this.ended) throw new SyncProtocolError('frame decoder is already finished')\n const frames: SyncFrame[] = []\n let inputOffset = 0\n\n while (inputOffset < chunk.length) {\n if (!this.payload) {\n const copied = Math.min(FRAME_HEADER_BYTES - this.headerOffset, chunk.length - inputOffset)\n chunk.copy(this.header, this.headerOffset, inputOffset, inputOffset + copied)\n this.headerOffset += copied\n inputOffset += copied\n if (this.headerOffset < FRAME_HEADER_BYTES) continue\n\n this.frameType = this.header[0]\n const payloadLength = this.header.readUInt32BE(1)\n if (this.frameType === CONTROL_FRAME) {\n if (payloadLength < 2 || payloadLength > MAX_SYNC_CONTROL_FRAME_BYTES) {\n throw new SyncProtocolError('control frame length is invalid')\n }\n } else if (this.frameType === DATA_FRAME) {\n if (payloadLength < DATA_METADATA_BYTES || payloadLength > DATA_METADATA_BYTES + MAX_SYNC_DATA_CHUNK_BYTES) {\n throw new SyncProtocolError('data frame length is invalid')\n }\n } else {\n throw new SyncProtocolError('unknown frame type')\n }\n this.payload = Buffer.allocUnsafe(payloadLength)\n this.payloadOffset = 0\n }\n\n const payload = this.payload\n const copied = Math.min(payload.length - this.payloadOffset, chunk.length - inputOffset)\n chunk.copy(payload, this.payloadOffset, inputOffset, inputOffset + copied)\n this.payloadOffset += copied\n inputOffset += copied\n\n if (this.payloadOffset === payload.length) {\n frames.push(this.decodeFrame(this.frameType, payload))\n this.resetFrame()\n }\n }\n return frames\n }\n\n finish(): void {\n if (this.headerOffset !== 0 || this.payload !== undefined) throw new SyncProtocolError('truncated frame stream')\n this.ended = true\n }\n\n private decodeFrame(type: number, payload: Buffer): SyncFrame {\n if (type === CONTROL_FRAME) {\n if (payload[payload.length - 1] !== 0x0a) throw new SyncProtocolError('control frame must end with a newline')\n let value: unknown\n try {\n value = JSON.parse(payload.subarray(0, -1).toString('utf8'))\n } catch {\n throw new SyncProtocolError('control frame contains invalid JSON')\n }\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new SyncProtocolError('control frame must contain an object')\n }\n return { kind: 'control', value: value as Record<string, unknown> }\n }\n\n const sequence = payload.readUInt32BE(0)\n if (sequence !== this.nextDataSequence) throw new SyncProtocolError('data sequence is out of order')\n const expectedDigest = payload.subarray(4, DATA_METADATA_BYTES)\n const data = payload.subarray(DATA_METADATA_BYTES)\n const actualDigest = createHash('sha256').update(data).digest()\n if (!timingSafeEqual(expectedDigest, actualDigest)) throw new SyncProtocolError('data chunk digest mismatch')\n this.nextDataSequence += 1\n return { kind: 'data', sequence, digest: actualDigest.toString('hex'), data: Buffer.from(data) }\n }\n\n private resetFrame(): void {\n this.headerOffset = 0\n this.frameType = -1\n this.payload = undefined\n this.payloadOffset = 0\n }\n}\n"],"mappings":";;AAEA,MAAa,wBAAwB;AACrC,MAAa,+BAA+B;AAC5C,MAAa,4BAA4B;AACzC,MAAa,0BAA0B;AAEvC,MAAM,qBAAqB;AAC3B,MAAM,gBAAgB;AACtB,MAAM,aAAa;AACnB,MAAM,sBAAsB;AAM5B,IAAa,oBAAb,cAAuC,MAAM;CAC3C,YAAY,SAAiB;EAC3B,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;AAEA,SAAS,WAAW,MAAc,eAA+B;CAC/D,MAAM,SAAS,OAAO,YAAY,kBAAkB;CACpD,OAAO,KAAK;CACZ,OAAO,cAAc,eAAe,CAAC;CACrC,OAAO;AACT;AAEA,SAAgB,uBAAuB,OAAwC;CAC7E,MAAM,UAAU,OAAO,KAAK,GAAG,KAAK,UAAU,KAAK,EAAE,KAAK,MAAM;CAChE,IAAI,QAAQ,SAAA,OAAuC,MAAM,IAAI,kBAAkB,kCAAkC;CACjH,OAAO,OAAO,OAAO,CAAC,WAAW,eAAe,QAAQ,MAAM,GAAG,OAAO,CAAC;AAC3E;AAEA,SAAgB,oBAAoB,UAAkB,MAAsB;CAC1E,IAAI,CAAC,OAAO,cAAc,QAAQ,KAAK,WAAW,KAAK,WAAW,YAChE,MAAM,IAAI,kBAAkB,0BAA0B;CAExD,IAAI,KAAK,SAAA,SAAoC,MAAM,IAAI,kBAAkB,+BAA+B;CACxG,MAAM,WAAW,OAAO,YAAY,mBAAmB;CACvD,SAAS,cAAc,UAAU,CAAC;CAClC,WAAW,QAAQ,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,UAAU,CAAC;CAC3D,OAAO,OAAO,OAAO;EAAC,WAAW,YAAY,SAAS,SAAS,KAAK,MAAM;EAAG;EAAU;CAAI,CAAC;AAC9F;AAEA,IAAa,mBAAb,MAA8B;CAC5B,SAA0B,OAAO,YAAY,kBAAkB;CAC/D,eAAuB;CACvB,YAAoB;CACpB;CACA,gBAAwB;CACxB,mBAA2B;CAC3B,QAAgB;CAEhB,KAAK,OAA4B;EAC/B,IAAI,KAAK,OAAO,MAAM,IAAI,kBAAkB,mCAAmC;EAC/E,MAAM,SAAsB,CAAC;EAC7B,IAAI,cAAc;EAElB,OAAO,cAAc,MAAM,QAAQ;GACjC,IAAI,CAAC,KAAK,SAAS;IACjB,MAAM,SAAS,KAAK,IAAI,qBAAqB,KAAK,cAAc,MAAM,SAAS,WAAW;IAC1F,MAAM,KAAK,KAAK,QAAQ,KAAK,cAAc,aAAa,cAAc,MAAM;IAC5E,KAAK,gBAAgB;IACrB,eAAe;IACf,IAAI,KAAK,eAAe,oBAAoB;IAE5C,KAAK,YAAY,KAAK,OAAO;IAC7B,MAAM,gBAAgB,KAAK,OAAO,aAAa,CAAC;IAChD,IAAI,KAAK,cAAc,eACjB;SAAA,gBAAgB,KAAK,gBAAA,OACvB,MAAM,IAAI,kBAAkB,iCAAiC;IAAA,OAE1D,IAAI,KAAK,cAAc,YACxB;SAAA,gBAAgB,uBAAuB,gBAAgB,SACzD,MAAM,IAAI,kBAAkB,8BAA8B;IAAA,OAG5D,MAAM,IAAI,kBAAkB,oBAAoB;IAElD,KAAK,UAAU,OAAO,YAAY,aAAa;IAC/C,KAAK,gBAAgB;GACvB;GAEA,MAAM,UAAU,KAAK;GACrB,MAAM,SAAS,KAAK,IAAI,QAAQ,SAAS,KAAK,eAAe,MAAM,SAAS,WAAW;GACvF,MAAM,KAAK,SAAS,KAAK,eAAe,aAAa,cAAc,MAAM;GACzE,KAAK,iBAAiB;GACtB,eAAe;GAEf,IAAI,KAAK,kBAAkB,QAAQ,QAAQ;IACzC,OAAO,KAAK,KAAK,YAAY,KAAK,WAAW,OAAO,CAAC;IACrD,KAAK,WAAW;GAClB;EACF;EACA,OAAO;CACT;CAEA,SAAe;EACb,IAAI,KAAK,iBAAiB,KAAK,KAAK,YAAY,KAAA,GAAW,MAAM,IAAI,kBAAkB,wBAAwB;EAC/G,KAAK,QAAQ;CACf;CAEA,YAAoB,MAAc,SAA4B;EAC5D,IAAI,SAAS,eAAe;GAC1B,IAAI,QAAQ,QAAQ,SAAS,OAAO,IAAM,MAAM,IAAI,kBAAkB,uCAAuC;GAC7G,IAAI;GACJ,IAAI;IACF,QAAQ,KAAK,MAAM,QAAQ,SAAS,GAAG,EAAE,CAAC,CAAC,SAAS,MAAM,CAAC;GAC7D,QAAQ;IACN,MAAM,IAAI,kBAAkB,qCAAqC;GACnE;GACA,IAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GACpE,MAAM,IAAI,kBAAkB,sCAAsC;GAEpE,OAAO;IAAE,MAAM;IAAkB;GAAiC;EACpE;EAEA,MAAM,WAAW,QAAQ,aAAa,CAAC;EACvC,IAAI,aAAa,KAAK,kBAAkB,MAAM,IAAI,kBAAkB,+BAA+B;EACnG,MAAM,iBAAiB,QAAQ,SAAS,GAAG,mBAAmB;EAC9D,MAAM,OAAO,QAAQ,SAAS,mBAAmB;EACjD,MAAM,eAAe,WAAW,QAAQ,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO;EAC9D,IAAI,CAAC,gBAAgB,gBAAgB,YAAY,GAAG,MAAM,IAAI,kBAAkB,4BAA4B;EAC5G,KAAK,oBAAoB;EACzB,OAAO;GAAE,MAAM;GAAQ;GAAU,QAAQ,aAAa,SAAS,KAAK;GAAG,MAAM,OAAO,KAAK,IAAI;EAAE;CACjG;CAEA,aAA2B;EACzB,KAAK,eAAe;EACpB,KAAK,YAAY;EACjB,KAAK,UAAU,KAAA;EACf,KAAK,gBAAgB;CACvB;AACF"}
1
+ {"version":3,"file":"protocol.js","names":[],"sources":["../../../src/core/sync/protocol.ts"],"sourcesContent":["import { createHash, timingSafeEqual } from 'node:crypto'\n\nexport const SYNC_PROTOCOL_VERSION = 1\nexport const SYNC_SESSION_MANIFEST_CAPABILITY = 'session-manifest-v1'\nexport const SYNC_SESSION_EVENT_TAIL_CAPABILITY = 'session-event-tail-delta-v1'\nexport const SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY = 'session-bundle-objects-v1'\nexport const MAX_SYNC_CONTROL_FRAME_BYTES = 64 * 1024\nexport const MAX_SYNC_DATA_CHUNK_BYTES = 1024 * 1024\nexport const MAX_SYNC_TRANSFER_BYTES = 100 * 1024 * 1024 * 1024\n\nconst FRAME_HEADER_BYTES = 5\nconst CONTROL_FRAME = 0\nconst DATA_FRAME = 1\nconst DATA_METADATA_BYTES = 36\n\nexport type SyncFrame =\n | { kind: 'control'; value: Record<string, unknown> }\n | { kind: 'data'; sequence: number; digest: string; data: Buffer }\n\nexport class SyncProtocolError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'SyncProtocolError'\n }\n}\n\nfunction makeHeader(type: number, payloadLength: number): Buffer {\n const header = Buffer.allocUnsafe(FRAME_HEADER_BYTES)\n header[0] = type\n header.writeUInt32BE(payloadLength, 1)\n return header\n}\n\nexport function encodeSyncControlFrame(value: Record<string, unknown>): Buffer {\n const payload = Buffer.from(`${JSON.stringify(value)}\\n`, 'utf8')\n if (payload.length > MAX_SYNC_CONTROL_FRAME_BYTES) throw new SyncProtocolError('control frame exceeds size limit')\n return Buffer.concat([makeHeader(CONTROL_FRAME, payload.length), payload])\n}\n\nexport function encodeSyncDataFrame(sequence: number, data: Buffer): Buffer {\n if (!Number.isSafeInteger(sequence) || sequence < 0 || sequence > 0xffffffff) {\n throw new SyncProtocolError('data sequence is invalid')\n }\n if (data.length > MAX_SYNC_DATA_CHUNK_BYTES) throw new SyncProtocolError('data chunk exceeds size limit')\n const metadata = Buffer.allocUnsafe(DATA_METADATA_BYTES)\n metadata.writeUInt32BE(sequence, 0)\n createHash('sha256').update(data).digest().copy(metadata, 4)\n return Buffer.concat([makeHeader(DATA_FRAME, metadata.length + data.length), metadata, data])\n}\n\nexport class SyncFrameDecoder {\n private readonly header = Buffer.allocUnsafe(FRAME_HEADER_BYTES)\n private headerOffset = 0\n private frameType = -1\n private payload: Buffer | undefined\n private payloadOffset = 0\n private nextDataSequence = 0\n private ended = false\n\n push(chunk: Buffer): SyncFrame[] {\n if (this.ended) throw new SyncProtocolError('frame decoder is already finished')\n const frames: SyncFrame[] = []\n let inputOffset = 0\n\n while (inputOffset < chunk.length) {\n if (!this.payload) {\n const copied = Math.min(FRAME_HEADER_BYTES - this.headerOffset, chunk.length - inputOffset)\n chunk.copy(this.header, this.headerOffset, inputOffset, inputOffset + copied)\n this.headerOffset += copied\n inputOffset += copied\n if (this.headerOffset < FRAME_HEADER_BYTES) continue\n\n this.frameType = this.header[0]\n const payloadLength = this.header.readUInt32BE(1)\n if (this.frameType === CONTROL_FRAME) {\n if (payloadLength < 2 || payloadLength > MAX_SYNC_CONTROL_FRAME_BYTES) {\n throw new SyncProtocolError('control frame length is invalid')\n }\n } else if (this.frameType === DATA_FRAME) {\n if (payloadLength < DATA_METADATA_BYTES || payloadLength > DATA_METADATA_BYTES + MAX_SYNC_DATA_CHUNK_BYTES) {\n throw new SyncProtocolError('data frame length is invalid')\n }\n } else {\n throw new SyncProtocolError('unknown frame type')\n }\n this.payload = Buffer.allocUnsafe(payloadLength)\n this.payloadOffset = 0\n }\n\n const payload = this.payload\n const copied = Math.min(payload.length - this.payloadOffset, chunk.length - inputOffset)\n chunk.copy(payload, this.payloadOffset, inputOffset, inputOffset + copied)\n this.payloadOffset += copied\n inputOffset += copied\n\n if (this.payloadOffset === payload.length) {\n frames.push(this.decodeFrame(this.frameType, payload))\n this.resetFrame()\n }\n }\n return frames\n }\n\n finish(): void {\n if (this.headerOffset !== 0 || this.payload !== undefined) throw new SyncProtocolError('truncated frame stream')\n this.ended = true\n }\n\n private decodeFrame(type: number, payload: Buffer): SyncFrame {\n if (type === CONTROL_FRAME) {\n if (payload[payload.length - 1] !== 0x0a) throw new SyncProtocolError('control frame must end with a newline')\n let value: unknown\n try {\n value = JSON.parse(payload.subarray(0, -1).toString('utf8'))\n } catch {\n throw new SyncProtocolError('control frame contains invalid JSON')\n }\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new SyncProtocolError('control frame must contain an object')\n }\n return { kind: 'control', value: value as Record<string, unknown> }\n }\n\n const sequence = payload.readUInt32BE(0)\n if (sequence !== this.nextDataSequence) throw new SyncProtocolError('data sequence is out of order')\n const expectedDigest = payload.subarray(4, DATA_METADATA_BYTES)\n const data = payload.subarray(DATA_METADATA_BYTES)\n const actualDigest = createHash('sha256').update(data).digest()\n if (!timingSafeEqual(expectedDigest, actualDigest)) throw new SyncProtocolError('data chunk digest mismatch')\n this.nextDataSequence += 1\n return { kind: 'data', sequence, digest: actualDigest.toString('hex'), data: Buffer.from(data) }\n }\n\n private resetFrame(): void {\n this.headerOffset = 0\n this.frameType = -1\n this.payload = undefined\n this.payloadOffset = 0\n }\n}\n"],"mappings":"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"}
@@ -10,6 +10,8 @@ const PATHS = {
10
10
  'node-manifest': { path: '/api/dshb/sync/internal/nodes/manifest', method: 'GET' },
11
11
  'node-apply': { path: '/api/dshb/sync/internal/nodes/apply', method: 'POST' },
12
12
  'session-bundle': { path: '/api/dshb/sync/internal/sessions/bundle', method: 'GET' },
13
+ 'session-manifest': { path: '/api/dshb/sync/internal/sessions/manifest', method: 'GET' },
14
+ 'session-entries': { path: '/api/dshb/sync/internal/sessions/entries', method: 'POST' },
13
15
  'session-apply': { path: '/api/dshb/sync/internal/sessions/apply', method: 'POST' },
14
16
  }
15
17
  const input = []
@@ -84,7 +86,7 @@ function parseRequest(buffer) {
84
86
  }
85
87
  if (!Number.isInteger(request.port) || request.port < 1 || request.port > 65535) throw new Error('invalid loopback port')
86
88
  const operation = PATHS[request.operation]
87
- if (request.operation === 'node-apply' || request.operation === 'session-apply') {
89
+ if (request.operation === 'node-apply' || request.operation === 'session-entries' || request.operation === 'session-apply') {
88
90
  const last = frames[frames.length - 1]
89
91
  if (!last || last.kind !== 'control' || last.value.kind !== 'request-end') throw new Error('apply request terminator required')
90
92
  if (frames.slice(1, -1).some((frame) => frame.kind !== 'data')) throw new Error('invalid apply request body')
@@ -113,7 +115,8 @@ async function run() {
113
115
  })
114
116
  }
115
117
 
116
- if (request.operation === 'node-manifest' || request.operation === 'session-bundle') {
118
+ if (request.operation === 'node-manifest' || request.operation === 'session-bundle' ||
119
+ request.operation === 'session-manifest' || request.operation === 'session-entries') {
117
120
  await writeStdout(encodeControl({ kind: 'response', status: response.status, bodyMode: 'data' }))
118
121
  if (!response.body) throw new Error('manifest response body missing')
119
122
  var sequence = 0
@@ -133,8 +136,11 @@ async function run() {
133
136
 
134
137
  var text = await response.text()
135
138
  if (Buffer.byteLength(text, 'utf8') > MAX_CONTROL) throw new Error('response control frame too large')
136
- await writeStdout(encodeControl({ kind: 'response', status: response.status, body: JSON.parse(text) }))
137
- if (!response.ok) process.exitCode = 1
139
+ var responseBody = JSON.parse(text)
140
+ await writeStdout(encodeControl({ kind: 'response', status: response.status, body: responseBody }))
141
+ var recoverableDeltaMismatch = request.operation === 'session-apply' && response.status === 409 &&
142
+ responseBody && responseBody.code === 'session-delta-prefix'
143
+ if (!response.ok && !recoverableDeltaMismatch) process.exitCode = 1
138
144
  }
139
145
 
140
146
  process.stdin.on('data', (chunk) => {
@@ -1 +1 @@
1
- {"version":3,"file":"remote-helper.js","names":[],"sources":["../../../src/core/sync/remote-helper.ts"],"sourcesContent":["import { Buffer } from 'node:buffer'\n\nconst REMOTE_HELPER_SOURCE = String.raw`'use strict'\nconst { createHash, timingSafeEqual } = require('node:crypto')\nconst MAX_CONTROL = 65536\nconst MAX_CHUNK = 1048576\nconst MAX_TRANSFER = 107374182400\nconst PATHS = {\n handshake: { path: '/api/dshb/sync/internal/handshake', method: 'GET' },\n 'node-manifest': { path: '/api/dshb/sync/internal/nodes/manifest', method: 'GET' },\n 'node-apply': { path: '/api/dshb/sync/internal/nodes/apply', method: 'POST' },\n 'session-bundle': { path: '/api/dshb/sync/internal/sessions/bundle', method: 'GET' },\n 'session-apply': { path: '/api/dshb/sync/internal/sessions/apply', method: 'POST' },\n}\nconst input = []\nlet inputBytes = 0\n\nfunction encodeControl(value) {\n const body = Buffer.from(JSON.stringify(value) + '\\n', 'utf8')\n if (body.length > MAX_CONTROL) throw new Error('control frame too large')\n const header = Buffer.alloc(5)\n header[0] = 0\n header.writeUInt32BE(body.length, 1)\n return Buffer.concat([header, body])\n}\n\nfunction encodeData(sequence, data) {\n const digest = createHash('sha256').update(data).digest()\n const metadata = Buffer.alloc(36)\n metadata.writeUInt32BE(sequence, 0)\n digest.copy(metadata, 4)\n const payload = Buffer.concat([metadata, data])\n const header = Buffer.alloc(5)\n header[0] = 1\n header.writeUInt32BE(payload.length, 1)\n return Buffer.concat([header, payload])\n}\n\nfunction decodeFrames(buffer) {\n const frames = []\n let offset = 0\n let sequence = 0\n let totalData = 0\n while (offset < buffer.length) {\n if (buffer.length - offset < 5) throw new Error('truncated frame header')\n const kind = buffer[offset]\n const length = buffer.readUInt32BE(offset + 1)\n offset += 5\n const maxLength = kind === 0 ? MAX_CONTROL : 36 + MAX_CHUNK\n if ((kind !== 0 && kind !== 1) || length > maxLength || length > buffer.length - offset) {\n throw new Error('invalid frame length')\n }\n const payload = buffer.subarray(offset, offset + length)\n offset += length\n if (kind === 0) {\n if (length < 2 || payload[length - 1] !== 10) throw new Error('invalid control frame')\n frames.push({ kind: 'control', value: JSON.parse(payload.subarray(0, -1).toString('utf8')) })\n } else {\n if (length < 36) throw new Error('invalid data frame')\n const currentSequence = payload.readUInt32BE(0)\n const expected = payload.subarray(4, 36)\n const data = payload.subarray(36)\n const actual = createHash('sha256').update(data).digest()\n if (currentSequence !== sequence || !timingSafeEqual(expected, actual)) throw new Error('invalid data chunk')\n sequence += 1\n totalData += data.length\n if (totalData > MAX_TRANSFER) throw new Error('transfer exceeds size limit')\n frames.push({ kind: 'data', data })\n }\n }\n return frames\n}\n\nfunction parseRequest(buffer) {\n const frames = decodeFrames(buffer)\n const first = frames[0]\n if (!first || first.kind !== 'control') throw new Error('request control frame required')\n const request = first.value\n if (!request || request.kind !== 'request' || request.protocolVersion !== 1 || !Object.prototype.hasOwnProperty.call(PATHS, request.operation)) {\n throw new Error('unsupported helper request')\n }\n if (Object.keys(request).some((key) => !['kind', 'protocolVersion', 'operation', 'port'].includes(key))) {\n throw new Error('unsupported helper request field')\n }\n if (!Number.isInteger(request.port) || request.port < 1 || request.port > 65535) throw new Error('invalid loopback port')\n const operation = PATHS[request.operation]\n if (request.operation === 'node-apply' || request.operation === 'session-apply') {\n const last = frames[frames.length - 1]\n if (!last || last.kind !== 'control' || last.value.kind !== 'request-end') throw new Error('apply request terminator required')\n if (frames.slice(1, -1).some((frame) => frame.kind !== 'data')) throw new Error('invalid apply request body')\n const chunks = frames.slice(1, -1).map((frame) => frame.data)\n return { request, operation, body: Buffer.concat(chunks) }\n }\n if (frames.length !== 1) throw new Error('unexpected helper request frames')\n return { request, operation }\n}\n\nasync function run() {\n const buffer = Buffer.concat(input, inputBytes)\n const { request, operation, body } = parseRequest(buffer)\n const url = 'http://127.0.0.1:' + request.port + operation.path\n const response = await fetch(url, {\n method: operation.method,\n redirect: 'error',\n headers: { accept: 'application/json', ...(body ? { 'content-type': 'application/json' } : {}) },\n ...(body ? { body } : {}),\n })\n\n function writeStdout(data) {\n return new Promise(function(resolve, reject) {\n if (process.stdout.write(data)) resolve()\n else process.stdout.once('drain', resolve)\n })\n }\n\n if (request.operation === 'node-manifest' || request.operation === 'session-bundle') {\n await writeStdout(encodeControl({ kind: 'response', status: response.status, bodyMode: 'data' }))\n if (!response.body) throw new Error('manifest response body missing')\n var sequence = 0\n var total = 0\n for await (var chunk of response.body) {\n var data = Buffer.from(chunk)\n total += data.length\n if (total > MAX_TRANSFER) throw new Error('manifest exceeds size limit')\n for (var offset = 0; offset < data.length; offset += MAX_CHUNK) {\n await writeStdout(encodeData(sequence++, data.subarray(offset, Math.min(offset + MAX_CHUNK, data.length))))\n }\n }\n await writeStdout(encodeControl({ kind: 'response-end' }))\n if (!response.ok) process.exitCode = 1\n return\n }\n\n var text = await response.text()\n if (Buffer.byteLength(text, 'utf8') > MAX_CONTROL) throw new Error('response control frame too large')\n await writeStdout(encodeControl({ kind: 'response', status: response.status, body: JSON.parse(text) }))\n if (!response.ok) process.exitCode = 1\n}\n\nprocess.stdin.on('data', (chunk) => {\n inputBytes += chunk.length\n if (inputBytes > MAX_TRANSFER + 2 * MAX_CONTROL) {\n process.exitCode = 1\n process.stdin.destroy()\n return\n }\n input.push(Buffer.from(chunk))\n})\nprocess.stdin.on('end', () => {\n run().catch(() => {\n try { process.stdout.write(encodeControl({ kind: 'error', code: 'helper-failed' })) } catch {}\n process.exitCode = 1\n })\n})\n`\n\nconst REMOTE_HELPER_BASE64 = Buffer.from(REMOTE_HELPER_SOURCE, 'utf8').toString('base64')\n\nexport const REMOTE_SYNC_HELPER_COMMAND =\n `node -e 'eval(Buffer.from(\"${REMOTE_HELPER_BASE64}\",\"base64\").toString(\"utf8\"))'`\n"],"mappings":";;AAEA,MAAM,uBAAuB,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4JvC,MAAa,6BACX,8BAH2B,OAAO,KAAK,sBAAsB,MAAM,CAAC,CAAC,SAAS,QAGhD,EAAqB"}
1
+ {"version":3,"file":"remote-helper.js","names":[],"sources":["../../../src/core/sync/remote-helper.ts"],"sourcesContent":["import { Buffer } from 'node:buffer'\n\nconst REMOTE_HELPER_SOURCE = String.raw`'use strict'\nconst { createHash, timingSafeEqual } = require('node:crypto')\nconst MAX_CONTROL = 65536\nconst MAX_CHUNK = 1048576\nconst MAX_TRANSFER = 107374182400\nconst PATHS = {\n handshake: { path: '/api/dshb/sync/internal/handshake', method: 'GET' },\n 'node-manifest': { path: '/api/dshb/sync/internal/nodes/manifest', method: 'GET' },\n 'node-apply': { path: '/api/dshb/sync/internal/nodes/apply', method: 'POST' },\n 'session-bundle': { path: '/api/dshb/sync/internal/sessions/bundle', method: 'GET' },\n 'session-manifest': { path: '/api/dshb/sync/internal/sessions/manifest', method: 'GET' },\n 'session-entries': { path: '/api/dshb/sync/internal/sessions/entries', method: 'POST' },\n 'session-apply': { path: '/api/dshb/sync/internal/sessions/apply', method: 'POST' },\n}\nconst input = []\nlet inputBytes = 0\n\nfunction encodeControl(value) {\n const body = Buffer.from(JSON.stringify(value) + '\\n', 'utf8')\n if (body.length > MAX_CONTROL) throw new Error('control frame too large')\n const header = Buffer.alloc(5)\n header[0] = 0\n header.writeUInt32BE(body.length, 1)\n return Buffer.concat([header, body])\n}\n\nfunction encodeData(sequence, data) {\n const digest = createHash('sha256').update(data).digest()\n const metadata = Buffer.alloc(36)\n metadata.writeUInt32BE(sequence, 0)\n digest.copy(metadata, 4)\n const payload = Buffer.concat([metadata, data])\n const header = Buffer.alloc(5)\n header[0] = 1\n header.writeUInt32BE(payload.length, 1)\n return Buffer.concat([header, payload])\n}\n\nfunction decodeFrames(buffer) {\n const frames = []\n let offset = 0\n let sequence = 0\n let totalData = 0\n while (offset < buffer.length) {\n if (buffer.length - offset < 5) throw new Error('truncated frame header')\n const kind = buffer[offset]\n const length = buffer.readUInt32BE(offset + 1)\n offset += 5\n const maxLength = kind === 0 ? MAX_CONTROL : 36 + MAX_CHUNK\n if ((kind !== 0 && kind !== 1) || length > maxLength || length > buffer.length - offset) {\n throw new Error('invalid frame length')\n }\n const payload = buffer.subarray(offset, offset + length)\n offset += length\n if (kind === 0) {\n if (length < 2 || payload[length - 1] !== 10) throw new Error('invalid control frame')\n frames.push({ kind: 'control', value: JSON.parse(payload.subarray(0, -1).toString('utf8')) })\n } else {\n if (length < 36) throw new Error('invalid data frame')\n const currentSequence = payload.readUInt32BE(0)\n const expected = payload.subarray(4, 36)\n const data = payload.subarray(36)\n const actual = createHash('sha256').update(data).digest()\n if (currentSequence !== sequence || !timingSafeEqual(expected, actual)) throw new Error('invalid data chunk')\n sequence += 1\n totalData += data.length\n if (totalData > MAX_TRANSFER) throw new Error('transfer exceeds size limit')\n frames.push({ kind: 'data', data })\n }\n }\n return frames\n}\n\nfunction parseRequest(buffer) {\n const frames = decodeFrames(buffer)\n const first = frames[0]\n if (!first || first.kind !== 'control') throw new Error('request control frame required')\n const request = first.value\n if (!request || request.kind !== 'request' || request.protocolVersion !== 1 || !Object.prototype.hasOwnProperty.call(PATHS, request.operation)) {\n throw new Error('unsupported helper request')\n }\n if (Object.keys(request).some((key) => !['kind', 'protocolVersion', 'operation', 'port'].includes(key))) {\n throw new Error('unsupported helper request field')\n }\n if (!Number.isInteger(request.port) || request.port < 1 || request.port > 65535) throw new Error('invalid loopback port')\n const operation = PATHS[request.operation]\n if (request.operation === 'node-apply' || request.operation === 'session-entries' || request.operation === 'session-apply') {\n const last = frames[frames.length - 1]\n if (!last || last.kind !== 'control' || last.value.kind !== 'request-end') throw new Error('apply request terminator required')\n if (frames.slice(1, -1).some((frame) => frame.kind !== 'data')) throw new Error('invalid apply request body')\n const chunks = frames.slice(1, -1).map((frame) => frame.data)\n return { request, operation, body: Buffer.concat(chunks) }\n }\n if (frames.length !== 1) throw new Error('unexpected helper request frames')\n return { request, operation }\n}\n\nasync function run() {\n const buffer = Buffer.concat(input, inputBytes)\n const { request, operation, body } = parseRequest(buffer)\n const url = 'http://127.0.0.1:' + request.port + operation.path\n const response = await fetch(url, {\n method: operation.method,\n redirect: 'error',\n headers: { accept: 'application/json', ...(body ? { 'content-type': 'application/json' } : {}) },\n ...(body ? { body } : {}),\n })\n\n function writeStdout(data) {\n return new Promise(function(resolve, reject) {\n if (process.stdout.write(data)) resolve()\n else process.stdout.once('drain', resolve)\n })\n }\n\n if (request.operation === 'node-manifest' || request.operation === 'session-bundle' ||\n request.operation === 'session-manifest' || request.operation === 'session-entries') {\n await writeStdout(encodeControl({ kind: 'response', status: response.status, bodyMode: 'data' }))\n if (!response.body) throw new Error('manifest response body missing')\n var sequence = 0\n var total = 0\n for await (var chunk of response.body) {\n var data = Buffer.from(chunk)\n total += data.length\n if (total > MAX_TRANSFER) throw new Error('manifest exceeds size limit')\n for (var offset = 0; offset < data.length; offset += MAX_CHUNK) {\n await writeStdout(encodeData(sequence++, data.subarray(offset, Math.min(offset + MAX_CHUNK, data.length))))\n }\n }\n await writeStdout(encodeControl({ kind: 'response-end' }))\n if (!response.ok) process.exitCode = 1\n return\n }\n\n var text = await response.text()\n if (Buffer.byteLength(text, 'utf8') > MAX_CONTROL) throw new Error('response control frame too large')\n var responseBody = JSON.parse(text)\n await writeStdout(encodeControl({ kind: 'response', status: response.status, body: responseBody }))\n var recoverableDeltaMismatch = request.operation === 'session-apply' && response.status === 409 &&\n responseBody && responseBody.code === 'session-delta-prefix'\n if (!response.ok && !recoverableDeltaMismatch) process.exitCode = 1\n}\n\nprocess.stdin.on('data', (chunk) => {\n inputBytes += chunk.length\n if (inputBytes > MAX_TRANSFER + 2 * MAX_CONTROL) {\n process.exitCode = 1\n process.stdin.destroy()\n return\n }\n input.push(Buffer.from(chunk))\n})\nprocess.stdin.on('end', () => {\n run().catch(() => {\n try { process.stdout.write(encodeControl({ kind: 'error', code: 'helper-failed' })) } catch {}\n process.exitCode = 1\n })\n})\n`\n\nconst REMOTE_HELPER_BASE64 = Buffer.from(REMOTE_HELPER_SOURCE, 'utf8').toString('base64')\n\nexport const REMOTE_SYNC_HELPER_COMMAND =\n `node -e 'eval(Buffer.from(\"${REMOTE_HELPER_BASE64}\",\"base64\").toString(\"utf8\"))'`\n"],"mappings":";;AAEA,MAAM,uBAAuB,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkKvC,MAAa,6BACX,8BAH2B,OAAO,KAAK,sBAAsB,MAAM,CAAC,CAAC,SAAS,QAGhD,EAAqB"}
@@ -1,5 +1,5 @@
1
1
  import { SyncPeerManager } from "./peer-manager.js";
2
- import { a as SyncSshTransport } from "../../ssh-transport-BcIiLxsX.js";
2
+ import { s as SyncSshTransport } from "../../ssh-transport-DGkQh-HA.js";
3
3
  import { SyncCoordinator } from "./coordinator.js";
4
4
  import { Context } from "@deepseek-ai/cordis";
5
5
  //#region src/core/sync/routes.d.ts
@@ -1,4 +1,4 @@
1
- import { c as SyncSessionService } from "../../session-sync-g6qKZzjV.js";
1
+ import { f as SyncSessionService } from "../../session-sync-C2j2rk9K.js";
2
2
  import { Context } from "@deepseek-ai/cordis";
3
3
  //#region src/core/sync/session-routes.d.ts
4
4
  declare function registerSyncSessionRoutes(ctx: Context, sessions: SyncSessionService): void;
@@ -1,4 +1,4 @@
1
- import { t as SyncSessionError } from "../../session-sync-EdfwUON5.js";
1
+ import { t as SyncSessionError } from "../../session-sync-C0uoizyB.js";
2
2
  //#region src/core/sync/session-routes.ts
3
3
  function sendJson(res, status, body) {
4
4
  res.writeHead(status, {
@@ -1,2 +1,2 @@
1
- import { a as SyncSessionError, c as SyncSessionService, d as WorkspaceRegistryLike, f as makeSyncSessionBundle, i as SyncSessionEntry, l as SyncSessionTransferPlan, m as validateSyncSessionBundle, n as SyncSessionApplyResult, o as SyncSessionInstructionSnapshot, p as planSessionTransfers, r as SyncSessionBundle, s as SyncSessionObject, t as SyncSessionApplyOptions, u as SyncSessionWorkspace } from "../../session-sync-g6qKZzjV.js";
2
- export { SyncSessionApplyOptions, SyncSessionApplyResult, SyncSessionBundle, SyncSessionEntry, SyncSessionError, SyncSessionInstructionSnapshot, SyncSessionObject, SyncSessionService, SyncSessionTransferPlan, SyncSessionWorkspace, WorkspaceRegistryLike, makeSyncSessionBundle, planSessionTransfers, validateSyncSessionBundle };
1
+ import { S as validateSyncSessionManifest, _ as makeSyncSessionManifest, a as SyncSessionError, b as syncSessionEntryKey, c as SyncSessionInstructionSnapshot, d as SyncSessionObject, f as SyncSessionService, g as makeSyncSessionBundle, h as WorkspaceRegistryLike, i as SyncSessionEntry, l as SyncSessionManifest, m as SyncSessionWorkspace, n as SyncSessionApplyResult, o as SyncSessionEventDelta, p as SyncSessionTransferPlan, r as SyncSessionBundle, s as SyncSessionEventPrefix, t as SyncSessionApplyOptions, u as SyncSessionManifestEntry, v as planSessionTransfers, x as validateSyncSessionBundle, y as sessionEventListDigest } from "../../session-sync-C2j2rk9K.js";
2
+ export { SyncSessionApplyOptions, SyncSessionApplyResult, SyncSessionBundle, SyncSessionEntry, SyncSessionError, SyncSessionEventDelta, SyncSessionEventPrefix, SyncSessionInstructionSnapshot, SyncSessionManifest, SyncSessionManifestEntry, SyncSessionObject, SyncSessionService, SyncSessionTransferPlan, SyncSessionWorkspace, WorkspaceRegistryLike, makeSyncSessionBundle, makeSyncSessionManifest, planSessionTransfers, sessionEventListDigest, syncSessionEntryKey, validateSyncSessionBundle, validateSyncSessionManifest };
@@ -1,2 +1,2 @@
1
- import { a as validateSyncSessionBundle, i as planSessionTransfers, n as SyncSessionService, r as makeSyncSessionBundle, t as SyncSessionError } from "../../session-sync-EdfwUON5.js";
2
- export { SyncSessionError, SyncSessionService, makeSyncSessionBundle, planSessionTransfers, validateSyncSessionBundle };
1
+ import { a as planSessionTransfers, c as validateSyncSessionBundle, i as makeSyncSessionManifest, l as validateSyncSessionManifest, n as SyncSessionService, o as sessionEventListDigest, r as makeSyncSessionBundle, s as syncSessionEntryKey, t as SyncSessionError } from "../../session-sync-C0uoizyB.js";
2
+ export { SyncSessionError, SyncSessionService, makeSyncSessionBundle, makeSyncSessionManifest, planSessionTransfers, sessionEventListDigest, syncSessionEntryKey, validateSyncSessionBundle, validateSyncSessionManifest };
@@ -1,2 +1,2 @@
1
- import { a as SyncSshTransport, i as SyncSshSession, n as SyncSshErrorCategory, o as buildSyncSshConnectConfig, r as SyncSshProbeError, t as SyncRemoteHandshake } from "../../ssh-transport-BcIiLxsX.js";
2
- export { SyncRemoteHandshake, SyncSshErrorCategory, SyncSshProbeError, SyncSshSession, SyncSshTransport, buildSyncSshConnectConfig };
1
+ import { a as SyncSshProbeError, c as buildSyncSshConnectConfig, i as SyncSshErrorCategory, n as SyncRemoteNodeManifest, o as SyncSshSession, r as SyncSessionDeltaPrefixError, s as SyncSshTransport, t as SyncRemoteHandshake } from "../../ssh-transport-DGkQh-HA.js";
2
+ export { SyncRemoteHandshake, SyncRemoteNodeManifest, SyncSessionDeltaPrefixError, SyncSshErrorCategory, SyncSshProbeError, SyncSshSession, SyncSshTransport, buildSyncSshConnectConfig };
@@ -17,6 +17,12 @@ var SyncSshProbeError = class extends Error {
17
17
  this.name = "SyncSshProbeError";
18
18
  }
19
19
  };
20
+ var SyncSessionDeltaPrefixError = class extends Error {
21
+ constructor() {
22
+ super("remote session event prefix changed during transfer");
23
+ this.name = "SyncSessionDeltaPrefixError";
24
+ }
25
+ };
20
26
  var SyncSshSession = class {
21
27
  client;
22
28
  channel;
@@ -172,50 +178,52 @@ var SyncSshTransport = class {
172
178
  }
173
179
  }
174
180
  async fetchNodeManifest(peerId) {
181
+ const value = await this.fetchDataOperation(peerId, "node-manifest");
182
+ if (!isRecord(value) || value.ok !== true || !isRecord(value.manifest)) throw new SyncSshProbeError("helper");
183
+ const manifest = value.manifest;
184
+ if (manifest.protocolVersion !== 1 || typeof manifest.instanceId !== "string" || !Array.isArray(manifest.records)) throw new SyncSshProbeError("protocol");
185
+ const capabilities = value.capabilities === void 0 ? void 0 : value.capabilities;
186
+ if (capabilities !== void 0 && (!Array.isArray(capabilities) || !capabilities.every((item) => typeof item === "string"))) throw new SyncSshProbeError("protocol");
187
+ return {
188
+ ...manifest,
189
+ ...capabilities ? { capabilities } : {}
190
+ };
191
+ }
192
+ async applyNodeManifest(peerId, records, sourceName) {
193
+ const requestBody = Buffer.from(JSON.stringify({
194
+ records,
195
+ sourceName
196
+ }), "utf8");
197
+ if (requestBody.length > 107374182400) throw new SyncSshProbeError("protocol");
175
198
  const { peer, session } = await this.openSession(peerId);
176
199
  try {
177
200
  await session.sendControl({
178
201
  kind: "request",
179
202
  protocolVersion: 1,
180
- operation: "node-manifest",
203
+ operation: "node-apply",
181
204
  port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT
182
205
  });
206
+ for (let offset = 0, sequence = 0; offset < requestBody.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) await session.sendData(sequence, requestBody.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, requestBody.length)));
207
+ await session.sendControl({ kind: "request-end" });
183
208
  session.endInput();
184
- let responseStatus;
185
- let bodyMode;
186
- let responseEnded = false;
187
- let totalBytes = 0;
188
- const chunks = [];
209
+ let response;
189
210
  for await (const frame of session.readFrames()) {
190
- if (frame.kind === "control") {
191
- if (frame.value.kind === "error") throw new SyncSshProbeError("helper");
192
- if (frame.value.kind === "response" && responseStatus === void 0) {
193
- responseStatus = typeof frame.value.status === "number" ? frame.value.status : void 0;
194
- bodyMode = typeof frame.value.bodyMode === "string" ? frame.value.bodyMode : void 0;
195
- continue;
196
- }
197
- if (frame.value.kind === "response-end" && responseStatus !== void 0 && !responseEnded) {
198
- responseEnded = true;
199
- continue;
200
- }
201
- throw new SyncProtocolError("unexpected node manifest control frame");
202
- }
203
- if (responseStatus === void 0 || bodyMode !== "data" || responseEnded) throw new SyncProtocolError("unexpected node manifest data frame");
204
- totalBytes += frame.data.length;
205
- if (totalBytes > 107374182400) throw new SyncProtocolError("node manifest exceeds transfer limit");
206
- chunks.push(frame.data);
207
- }
208
- if (responseStatus !== 200 || !responseEnded) throw new SyncSshProbeError("helper");
209
- let value;
210
- try {
211
- value = JSON.parse(Buffer.concat(chunks, totalBytes).toString("utf8"));
212
- } catch {
213
- throw new SyncProtocolError("node manifest response is invalid JSON");
211
+ if (frame.kind !== "control" || response) throw new SyncProtocolError("unexpected node apply response frame");
212
+ response = frame.value;
214
213
  }
215
- if (value === null || typeof value !== "object" || Array.isArray(value) || value.ok !== true) throw new SyncSshProbeError("helper");
216
- const manifest = value.manifest;
217
- if (manifest === null || typeof manifest !== "object" || Array.isArray(manifest) || manifest.protocolVersion !== 1 || typeof manifest.instanceId !== "string" || !Array.isArray(manifest.records)) throw new SyncSshProbeError("protocol");
218
- return manifest;
214
+ if (!response || response.kind === "error") throw new SyncSshProbeError("helper");
215
+ if (response.kind !== "response" || response.status !== 200 || response.body === null || typeof response.body !== "object") throw new SyncSshProbeError("helper");
216
+ const body = response.body;
217
+ if (body.ok !== true || body.result === null || typeof body.result !== "object") throw new SyncSshProbeError("helper");
218
+ const result = body.result;
219
+ for (const key of [
220
+ "created",
221
+ "updated",
222
+ "deleted",
223
+ "pendingRemoval",
224
+ "skipped"
225
+ ]) if (!Number.isSafeInteger(result[key]) || result[key] < 0) throw new SyncSshProbeError("protocol");
226
+ return result;
219
227
  } catch (error) {
220
228
  if (error instanceof SyncSshProbeError) throw error;
221
229
  if (error instanceof SyncProtocolError) throw new SyncSshProbeError("protocol");
@@ -224,10 +232,36 @@ var SyncSshTransport = class {
224
232
  session.close();
225
233
  }
226
234
  }
227
- async applyNodeManifest(peerId, records, sourceName) {
235
+ async fetchSessionBundle(peerId) {
236
+ const value = await this.fetchDataOperation(peerId, "session-bundle");
237
+ if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError("helper");
238
+ const bundle = value.bundle;
239
+ if (bundle.protocolVersion !== 1 || typeof bundle.instanceId !== "string" || !Array.isArray(bundle.entries) || !Array.isArray(bundle.skipped) || typeof bundle.digest !== "string") throw new SyncSshProbeError("protocol");
240
+ return bundle;
241
+ }
242
+ async fetchSessionManifest(peerId) {
243
+ const value = await this.fetchDataOperation(peerId, "session-manifest");
244
+ if (!isRecord(value) || value.ok !== true || !isRecord(value.manifest)) throw new SyncSshProbeError("helper");
245
+ const manifest = value.manifest;
246
+ if (manifest.protocolVersion !== 1 || typeof manifest.instanceId !== "string" || !Array.isArray(manifest.entries) || !Array.isArray(manifest.skipped) || typeof manifest.digest !== "string") throw new SyncSshProbeError("protocol");
247
+ return manifest;
248
+ }
249
+ async fetchSessionEntries(peerId, keys, manifestDigest, prefixes = []) {
250
+ const value = await this.fetchDataOperation(peerId, "session-entries", {
251
+ keys,
252
+ manifestDigest,
253
+ prefixes
254
+ });
255
+ if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError("helper");
256
+ const bundle = value.bundle;
257
+ if (bundle.protocolVersion !== 1 || typeof bundle.instanceId !== "string" || !Array.isArray(bundle.entries) || !Array.isArray(bundle.skipped) || typeof bundle.digest !== "string") throw new SyncSshProbeError("protocol");
258
+ return bundle;
259
+ }
260
+ async applySessionBundle(peerId, bundle, sourceName) {
228
261
  const requestBody = Buffer.from(JSON.stringify({
229
- records,
230
- sourceName
262
+ bundle,
263
+ sourceName,
264
+ peerId
231
265
  }), "utf8");
232
266
  if (requestBody.length > 107374182400) throw new SyncSshProbeError("protocol");
233
267
  const { peer, session } = await this.openSession(peerId);
@@ -235,7 +269,7 @@ var SyncSshTransport = class {
235
269
  await session.sendControl({
236
270
  kind: "request",
237
271
  protocolVersion: 1,
238
- operation: "node-apply",
272
+ operation: "session-apply",
239
273
  port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT
240
274
  });
241
275
  for (let offset = 0, sequence = 0; offset < requestBody.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) await session.sendData(sequence, requestBody.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, requestBody.length)));
@@ -243,10 +277,11 @@ var SyncSshTransport = class {
243
277
  session.endInput();
244
278
  let response;
245
279
  for await (const frame of session.readFrames()) {
246
- if (frame.kind !== "control" || response) throw new SyncProtocolError("unexpected node apply response frame");
280
+ if (frame.kind !== "control" || response) throw new SyncProtocolError("unexpected session apply response frame");
247
281
  response = frame.value;
248
282
  }
249
283
  if (!response || response.kind === "error") throw new SyncSshProbeError("helper");
284
+ if (response.kind === "response" && response.status === 409 && isRecord(response.body) && response.body.code === "session-delta-prefix") throw new SyncSessionDeltaPrefixError();
250
285
  if (response.kind !== "response" || response.status !== 200 || response.body === null || typeof response.body !== "object") throw new SyncSshProbeError("helper");
251
286
  const body = response.body;
252
287
  if (body.ok !== true || body.result === null || typeof body.result !== "object") throw new SyncSshProbeError("helper");
@@ -254,12 +289,14 @@ var SyncSshTransport = class {
254
289
  for (const key of [
255
290
  "created",
256
291
  "updated",
292
+ "archived",
257
293
  "deleted",
258
- "pendingRemoval",
259
- "skipped"
294
+ "skipped",
295
+ "unmapped"
260
296
  ]) if (!Number.isSafeInteger(result[key]) || result[key] < 0) throw new SyncSshProbeError("protocol");
261
297
  return result;
262
298
  } catch (error) {
299
+ if (error instanceof SyncSessionDeltaPrefixError) throw error;
263
300
  if (error instanceof SyncSshProbeError) throw error;
264
301
  if (error instanceof SyncProtocolError) throw new SyncSshProbeError("protocol");
265
302
  throw new SyncSshProbeError("helper");
@@ -267,15 +304,26 @@ var SyncSshTransport = class {
267
304
  session.close();
268
305
  }
269
306
  }
270
- async fetchSessionBundle(peerId) {
307
+ async trustObservedHostKey(peerId) {
308
+ const { peer } = await this.peerManager.connectionData(peerId);
309
+ const fingerprint = this.hostKeyChallenges.acceptObserved(peer);
310
+ await this.peerManager.pinHostKey(peerId, fingerprint);
311
+ }
312
+ async fetchDataOperation(peerId, operation, requestBody) {
313
+ const body = requestBody === void 0 ? void 0 : Buffer.from(JSON.stringify(requestBody), "utf8");
314
+ if (body && body.length > 107374182400) throw new SyncSshProbeError("protocol");
271
315
  const { peer, session } = await this.openSession(peerId);
272
316
  try {
273
317
  await session.sendControl({
274
318
  kind: "request",
275
319
  protocolVersion: 1,
276
- operation: "session-bundle",
320
+ operation,
277
321
  port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT
278
322
  });
323
+ if (body) {
324
+ for (let offset = 0, sequence = 0; offset < body.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) await session.sendData(sequence, body.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, body.length)));
325
+ await session.sendControl({ kind: "request-end" });
326
+ }
279
327
  session.endInput();
280
328
  let responseStatus;
281
329
  let bodyMode;
@@ -294,24 +342,19 @@ var SyncSshTransport = class {
294
342
  responseEnded = true;
295
343
  continue;
296
344
  }
297
- throw new SyncProtocolError("unexpected session bundle control frame");
345
+ throw new SyncProtocolError(`unexpected ${operation} control frame`);
298
346
  }
299
- if (responseStatus === void 0 || bodyMode !== "data" || responseEnded) throw new SyncProtocolError("unexpected session bundle data frame");
347
+ if (responseStatus === void 0 || bodyMode !== "data" || responseEnded) throw new SyncProtocolError(`unexpected ${operation} data frame`);
300
348
  totalBytes += frame.data.length;
301
- if (totalBytes > 107374182400) throw new SyncProtocolError("session bundle exceeds transfer limit");
349
+ if (totalBytes > 107374182400) throw new SyncProtocolError(`${operation} exceeds transfer limit`);
302
350
  chunks.push(frame.data);
303
351
  }
304
352
  if (responseStatus !== 200 || !responseEnded) throw new SyncSshProbeError("helper");
305
- let value;
306
353
  try {
307
- value = JSON.parse(Buffer.concat(chunks, totalBytes).toString("utf8"));
354
+ return JSON.parse(Buffer.concat(chunks, totalBytes).toString("utf8"));
308
355
  } catch {
309
- throw new SyncProtocolError("session bundle response is invalid JSON");
356
+ throw new SyncProtocolError(`${operation} response is invalid JSON`);
310
357
  }
311
- if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError("helper");
312
- const bundle = value.bundle;
313
- if (bundle.protocolVersion !== 1 || typeof bundle.instanceId !== "string" || !Array.isArray(bundle.entries) || !Array.isArray(bundle.skipped) || typeof bundle.digest !== "string") throw new SyncSshProbeError("protocol");
314
- return bundle;
315
358
  } catch (error) {
316
359
  if (error instanceof SyncSshProbeError) throw error;
317
360
  if (error instanceof SyncProtocolError) {
@@ -324,56 +367,6 @@ var SyncSshTransport = class {
324
367
  session.close();
325
368
  }
326
369
  }
327
- async applySessionBundle(peerId, bundle, sourceName) {
328
- const requestBody = Buffer.from(JSON.stringify({
329
- bundle,
330
- sourceName,
331
- peerId
332
- }), "utf8");
333
- if (requestBody.length > 107374182400) throw new SyncSshProbeError("protocol");
334
- const { peer, session } = await this.openSession(peerId);
335
- try {
336
- await session.sendControl({
337
- kind: "request",
338
- protocolVersion: 1,
339
- operation: "session-apply",
340
- port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT
341
- });
342
- for (let offset = 0, sequence = 0; offset < requestBody.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) await session.sendData(sequence, requestBody.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, requestBody.length)));
343
- await session.sendControl({ kind: "request-end" });
344
- session.endInput();
345
- let response;
346
- for await (const frame of session.readFrames()) {
347
- if (frame.kind !== "control" || response) throw new SyncProtocolError("unexpected session apply response frame");
348
- response = frame.value;
349
- }
350
- if (!response || response.kind === "error") throw new SyncSshProbeError("helper");
351
- if (response.kind !== "response" || response.status !== 200 || response.body === null || typeof response.body !== "object") throw new SyncSshProbeError("helper");
352
- const body = response.body;
353
- if (body.ok !== true || body.result === null || typeof body.result !== "object") throw new SyncSshProbeError("helper");
354
- const result = body.result;
355
- for (const key of [
356
- "created",
357
- "updated",
358
- "archived",
359
- "deleted",
360
- "skipped",
361
- "unmapped"
362
- ]) if (!Number.isSafeInteger(result[key]) || result[key] < 0) throw new SyncSshProbeError("protocol");
363
- return result;
364
- } catch (error) {
365
- if (error instanceof SyncSshProbeError) throw error;
366
- if (error instanceof SyncProtocolError) throw new SyncSshProbeError("protocol");
367
- throw new SyncSshProbeError("helper");
368
- } finally {
369
- session.close();
370
- }
371
- }
372
- async trustObservedHostKey(peerId) {
373
- const { peer } = await this.peerManager.connectionData(peerId);
374
- const fingerprint = this.hostKeyChallenges.acceptObserved(peer);
375
- await this.peerManager.pinHostKey(peerId, fingerprint);
376
- }
377
370
  async requestHandshake(session, port) {
378
371
  await session.sendControl({
379
372
  kind: "request",
@@ -403,6 +396,6 @@ function validateHandshakeResponse(value) {
403
396
  };
404
397
  }
405
398
  //#endregion
406
- export { SyncSshProbeError, SyncSshSession, SyncSshTransport, buildSyncSshConnectConfig };
399
+ export { SyncSessionDeltaPrefixError, SyncSshProbeError, SyncSshSession, SyncSshTransport, buildSyncSshConnectConfig };
407
400
 
408
401
  //# sourceMappingURL=ssh-transport.js.map