@rocicorp/zero 1.11.0-canary.11 → 1.11.0-canary.13

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 (174) hide show
  1. package/out/analyze-query/src/analyze-cli.js +2 -2
  2. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  3. package/out/shared/src/ring-buffer.d.ts +2 -0
  4. package/out/shared/src/ring-buffer.d.ts.map +1 -1
  5. package/out/shared/src/ring-buffer.js +4 -0
  6. package/out/shared/src/ring-buffer.js.map +1 -1
  7. package/out/zero/package.js +1 -1
  8. package/out/zero/package.js.map +1 -1
  9. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  10. package/out/zero-cache/src/server/replicator.js +26 -13
  11. package/out/zero-cache/src/server/replicator.js.map +1 -1
  12. package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
  13. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +169 -19
  14. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  15. package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
  16. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  17. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +45 -6
  18. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  19. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +118 -33
  20. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  21. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +2 -2
  22. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  23. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +10 -17
  24. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  25. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  26. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +1 -1
  27. package/out/zero-cache/src/services/change-streamer/schema/tables.js +15 -4
  28. package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
  29. package/out/zero-cache/src/services/change-streamer/snapshot-message.d.ts +92 -0
  30. package/out/zero-cache/src/services/change-streamer/snapshot-message.d.ts.map +1 -0
  31. package/out/zero-cache/src/services/change-streamer/snapshot-message.js +42 -0
  32. package/out/zero-cache/src/services/change-streamer/snapshot-message.js.map +1 -0
  33. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +1 -1
  34. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +1 -1
  35. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js +1 -1
  36. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -1
  37. package/out/zero-cache/src/services/change-streamer/snapshot.d.ts +7 -89
  38. package/out/zero-cache/src/services/change-streamer/snapshot.d.ts.map +1 -1
  39. package/out/zero-cache/src/services/change-streamer/snapshot.js +26 -98
  40. package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
  41. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +358 -0
  42. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts.map +1 -0
  43. package/out/zero-cache/src/services/change-streamer/subscribe.js +68 -0
  44. package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -0
  45. package/out/zero-cache/src/services/litestream/commands.d.ts +2 -3
  46. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  47. package/out/zero-cache/src/services/litestream/commands.js +3 -2
  48. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  49. package/out/zero-cache/src/services/replicator/change-processor.js +12 -1
  50. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  51. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +2 -1
  52. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
  53. package/out/zero-cache/src/services/replicator/incremental-sync.js +13 -7
  54. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  55. package/out/zero-cache/src/services/replicator/replicator.d.ts +2 -1
  56. package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
  57. package/out/zero-cache/src/services/replicator/replicator.js +2 -2
  58. package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
  59. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +19 -2
  60. package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
  61. package/out/zero-cache/src/services/view-syncer/client-handler.js +8 -1
  62. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  63. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts +19 -1
  64. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  65. package/out/zero-cache/src/services/view-syncer/cvr-store.js +34 -11
  66. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  67. package/out/zero-cache/src/services/view-syncer/cvr.d.ts +6 -2
  68. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  69. package/out/zero-cache/src/services/view-syncer/cvr.js +9 -5
  70. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  71. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  72. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +44 -11
  73. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  74. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +21 -1
  75. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  76. package/out/zero-cache/src/services/view-syncer/snapshotter.js +14 -6
  77. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  78. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  79. package/out/zero-cache/src/services/view-syncer/view-syncer.js +28 -4
  80. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  81. package/out/zero-cache/src/types/streams.d.ts +33 -7
  82. package/out/zero-cache/src/types/streams.d.ts.map +1 -1
  83. package/out/zero-cache/src/types/streams.js +93 -50
  84. package/out/zero-cache/src/types/streams.js.map +1 -1
  85. package/out/zero-cache/src/types/subscription.d.ts +11 -0
  86. package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
  87. package/out/zero-cache/src/types/subscription.js +56 -35
  88. package/out/zero-cache/src/types/subscription.js.map +1 -1
  89. package/out/zero-cache/src/types/ws.js +1 -31
  90. package/out/zero-cache/src/types/ws.js.map +1 -1
  91. package/out/zero-cache/src/workers/connection.js +3 -3
  92. package/out/zero-cache/src/workers/connection.js.map +1 -1
  93. package/out/zero-cache/src/workers/syncer.js +2 -2
  94. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  95. package/out/zero-client/src/client/version.js +1 -1
  96. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  97. package/out/zero-client/src/client/zero.js +5 -5
  98. package/out/zero-client/src/client/zero.js.map +1 -1
  99. package/out/zero-protocol/src/inspect-down.d.ts +81 -81
  100. package/out/zql/src/builder/builder.d.ts.map +1 -1
  101. package/out/zql/src/builder/builder.js +6 -4
  102. package/out/zql/src/builder/builder.js.map +1 -1
  103. package/out/zql/src/ivm/cap.d.ts +2 -1
  104. package/out/zql/src/ivm/cap.d.ts.map +1 -1
  105. package/out/zql/src/ivm/cap.js +3 -0
  106. package/out/zql/src/ivm/cap.js.map +1 -1
  107. package/out/zql/src/ivm/exists.d.ts +2 -0
  108. package/out/zql/src/ivm/exists.d.ts.map +1 -1
  109. package/out/zql/src/ivm/exists.js +3 -0
  110. package/out/zql/src/ivm/exists.js.map +1 -1
  111. package/out/zql/src/ivm/fan-in.d.ts +5 -1
  112. package/out/zql/src/ivm/fan-in.d.ts.map +1 -1
  113. package/out/zql/src/ivm/fan-in.js +7 -0
  114. package/out/zql/src/ivm/fan-in.js.map +1 -1
  115. package/out/zql/src/ivm/fan-out.d.ts +3 -0
  116. package/out/zql/src/ivm/fan-out.d.ts.map +1 -1
  117. package/out/zql/src/ivm/fan-out.js +4 -0
  118. package/out/zql/src/ivm/fan-out.js.map +1 -1
  119. package/out/zql/src/ivm/filter-operators.d.ts +2 -0
  120. package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
  121. package/out/zql/src/ivm/filter-operators.js +9 -0
  122. package/out/zql/src/ivm/filter-operators.js.map +1 -1
  123. package/out/zql/src/ivm/filter.d.ts +4 -1
  124. package/out/zql/src/ivm/filter.d.ts.map +1 -1
  125. package/out/zql/src/ivm/filter.js +3 -0
  126. package/out/zql/src/ivm/filter.js.map +1 -1
  127. package/out/zql/src/ivm/flipped-join.d.ts +4 -3
  128. package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
  129. package/out/zql/src/ivm/flipped-join.js +73 -22
  130. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  131. package/out/zql/src/ivm/join-utils.d.ts +24 -7
  132. package/out/zql/src/ivm/join-utils.d.ts.map +1 -1
  133. package/out/zql/src/ivm/join-utils.js +87 -4
  134. package/out/zql/src/ivm/join-utils.js.map +1 -1
  135. package/out/zql/src/ivm/join.d.ts +4 -4
  136. package/out/zql/src/ivm/join.d.ts.map +1 -1
  137. package/out/zql/src/ivm/join.js +55 -64
  138. package/out/zql/src/ivm/join.js.map +1 -1
  139. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  140. package/out/zql/src/ivm/memory-source.js +33 -23
  141. package/out/zql/src/ivm/memory-source.js.map +1 -1
  142. package/out/zql/src/ivm/operator.d.ts +39 -10
  143. package/out/zql/src/ivm/operator.d.ts.map +1 -1
  144. package/out/zql/src/ivm/operator.js +9 -4
  145. package/out/zql/src/ivm/operator.js.map +1 -1
  146. package/out/zql/src/ivm/push-accumulated.d.ts +6 -1
  147. package/out/zql/src/ivm/push-accumulated.d.ts.map +1 -1
  148. package/out/zql/src/ivm/push-accumulated.js +47 -2
  149. package/out/zql/src/ivm/push-accumulated.js.map +1 -1
  150. package/out/zql/src/ivm/skip.d.ts +2 -1
  151. package/out/zql/src/ivm/skip.d.ts.map +1 -1
  152. package/out/zql/src/ivm/skip.js +3 -0
  153. package/out/zql/src/ivm/skip.js.map +1 -1
  154. package/out/zql/src/ivm/take-gate.d.ts +24 -2
  155. package/out/zql/src/ivm/take-gate.d.ts.map +1 -1
  156. package/out/zql/src/ivm/take-gate.js +26 -1
  157. package/out/zql/src/ivm/take-gate.js.map +1 -1
  158. package/out/zql/src/ivm/take.d.ts +2 -1
  159. package/out/zql/src/ivm/take.d.ts.map +1 -1
  160. package/out/zql/src/ivm/take.js +59 -40
  161. package/out/zql/src/ivm/take.js.map +1 -1
  162. package/out/zql/src/ivm/union-fan-in.d.ts +3 -0
  163. package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
  164. package/out/zql/src/ivm/union-fan-in.js +56 -5
  165. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  166. package/out/zql/src/ivm/union-fan-out.d.ts +2 -1
  167. package/out/zql/src/ivm/union-fan-out.d.ts.map +1 -1
  168. package/out/zql/src/ivm/union-fan-out.js +5 -0
  169. package/out/zql/src/ivm/union-fan-out.js.map +1 -1
  170. package/out/zql/src/query/measure-push-operator.d.ts +2 -1
  171. package/out/zql/src/query/measure-push-operator.d.ts.map +1 -1
  172. package/out/zql/src/query/measure-push-operator.js +7 -0
  173. package/out/zql/src/query/measure-push-operator.js.map +1 -1
  174. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot.js","names":[],"sources":["../../../../../../zero-cache/src/services/change-streamer/snapshot.ts"],"sourcesContent":["/**\n * The `snapshot` API serves the purpose of:\n * - informing subscribers (i.e. view-syncers) of the (litestream)\n * backup location from which to restore a replica snapshot\n * - checking whether a restored backup or existing replica is\n * compatible with the change-streamer\n * - preventing change-log cleanup while a snapshot restore is in\n * progress\n * - tracking the approximate time it takes from the beginning of\n * snapshot \"reservation\" to the subsequent subscription, which\n * serves as the minimum interval to wait before cleaning up\n * backed up changes.\n */\n\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {AbortError} from '../../../../shared/src/abort-error.ts';\nimport {sleep} from '../../../../shared/src/sleep.ts';\nimport * as v from '../../../../shared/src/valita.ts';\nimport {type NormalizedZeroConfig} from '../../config/normalize.ts';\nimport {getShardConfig} from '../../types/shards.ts';\nimport type {Source} from '../../types/streams.ts';\nimport {ChangeStreamerHttpClient} from './change-streamer-http.ts';\n\nconst statusSchema = v.object({\n tag: v.literal('status'),\n\n /**\n * The location from which litestream should perform the restore.\n */\n backupURL: v.string(),\n\n /**\n * The `replicaVersion` of the backup. If a subscriber's restored or\n * existing replica is of a different version, it should delete it and\n * retry the restore from litestream (i.e. equivalent to a\n * `WrongReplicaVersion` response from a `/changes` subscription).\n */\n replicaVersion: v.string(),\n\n /**\n * The earliest watermark from which catchup is possible. If the\n * subscriber's replica is older that this watermark, it should delete it\n * and (retry the) restore from litestream (i.e. equivalent to a\n * `WatermarkTooOld` response from a `/changes` subscription).\n */\n minWatermark: v.string(),\n\n /**\n * The size in bytes of the replication-manager's replica when the snapshot\n * was reserved, used as an estimate of the size of the restored replica\n * when reporting restore progress. Absent from older replication-managers.\n */\n replicaSize: v.number().optional(),\n});\n\nexport type SnapshotStatus = v.Infer<typeof statusSchema>;\n\nconst statusMessageSchema = v.tuple([v.literal('status'), statusSchema]);\n\nexport const snapshotMessageSchema = v.union(statusMessageSchema);\n\nexport type SnapshotMessage = v.Infer<typeof statusMessageSchema>;\n\nexport type ReserveSnapshot = (\n lc: LogContext,\n config: NormalizedZeroConfig,\n) => Promise<Source<SnapshotMessage>>;\n\nexport function reserveAndGetSnapshotStatus(\n lc: LogContext,\n config: NormalizedZeroConfig,\n reserve: ReserveSnapshot = reserveSnapshot, // for testing\n): Promise<SnapshotStatus> {\n const {promise: status, resolve, reject} = resolver<SnapshotStatus>();\n\n void (async function () {\n const abort = new AbortController();\n const {signal} = abort;\n const onSignal = () => abort.abort();\n process.on('SIGINT', onSignal);\n process.on('SIGTERM', onSignal);\n\n try {\n for (let i = 0; ; i++) {\n let err: unknown;\n try {\n let resolved = false;\n const stream = await untilAborted(reserve(lc, config), signal);\n // A signal while the stream is open cancels it, which ends the\n // iteration below with an AbortError.\n const cancelStream = () => stream.cancel(new AbortError('Aborted'));\n signal.addEventListener('abort', cancelStream, {once: true});\n try {\n for await (const msg of stream) {\n // Capture the value of the status message that the change-streamer\n // backup monitor returns, and hold the connection open to\n // \"reserve\" the snapshot and prevent change log cleanup.\n resolve(msg[1]);\n resolved = true;\n }\n } finally {\n signal.removeEventListener('abort', cancelStream);\n }\n // The change-streamer itself closes the connection when the\n // subscription is started (or the reservation retried).\n if (resolved) {\n break;\n }\n } catch (e) {\n err = e;\n }\n if (signal.aborted) {\n // (A no-op if the status was already resolved.)\n return reject(\n err instanceof AbortError ? err : new AbortError('Aborted'),\n );\n }\n // Retry in the view-syncer since it cannot proceed until it connects\n // to a (compatible) replication-manager. In particular, a\n // replication-manager that does not support the view-syncer's\n // change-streamer protocol will close the stream with an error; this\n // retry logic essentially delays the startup of a view-syncer until\n // a compatible replication-manager has been rolled out, allowing\n // replication-manager and view-syncer services to be updated in\n // parallel.\n lc.warn?.(\n `Unable to reserve snapshot (attempt ${i + 1}). Retrying in 5 seconds.`,\n String(err),\n );\n try {\n await sleep(5000, abort.signal);\n } catch (e) {\n return reject(e);\n }\n }\n } finally {\n // This function is called repeatedly (e.g. by the replicator's restore\n // loop), so the signal handlers must not outlive the reservation.\n process.off('SIGINT', onSignal);\n process.off('SIGTERM', onSignal);\n }\n })();\n\n return status;\n}\n\n/**\n * Resolves with the reserved stream, or rejects with an AbortError if the\n * `signal` is aborted while the reservation is pending (in which case a\n * stream that arrives later is canceled rather than held open).\n */\nfunction untilAborted<T>(\n reservation: Promise<Source<T>>,\n signal: AbortSignal,\n): Promise<Source<T>> {\n return new Promise((resolve, reject) => {\n const onAbort = () => reject(new AbortError('Aborted'));\n signal.addEventListener('abort', onAbort, {once: true});\n reservation.then(\n stream => {\n signal.removeEventListener('abort', onAbort);\n if (signal.aborted) {\n stream.cancel();\n } else {\n resolve(stream);\n }\n },\n e => {\n signal.removeEventListener('abort', onAbort);\n reject(e);\n },\n );\n });\n}\n\nfunction reserveSnapshot(\n lc: LogContext,\n config: NormalizedZeroConfig,\n): Promise<Source<SnapshotMessage>> {\n const {taskID, change, changeStreamer} = config;\n const shardID = getShardConfig(config);\n\n const changeStreamerClient = new ChangeStreamerHttpClient(\n lc,\n shardID,\n change.db,\n changeStreamer.uri,\n );\n\n return changeStreamerClient.reserveSnapshot(taskID);\n}\n"],"mappings":";;;;;;;;AAwBA,IAAM,eAAe,eAAE,OAAO;CAC5B,KAAK,eAAE,QAAQ,QAAQ;;;;CAKvB,WAAW,eAAE,OAAO;;;;;;;CAQpB,gBAAgB,eAAE,OAAO;;;;;;;CAQzB,cAAc,eAAE,OAAO;;;;;;CAOvB,aAAa,eAAE,OAAO,EAAE,SAAS;AACnC,CAAC;AAID,IAAM,sBAAsB,eAAE,MAAM,CAAC,eAAE,QAAQ,QAAQ,GAAG,YAAY,CAAC;AAEvE,IAAa,wBAAwB,eAAE,MAAM,mBAAmB;AAShE,SAAgB,4BACd,IACA,QACA,UAA2B,iBACF;CACzB,MAAM,EAAC,SAAS,QAAQ,SAAS,WAAU,SAAyB;CAEpE,CAAM,iBAAkB;EACtB,MAAM,QAAQ,IAAI,gBAAgB;EAClC,MAAM,EAAC,WAAU;EACjB,MAAM,iBAAiB,MAAM,MAAM;EACnC,QAAQ,GAAG,UAAU,QAAQ;EAC7B,QAAQ,GAAG,WAAW,QAAQ;EAE9B,IAAI;GACF,KAAK,IAAI,IAAI,IAAK,KAAK;IACrB,IAAI;IACJ,IAAI;KACF,IAAI,WAAW;KACf,MAAM,SAAS,MAAM,aAAa,QAAQ,IAAI,MAAM,GAAG,MAAM;KAG7D,MAAM,qBAAqB,OAAO,OAAO,IAAI,WAAW,SAAS,CAAC;KAClE,OAAO,iBAAiB,SAAS,cAAc,EAAC,MAAM,KAAI,CAAC;KAC3D,IAAI;MACF,WAAW,MAAM,OAAO,QAAQ;OAI9B,QAAQ,IAAI,EAAE;OACd,WAAW;MACb;KACF,UAAU;MACR,OAAO,oBAAoB,SAAS,YAAY;KAClD;KAGA,IAAI,UACF;IAEJ,SAAS,GAAG;KACV,MAAM;IACR;IACA,IAAI,OAAO,SAET,OAAO,OACL,eAAe,aAAa,MAAM,IAAI,WAAW,SAAS,CAC5D;IAUF,GAAG,OACD,uCAAuC,IAAI,EAAE,4BAC7C,OAAO,GAAG,CACZ;IACA,IAAI;KACF,MAAM,MAAM,KAAM,MAAM,MAAM;IAChC,SAAS,GAAG;KACV,OAAO,OAAO,CAAC;IACjB;GACF;EACF,UAAU;GAGR,QAAQ,IAAI,UAAU,QAAQ;GAC9B,QAAQ,IAAI,WAAW,QAAQ;EACjC;CACF,GAAG;CAEH,OAAO;AACT;;;;;;AAOA,SAAS,aACP,aACA,QACoB;CACpB,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,gBAAgB,OAAO,IAAI,WAAW,SAAS,CAAC;EACtD,OAAO,iBAAiB,SAAS,SAAS,EAAC,MAAM,KAAI,CAAC;EACtD,YAAY,MACV,WAAU;GACR,OAAO,oBAAoB,SAAS,OAAO;GAC3C,IAAI,OAAO,SACT,OAAO,OAAO;QAEd,QAAQ,MAAM;EAElB,IACA,MAAK;GACH,OAAO,oBAAoB,SAAS,OAAO;GAC3C,OAAO,CAAC;EACV,CACF;CACF,CAAC;AACH;AAEA,SAAS,gBACP,IACA,QACkC;CAClC,MAAM,EAAC,QAAQ,QAAQ,mBAAkB;CAUzC,OAAO,IAP0B,yBAC/B,IAHc,eAAe,MAI7B,GACA,OAAO,IACP,eAAe,GAGV,EAAqB,gBAAgB,MAAM;AACpD"}
1
+ {"version":3,"file":"snapshot.js","names":[],"sources":["../../../../../../zero-cache/src/services/change-streamer/snapshot.ts"],"sourcesContent":["/**\n * The `snapshot` API serves the purpose of:\n * - informing subscribers (i.e. view-syncers) of the (litestream)\n * backup location from which to restore a replica snapshot\n * - checking whether a restored backup or existing replica is\n * compatible with the change-streamer\n * - preventing change-log cleanup while a snapshot restore is in\n * progress\n * - tracking the approximate time it takes from the beginning of\n * snapshot \"reservation\" to the subsequent subscription, which\n * serves as the minimum interval to wait before cleaning up\n * backed up changes.\n */\n\nimport type {LogContext} from '@rocicorp/logger';\nimport {AbortError} from '../../../../shared/src/abort-error.ts';\nimport {promiseOrAbort} from '../../../../shared/src/promise-race.ts';\nimport {sleep} from '../../../../shared/src/sleep.ts';\n\n// The schema definitions live in the pure schema module (with no dependency on\n// the HTTP client) so that subscribe.ts can import them without forming an\n// import cycle through change-streamer-http.ts. Imported here for local use and\n// re-exported for existing consumers.\nimport type {\n ReservationFollowup,\n SnapshotReserver,\n} from './change-streamer-http.ts';\nimport type {SnapshotStatus} from './subscribe.ts';\n\nexport type {SnapshotStatus};\n\nexport async function reserveAndGetSnapshotStatus(\n lc: LogContext,\n taskID: string,\n changeStreamer: SnapshotReserver,\n): Promise<{reserved: SnapshotStatus; followup: ReservationFollowup}> {\n const abort = new AbortController();\n const {signal} = abort;\n // Use our own AbortError as the abort reason (rather than the native\n // DOMException that AbortController.abort() defaults to) so that\n // promiseOrAbort()'s rejection, which propagates `signal.reason` verbatim,\n // is always an instance of the shared AbortError type.\n const onSignal = () => abort.abort(new AbortError('SIGTERM/SIGINT'));\n process.on('SIGINT', onSignal);\n process.on('SIGTERM', onSignal);\n\n try {\n for (let i = 0; ; i++) {\n let err: unknown;\n const reservation = changeStreamer.reserveSnapshot(taskID);\n try {\n return await promiseOrAbort(reservation, signal);\n } catch (e) {\n err = e;\n }\n if (signal.aborted) {\n // promiseOrAbort() does not cancel the loser of the race: the\n // reservation may still resolve later. If it does, cancel its\n // connection rather than leaking it, since this function has already\n // given up and nobody will call followup.subscribe().\n reservation.then(\n ({followup}) => followup.cancel(),\n () => {},\n );\n throw err;\n }\n // Retry in the view-syncer since it cannot proceed until it connects\n // to a (compatible) replication-manager. In particular, a\n // replication-manager that does not support the view-syncer's\n // change-streamer protocol will close the stream with an error; this\n // retry logic essentially delays the startup of a view-syncer until\n // a compatible replication-manager has been rolled out, allowing\n // replication-manager and view-syncer services to be updated in\n // parallel.\n lc.warn?.(\n `Unable to reserve snapshot (attempt ${i + 1}). Retrying in 5 seconds.`,\n String(err),\n );\n await sleep(5000, signal);\n }\n } finally {\n // This function is called repeatedly (e.g. by the replicator's restore\n // loop), so the signal handlers must not outlive the reservation.\n process.off('SIGINT', onSignal);\n process.off('SIGTERM', onSignal);\n }\n}\n"],"mappings":";;;;AA+BA,eAAsB,4BACpB,IACA,QACA,gBACoE;CACpE,MAAM,QAAQ,IAAI,gBAAgB;CAClC,MAAM,EAAC,WAAU;CAKjB,MAAM,iBAAiB,MAAM,MAAM,IAAI,WAAW,gBAAgB,CAAC;CACnE,QAAQ,GAAG,UAAU,QAAQ;CAC7B,QAAQ,GAAG,WAAW,QAAQ;CAE9B,IAAI;EACF,KAAK,IAAI,IAAI,IAAK,KAAK;GACrB,IAAI;GACJ,MAAM,cAAc,eAAe,gBAAgB,MAAM;GACzD,IAAI;IACF,OAAO,MAAM,eAAe,aAAa,MAAM;GACjD,SAAS,GAAG;IACV,MAAM;GACR;GACA,IAAI,OAAO,SAAS;IAKlB,YAAY,MACT,EAAC,eAAc,SAAS,OAAO,SAC1B,CAAC,CACT;IACA,MAAM;GACR;GASA,GAAG,OACD,uCAAuC,IAAI,EAAE,4BAC7C,OAAO,GAAG,CACZ;GACA,MAAM,MAAM,KAAM,MAAM;EAC1B;CACF,UAAU;EAGR,QAAQ,IAAI,UAAU,QAAQ;EAC9B,QAAQ,IAAI,WAAW,QAAQ;CACjC;AACF"}
@@ -0,0 +1,358 @@
1
+ /**
2
+ * The `subscribe` API (protocol v7+) merges the previous `/snapshot` and
3
+ * `/changes` connections into a single bidirectional WebSocket, so that the
4
+ * replication-manager that reserves change-log entries for a soon-to-be
5
+ * subscriber is the same replication-manager that serves the subscription.
6
+ *
7
+ * The connection is driven by an upstream (view-syncer → replication-manager)
8
+ * application message that selects the flow:
9
+ *
10
+ * - **Fresh startup:** the view-syncer sends `['reserve-snapshot', {taskID}]`.
11
+ * The change-streamer opens a snapshot reservation (pinning the change log)
12
+ * and replies downstream with a single `['reserved', …]` message telling the
13
+ * view-syncer where to restore the backup from and the watermark from which
14
+ * catchup is possible. Once the backup is restored, the view-syncer sends
15
+ * `['start-subscription', ctx]`, which transitions the same connection into
16
+ * the subscription phase (change stream downstream). The reservation is
17
+ * released only after the subscriber is registered, so there is never a gap
18
+ * in which the change-streamer does not know the client's watermark.
19
+ *
20
+ * - **Reconnect (replica already valid):** the view-syncer sends
21
+ * `['start-subscription', ctx]` immediately, with no reservation.
22
+ *
23
+ * The reservation status uses a distinct `'reserved'` tag (rather than the
24
+ * change stream's `'status'`) so the downstream union is unambiguous under
25
+ * passthrough parsing.
26
+ */
27
+ import * as v from '../../../../shared/src/valita.ts';
28
+ declare const snapshotStatusSchema: v.ObjectType<Omit<{
29
+ tag: v.Type<"status">;
30
+ backupURL: v.Type<string>;
31
+ replicaVersion: v.Type<string>;
32
+ minWatermark: v.Type<string>;
33
+ replicaSize: v.Optional<number>;
34
+ }, "tag"> & {
35
+ tag: v.Type<"snapshot">;
36
+ }, undefined>;
37
+ export type SnapshotStatus = v.Infer<typeof snapshotStatusSchema>;
38
+ export declare const reserveSnapshotMessageSchema: v.TupleType<[v.Type<"reserve-snapshot">, v.ObjectType<{
39
+ taskID: v.Type<string>;
40
+ }, undefined>]>;
41
+ /**
42
+ * The subscription-initialization context, sent in the `start-subscription`
43
+ * message. Equivalent to the previous URL-encoded `SubscriberContext`, minus
44
+ * the fields that are now implicit on the merged connection:
45
+ * - `protocolVersion` comes from the request path.
46
+ * - `wsBatched` is always on (the transport batches internally).
47
+ *
48
+ * Parsed in `passthrough` mode so a forthcoming, orthogonal change can add
49
+ * arbitrary table/column/backfill state without a protocol bump.
50
+ */
51
+ export declare const subscribeContextSchema: v.ObjectType<{
52
+ /** Task ID, links the subscription to a preceding snapshot reservation. */
53
+ taskID: v.Type<string>;
54
+ /** Subscriber id (debugging only). */
55
+ id: v.Type<string>;
56
+ /** 'serving' (user-facing) or 'backup' (in-RM replicator). */
57
+ mode: v.UnionType<[v.Type<"serving">, v.Type<"backup">]>;
58
+ /** The replica version the subscriber restored / is running. */
59
+ replicaVersion: v.Type<string>;
60
+ /** The watermark up to which the subscriber is up to date. */
61
+ watermark: v.Type<string>;
62
+ }, undefined>;
63
+ export type SubscribeContext = v.Infer<typeof subscribeContextSchema>;
64
+ export declare const startSubscriptionMessageSchema: v.TupleType<[v.Type<"start-subscription">, v.ObjectType<{
65
+ /** Task ID, links the subscription to a preceding snapshot reservation. */
66
+ taskID: v.Type<string>;
67
+ /** Subscriber id (debugging only). */
68
+ id: v.Type<string>;
69
+ /** 'serving' (user-facing) or 'backup' (in-RM replicator). */
70
+ mode: v.UnionType<[v.Type<"serving">, v.Type<"backup">]>;
71
+ /** The replica version the subscriber restored / is running. */
72
+ replicaVersion: v.Type<string>;
73
+ /** The watermark up to which the subscriber is up to date. */
74
+ watermark: v.Type<string>;
75
+ }, undefined>]>;
76
+ export declare const subscribeUpstreamSchema: v.UnionType<[v.TupleType<[v.Type<"reserve-snapshot">, v.ObjectType<{
77
+ taskID: v.Type<string>;
78
+ }, undefined>]>, v.TupleType<[v.Type<"start-subscription">, v.ObjectType<{
79
+ /** Task ID, links the subscription to a preceding snapshot reservation. */
80
+ taskID: v.Type<string>;
81
+ /** Subscriber id (debugging only). */
82
+ id: v.Type<string>;
83
+ /** 'serving' (user-facing) or 'backup' (in-RM replicator). */
84
+ mode: v.UnionType<[v.Type<"serving">, v.Type<"backup">]>;
85
+ /** The replica version the subscriber restored / is running. */
86
+ replicaVersion: v.Type<string>;
87
+ /** The watermark up to which the subscriber is up to date. */
88
+ watermark: v.Type<string>;
89
+ }, undefined>]>]>;
90
+ export type SubscribeUpstream = v.Infer<typeof subscribeUpstreamSchema>;
91
+ /**
92
+ * The reservation confirmation. Carries the same payload as the legacy
93
+ * `/snapshot` status (`backupURL`, `replicaVersion`, `minWatermark`,
94
+ * `replicaSize?`) but under a distinct `'reserved'` tag so it does not collide
95
+ * with the change stream's `['status', …]` message.
96
+ */
97
+ export declare const reservedMessageSchema: v.TupleType<[v.Type<"reserved">, v.ObjectType<Omit<{
98
+ tag: v.Type<"status">;
99
+ backupURL: v.Type<string>;
100
+ replicaVersion: v.Type<string>;
101
+ minWatermark: v.Type<string>;
102
+ replicaSize: v.Optional<number>;
103
+ }, "tag"> & {
104
+ tag: v.Type<"snapshot">;
105
+ }, undefined>]>;
106
+ export type ReservedMessage = v.Infer<typeof reservedMessageSchema>;
107
+ export declare const subscribeDownstreamSchema: v.UnionType<[v.TupleType<[v.Type<"reserved">, v.ObjectType<Omit<{
108
+ tag: v.Type<"status">;
109
+ backupURL: v.Type<string>;
110
+ replicaVersion: v.Type<string>;
111
+ minWatermark: v.Type<string>;
112
+ replicaSize: v.Optional<number>;
113
+ }, "tag"> & {
114
+ tag: v.Type<"snapshot">;
115
+ }, undefined>]>, v.UnionType<[v.TupleType<[v.Type<"status">, v.ObjectType<{
116
+ tag: v.Type<"status">;
117
+ lagReport: v.Optional<{
118
+ lastTimings?: {
119
+ sendTimeMs: number;
120
+ commitTimeMs: number;
121
+ receiveTimeMs: number;
122
+ } | undefined;
123
+ nextSendTimeMs: number;
124
+ }>;
125
+ }, undefined>]>, v.UnionType<[v.TupleType<[v.Type<"begin">, v.ObjectType<{
126
+ tag: v.Type<"begin">;
127
+ json: v.Optional<"p" | "s">;
128
+ skipAck: v.Optional<boolean>;
129
+ }, undefined>, v.ObjectType<{
130
+ commitWatermark: v.Type<string>;
131
+ }, undefined>]>, v.TupleType<[v.Type<"data">, v.UnionType<[v.UnionType<[v.ObjectType<{
132
+ tag: v.Type<"insert">;
133
+ relation: v.Type<{
134
+ schema: string;
135
+ name: string;
136
+ keyColumns?: string[] | undefined;
137
+ replicaIdentity?: "default" | "full" | "index" | "nothing" | undefined;
138
+ rowKey: {
139
+ columns: string[];
140
+ type?: "default" | "full" | "index" | "nothing" | undefined;
141
+ };
142
+ }>;
143
+ new: v.Type<Record<string, import("../../../../shared/src/bigint-json.ts").JSONValue>>;
144
+ }, undefined>, v.ObjectType<{
145
+ tag: v.Type<"update">;
146
+ relation: v.Type<{
147
+ schema: string;
148
+ name: string;
149
+ keyColumns?: string[] | undefined;
150
+ replicaIdentity?: "default" | "full" | "index" | "nothing" | undefined;
151
+ rowKey: {
152
+ columns: string[];
153
+ type?: "default" | "full" | "index" | "nothing" | undefined;
154
+ };
155
+ }>;
156
+ key: v.Type<Record<string, import("../../../../shared/src/bigint-json.ts").JSONValue> | null>;
157
+ new: v.Type<Record<string, import("../../../../shared/src/bigint-json.ts").JSONValue>>;
158
+ }, undefined>, v.ObjectType<{
159
+ tag: v.Type<"delete">;
160
+ relation: v.Type<{
161
+ schema: string;
162
+ name: string;
163
+ keyColumns?: string[] | undefined;
164
+ replicaIdentity?: "default" | "full" | "index" | "nothing" | undefined;
165
+ rowKey: {
166
+ columns: string[];
167
+ type?: "default" | "full" | "index" | "nothing" | undefined;
168
+ };
169
+ }>;
170
+ key: v.Type<Record<string, import("../../../../shared/src/bigint-json.ts").JSONValue>>;
171
+ }, undefined>, v.ObjectType<{
172
+ tag: v.Type<"truncate">;
173
+ relations: v.ArrayType<v.Type<{
174
+ schema: string;
175
+ name: string;
176
+ keyColumns?: string[] | undefined;
177
+ replicaIdentity?: "default" | "full" | "index" | "nothing" | undefined;
178
+ rowKey: {
179
+ columns: string[];
180
+ type?: "default" | "full" | "index" | "nothing" | undefined;
181
+ };
182
+ }>>;
183
+ }, undefined>, v.ObjectType<{
184
+ tag: v.Type<"backfill">;
185
+ relation: v.ObjectType<{
186
+ schema: v.Type<string>;
187
+ name: v.Type<string>;
188
+ rowKey: v.ObjectType<{
189
+ columns: v.ArrayType<v.Type<string>>;
190
+ type: v.Optional<"default" | "full" | "index" | "nothing">;
191
+ }, undefined>;
192
+ }, undefined>;
193
+ columns: v.ArrayType<v.Type<string>>;
194
+ watermark: v.Type<string>;
195
+ rowValues: v.ArrayType<v.ArrayType<v.Type<import("../../../../shared/src/bigint-json.ts").JSONValue>>>;
196
+ status: v.Optional<{
197
+ rows: number;
198
+ totalRows: number;
199
+ totalBytes?: number | undefined;
200
+ }>;
201
+ }, undefined>]>, v.UnionType<[v.ObjectType<{
202
+ tag: v.Type<"create-table">;
203
+ spec: v.ObjectType<Omit<{
204
+ name: v.Type<string>;
205
+ columns: v.Type<Record<string, {
206
+ pos: number;
207
+ dataType: string;
208
+ pgTypeClass?: "b" | "c" | "d" | "e" | "m" | "p" | "r" | undefined;
209
+ elemPgTypeClass?: "b" | "c" | "d" | "e" | "m" | "p" | "r" | null | undefined;
210
+ characterMaximumLength?: number | null | undefined;
211
+ notNull?: boolean | null | undefined;
212
+ dflt?: string | null | undefined;
213
+ }>>;
214
+ primaryKey: v.Optional<string[]>;
215
+ }, "schema"> & {
216
+ schema: v.Type<string>;
217
+ }, undefined>;
218
+ metadata: v.Optional<{
219
+ [x: string]: import("../../../../shared/src/bigint-json.ts").JSONValue;
220
+ rowKey: Record<string, import("../../../../shared/src/bigint-json.ts").JSONValue>;
221
+ }>;
222
+ backfill: v.Optional<Record<string, Record<string, import("../change-source/protocol/current.ts").JSONValue | undefined>>>;
223
+ }, undefined>, v.ObjectType<{
224
+ tag: v.Type<"rename-table">;
225
+ old: v.ObjectType<{
226
+ schema: v.Type<string>;
227
+ name: v.Type<string>;
228
+ }, undefined>;
229
+ new: v.ObjectType<{
230
+ schema: v.Type<string>;
231
+ name: v.Type<string>;
232
+ }, undefined>;
233
+ }, undefined>, v.ObjectType<{
234
+ tag: v.Type<"update-table-metadata">;
235
+ table: v.ObjectType<{
236
+ schema: v.Type<string>;
237
+ name: v.Type<string>;
238
+ }, undefined>;
239
+ old: v.ObjectType<{
240
+ rowKey: v.Type<Record<string, import("../../../../shared/src/bigint-json.ts").JSONValue>>;
241
+ }, v.Type<import("../../../../shared/src/bigint-json.ts").JSONValue>>;
242
+ new: v.ObjectType<{
243
+ rowKey: v.Type<Record<string, import("../../../../shared/src/bigint-json.ts").JSONValue>>;
244
+ }, v.Type<import("../../../../shared/src/bigint-json.ts").JSONValue>>;
245
+ }, undefined>, v.ObjectType<{
246
+ tag: v.Type<"add-column">;
247
+ table: v.ObjectType<{
248
+ schema: v.Type<string>;
249
+ name: v.Type<string>;
250
+ }, undefined>;
251
+ column: v.ObjectType<{
252
+ name: v.Type<string>;
253
+ spec: v.ObjectType<{
254
+ pos: v.Type<number>;
255
+ dataType: v.Type<string>;
256
+ pgTypeClass: v.Optional<"b" | "c" | "d" | "e" | "m" | "p" | "r">;
257
+ elemPgTypeClass: v.Optional<"b" | "c" | "d" | "e" | "m" | "p" | "r" | null>;
258
+ characterMaximumLength: v.Optional<number | null>;
259
+ notNull: v.Optional<boolean | null>;
260
+ dflt: v.Optional<string | null>;
261
+ }, undefined>;
262
+ }, undefined>;
263
+ tableMetadata: v.Optional<{
264
+ [x: string]: import("../../../../shared/src/bigint-json.ts").JSONValue;
265
+ rowKey: Record<string, import("../../../../shared/src/bigint-json.ts").JSONValue>;
266
+ }>;
267
+ backfill: v.Optional<Record<string, import("../change-source/protocol/current.ts").JSONValue | undefined>>;
268
+ }, undefined>, v.ObjectType<{
269
+ tag: v.Type<"update-column">;
270
+ table: v.ObjectType<{
271
+ schema: v.Type<string>;
272
+ name: v.Type<string>;
273
+ }, undefined>;
274
+ old: v.ObjectType<{
275
+ name: v.Type<string>;
276
+ spec: v.ObjectType<{
277
+ pos: v.Type<number>;
278
+ dataType: v.Type<string>;
279
+ pgTypeClass: v.Optional<"b" | "c" | "d" | "e" | "m" | "p" | "r">;
280
+ elemPgTypeClass: v.Optional<"b" | "c" | "d" | "e" | "m" | "p" | "r" | null>;
281
+ characterMaximumLength: v.Optional<number | null>;
282
+ notNull: v.Optional<boolean | null>;
283
+ dflt: v.Optional<string | null>;
284
+ }, undefined>;
285
+ }, undefined>;
286
+ new: v.ObjectType<{
287
+ name: v.Type<string>;
288
+ spec: v.ObjectType<{
289
+ pos: v.Type<number>;
290
+ dataType: v.Type<string>;
291
+ pgTypeClass: v.Optional<"b" | "c" | "d" | "e" | "m" | "p" | "r">;
292
+ elemPgTypeClass: v.Optional<"b" | "c" | "d" | "e" | "m" | "p" | "r" | null>;
293
+ characterMaximumLength: v.Optional<number | null>;
294
+ notNull: v.Optional<boolean | null>;
295
+ dflt: v.Optional<string | null>;
296
+ }, undefined>;
297
+ }, undefined>;
298
+ }, undefined>, v.ObjectType<{
299
+ tag: v.Type<"drop-column">;
300
+ table: v.ObjectType<{
301
+ schema: v.Type<string>;
302
+ name: v.Type<string>;
303
+ }, undefined>;
304
+ column: v.Type<string>;
305
+ }, undefined>, v.ObjectType<{
306
+ tag: v.Type<"drop-table">;
307
+ id: v.ObjectType<{
308
+ schema: v.Type<string>;
309
+ name: v.Type<string>;
310
+ }, undefined>;
311
+ }, undefined>, v.ObjectType<{
312
+ tag: v.Type<"create-index">;
313
+ spec: v.ObjectType<Omit<{
314
+ name: v.Type<string>;
315
+ tableName: v.Type<string>;
316
+ unique: v.Type<boolean>;
317
+ columns: v.Type<Record<string, "ASC" | "DESC">>;
318
+ predicate: v.Optional<import("../../db/specs.ts").IndexPredicate>;
319
+ }, "schema"> & {
320
+ schema: v.Type<string>;
321
+ }, undefined>;
322
+ }, undefined>, v.ObjectType<{
323
+ tag: v.Type<"drop-index">;
324
+ id: v.ObjectType<{
325
+ schema: v.Type<string>;
326
+ name: v.Type<string>;
327
+ }, undefined>;
328
+ }, undefined>, v.ObjectType<{
329
+ tag: v.Type<"backfill-completed">;
330
+ relation: v.ObjectType<{
331
+ schema: v.Type<string>;
332
+ name: v.Type<string>;
333
+ rowKey: v.ObjectType<{
334
+ columns: v.ArrayType<v.Type<string>>;
335
+ type: v.Optional<"default" | "full" | "index" | "nothing">;
336
+ }, undefined>;
337
+ }, undefined>;
338
+ columns: v.ArrayType<v.Type<string>>;
339
+ watermark: v.Type<string>;
340
+ status: v.Optional<{
341
+ rows: number;
342
+ totalRows: number;
343
+ totalBytes?: number | undefined;
344
+ }>;
345
+ }, undefined>]>]>]>, v.TupleType<[v.Type<"commit">, v.ObjectType<{
346
+ tag: v.Type<"commit">;
347
+ commitTimeMs: v.Optional<number>;
348
+ }, undefined>, v.ObjectType<{
349
+ watermark: v.Type<string>;
350
+ }, undefined>]>, v.TupleType<[v.Type<"rollback">, v.ObjectType<{
351
+ tag: v.Type<"rollback">;
352
+ }, undefined>]>]>, v.TupleType<[v.Type<"error">, v.ObjectType<{
353
+ type: v.Type<number>;
354
+ message: v.Optional<string>;
355
+ }, undefined>]>]>]>;
356
+ export type SubscribeDownstream = v.Infer<typeof subscribeDownstreamSchema>;
357
+ export {};
358
+ //# sourceMappingURL=subscribe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscribe.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/subscribe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,CAAC,MAAM,kCAAkC,CAAC;AAItD,QAAA,MAAM,oBAAoB;;;;;;;;aAExB,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAMlE,eAAO,MAAM,4BAA4B;;eAGvC,CAAC;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB;IACjC,2EAA2E;;IAE3E,sCAAsC;;IAEtC,8DAA8D;;IAE9D,gEAAgE;;IAEhE,8DAA8D;;aAE9D,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,eAAO,MAAM,8BAA8B;IAdzC,2EAA2E;;IAE3E,sCAAsC;;IAEtC,8DAA8D;;IAE9D,gEAAgE;;IAEhE,8DAA8D;;eAS9D,CAAC;AAEH,eAAO,MAAM,uBAAuB;;;IAnBlC,2EAA2E;;IAE3E,sCAAsC;;IAEtC,8DAA8D;;IAE9D,gEAAgE;;IAEhE,8DAA8D;;iBAc/D,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAMxE;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;eAGhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAGrC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
@@ -0,0 +1,68 @@
1
+ import { valita_exports } from "../../../../shared/src/valita.js";
2
+ import { downstreamSchema } from "./change-streamer.js";
3
+ import { statusSchema } from "./snapshot-message.js";
4
+ //#region ../zero-cache/src/services/change-streamer/subscribe.ts
5
+ /**
6
+ * The `subscribe` API (protocol v7+) merges the previous `/snapshot` and
7
+ * `/changes` connections into a single bidirectional WebSocket, so that the
8
+ * replication-manager that reserves change-log entries for a soon-to-be
9
+ * subscriber is the same replication-manager that serves the subscription.
10
+ *
11
+ * The connection is driven by an upstream (view-syncer → replication-manager)
12
+ * application message that selects the flow:
13
+ *
14
+ * - **Fresh startup:** the view-syncer sends `['reserve-snapshot', {taskID}]`.
15
+ * The change-streamer opens a snapshot reservation (pinning the change log)
16
+ * and replies downstream with a single `['reserved', …]` message telling the
17
+ * view-syncer where to restore the backup from and the watermark from which
18
+ * catchup is possible. Once the backup is restored, the view-syncer sends
19
+ * `['start-subscription', ctx]`, which transitions the same connection into
20
+ * the subscription phase (change stream downstream). The reservation is
21
+ * released only after the subscriber is registered, so there is never a gap
22
+ * in which the change-streamer does not know the client's watermark.
23
+ *
24
+ * - **Reconnect (replica already valid):** the view-syncer sends
25
+ * `['start-subscription', ctx]` immediately, with no reservation.
26
+ *
27
+ * The reservation status uses a distinct `'reserved'` tag (rather than the
28
+ * change stream's `'status'`) so the downstream union is unambiguous under
29
+ * passthrough parsing.
30
+ */
31
+ var snapshotStatusSchema = statusSchema.extend({ tag: valita_exports.literal("snapshot") });
32
+ var reserveSnapshotMessageSchema = valita_exports.tuple([valita_exports.literal("reserve-snapshot"), valita_exports.object({ taskID: valita_exports.string() })]);
33
+ /**
34
+ * The subscription-initialization context, sent in the `start-subscription`
35
+ * message. Equivalent to the previous URL-encoded `SubscriberContext`, minus
36
+ * the fields that are now implicit on the merged connection:
37
+ * - `protocolVersion` comes from the request path.
38
+ * - `wsBatched` is always on (the transport batches internally).
39
+ *
40
+ * Parsed in `passthrough` mode so a forthcoming, orthogonal change can add
41
+ * arbitrary table/column/backfill state without a protocol bump.
42
+ */
43
+ var subscribeContextSchema = valita_exports.object({
44
+ /** Task ID, links the subscription to a preceding snapshot reservation. */
45
+ taskID: valita_exports.string(),
46
+ /** Subscriber id (debugging only). */
47
+ id: valita_exports.string(),
48
+ /** 'serving' (user-facing) or 'backup' (in-RM replicator). */
49
+ mode: valita_exports.union(valita_exports.literal("serving"), valita_exports.literal("backup")),
50
+ /** The replica version the subscriber restored / is running. */
51
+ replicaVersion: valita_exports.string(),
52
+ /** The watermark up to which the subscriber is up to date. */
53
+ watermark: valita_exports.string()
54
+ });
55
+ var startSubscriptionMessageSchema = valita_exports.tuple([valita_exports.literal("start-subscription"), subscribeContextSchema]);
56
+ var subscribeUpstreamSchema = valita_exports.union(reserveSnapshotMessageSchema, startSubscriptionMessageSchema);
57
+ /**
58
+ * The reservation confirmation. Carries the same payload as the legacy
59
+ * `/snapshot` status (`backupURL`, `replicaVersion`, `minWatermark`,
60
+ * `replicaSize?`) but under a distinct `'reserved'` tag so it does not collide
61
+ * with the change stream's `['status', …]` message.
62
+ */
63
+ var reservedMessageSchema = valita_exports.tuple([valita_exports.literal("reserved"), snapshotStatusSchema]);
64
+ var subscribeDownstreamSchema = valita_exports.union(reservedMessageSchema, downstreamSchema);
65
+ //#endregion
66
+ export { subscribeDownstreamSchema, subscribeUpstreamSchema };
67
+
68
+ //# sourceMappingURL=subscribe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscribe.js","names":[],"sources":["../../../../../../zero-cache/src/services/change-streamer/subscribe.ts"],"sourcesContent":["/**\n * The `subscribe` API (protocol v7+) merges the previous `/snapshot` and\n * `/changes` connections into a single bidirectional WebSocket, so that the\n * replication-manager that reserves change-log entries for a soon-to-be\n * subscriber is the same replication-manager that serves the subscription.\n *\n * The connection is driven by an upstream (view-syncer → replication-manager)\n * application message that selects the flow:\n *\n * - **Fresh startup:** the view-syncer sends `['reserve-snapshot', {taskID}]`.\n * The change-streamer opens a snapshot reservation (pinning the change log)\n * and replies downstream with a single `['reserved', …]` message telling the\n * view-syncer where to restore the backup from and the watermark from which\n * catchup is possible. Once the backup is restored, the view-syncer sends\n * `['start-subscription', ctx]`, which transitions the same connection into\n * the subscription phase (change stream downstream). The reservation is\n * released only after the subscriber is registered, so there is never a gap\n * in which the change-streamer does not know the client's watermark.\n *\n * - **Reconnect (replica already valid):** the view-syncer sends\n * `['start-subscription', ctx]` immediately, with no reservation.\n *\n * The reservation status uses a distinct `'reserved'` tag (rather than the\n * change stream's `'status'`) so the downstream union is unambiguous under\n * passthrough parsing.\n */\n\nimport * as v from '../../../../shared/src/valita.ts';\nimport {downstreamSchema} from './change-streamer.ts';\nimport {statusSchema} from './snapshot-message.ts';\n\nconst snapshotStatusSchema = statusSchema.extend({\n tag: v.literal('snapshot'), // Replace tag: 'status' with tag: 'snapshot'\n});\n\nexport type SnapshotStatus = v.Infer<typeof snapshotStatusSchema>;\n\n// ---------------------------------------------------------------------------\n// Upstream (view-syncer → replication-manager) — application control messages.\n// ---------------------------------------------------------------------------\n\nexport const reserveSnapshotMessageSchema = v.tuple([\n v.literal('reserve-snapshot'),\n v.object({taskID: v.string()}),\n]);\n\n/**\n * The subscription-initialization context, sent in the `start-subscription`\n * message. Equivalent to the previous URL-encoded `SubscriberContext`, minus\n * the fields that are now implicit on the merged connection:\n * - `protocolVersion` comes from the request path.\n * - `wsBatched` is always on (the transport batches internally).\n *\n * Parsed in `passthrough` mode so a forthcoming, orthogonal change can add\n * arbitrary table/column/backfill state without a protocol bump.\n */\nexport const subscribeContextSchema = v.object({\n /** Task ID, links the subscription to a preceding snapshot reservation. */\n taskID: v.string(),\n /** Subscriber id (debugging only). */\n id: v.string(),\n /** 'serving' (user-facing) or 'backup' (in-RM replicator). */\n mode: v.union(v.literal('serving'), v.literal('backup')),\n /** The replica version the subscriber restored / is running. */\n replicaVersion: v.string(),\n /** The watermark up to which the subscriber is up to date. */\n watermark: v.string(),\n});\n\nexport type SubscribeContext = v.Infer<typeof subscribeContextSchema>;\n\nexport const startSubscriptionMessageSchema = v.tuple([\n v.literal('start-subscription'),\n subscribeContextSchema,\n]);\n\nexport const subscribeUpstreamSchema = v.union(\n reserveSnapshotMessageSchema,\n startSubscriptionMessageSchema,\n);\n\nexport type SubscribeUpstream = v.Infer<typeof subscribeUpstreamSchema>;\n\n// ---------------------------------------------------------------------------\n// Downstream (replication-manager → view-syncer).\n// ---------------------------------------------------------------------------\n\n/**\n * The reservation confirmation. Carries the same payload as the legacy\n * `/snapshot` status (`backupURL`, `replicaVersion`, `minWatermark`,\n * `replicaSize?`) but under a distinct `'reserved'` tag so it does not collide\n * with the change stream's `['status', …]` message.\n */\nexport const reservedMessageSchema = v.tuple([\n v.literal('reserved'),\n snapshotStatusSchema,\n]);\n\nexport type ReservedMessage = v.Infer<typeof reservedMessageSchema>;\n\nexport const subscribeDownstreamSchema = v.union(\n reservedMessageSchema,\n downstreamSchema,\n);\n\nexport type SubscribeDownstream = v.Infer<typeof subscribeDownstreamSchema>;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,IAAM,uBAAuB,aAAa,OAAO,EAC/C,KAAK,eAAE,QAAQ,UAAU,EAC3B,CAAC;AAQD,IAAa,+BAA+B,eAAE,MAAM,CAClD,eAAE,QAAQ,kBAAkB,GAC5B,eAAE,OAAO,EAAC,QAAQ,eAAE,OAAO,EAAC,CAAC,CAC/B,CAAC;;;;;;;;;;;AAYD,IAAa,yBAAyB,eAAE,OAAO;;CAE7C,QAAQ,eAAE,OAAO;;CAEjB,IAAI,eAAE,OAAO;;CAEb,MAAM,eAAE,MAAM,eAAE,QAAQ,SAAS,GAAG,eAAE,QAAQ,QAAQ,CAAC;;CAEvD,gBAAgB,eAAE,OAAO;;CAEzB,WAAW,eAAE,OAAO;AACtB,CAAC;AAID,IAAa,iCAAiC,eAAE,MAAM,CACpD,eAAE,QAAQ,oBAAoB,GAC9B,sBACF,CAAC;AAED,IAAa,0BAA0B,eAAE,MACvC,8BACA,8BACF;;;;;;;AAcA,IAAa,wBAAwB,eAAE,MAAM,CAC3C,eAAE,QAAQ,UAAU,GACpB,oBACF,CAAC;AAID,IAAa,4BAA4B,eAAE,MACzC,uBACA,gBACF"}
@@ -7,7 +7,7 @@ export type ReplicaConstraints = {
7
7
  minWatermark: string;
8
8
  };
9
9
  export type RestoreResult = 'success' | 'no_backup' | 'invalid_replica' | 'error';
10
- type RestoreAttempt = {
10
+ export type RestoreAttempt = {
11
11
  restored: boolean;
12
12
  backupURL: string | undefined;
13
13
  result: RestoreResult;
@@ -16,7 +16,7 @@ export declare class BackupNotFoundException extends Error {
16
16
  static readonly name = "BackupNotFoundException";
17
17
  constructor(backupURL: string | undefined);
18
18
  }
19
- export declare function tryRestore(lc: LogContext, config: LitestreamConfig, replicaFile: string, replicaConstraints: ReplicaConstraints | undefined, role: LitestreamRole): Promise<RestoreAttempt>;
19
+ export declare function tryRestore(lc: LogContext, config: LitestreamConfig, replicaFile: string, replicaConstraints: ReplicaConstraints | undefined, role: LitestreamRole, signal?: AbortSignal): Promise<RestoreAttempt>;
20
20
  export declare function startReplicaBackupProcess(lc: LogContext, config: LitestreamConfig, replicaFile: string): ChildProcess;
21
21
  /**
22
22
  * Returns the time of the most recent object (snapshot or WAL segment)
@@ -41,5 +41,4 @@ export declare function getLastBackupTime(lc: LogContext, config: LitestreamConf
41
41
  * Exported for testing.
42
42
  */
43
43
  export declare function parseBackupCreatedTimes(lc: LogContext, command: 'snapshots' | 'wal', output: string): Date[];
44
- export {};
45
44
  //# sourceMappingURL=commands.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/litestream/commands.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAIrD,OAAO,KAAK,EAAC,UAAU,EAAW,MAAM,kBAAkB,CAAC;AAI3D,OAAO,EAAC,KAAK,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AAUhE,OAAO,EAWL,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AAEtB,MAAM,MAAM,kBAAkB,GAAG;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,WAAW,GACX,iBAAiB,GACjB,OAAO,CAAC;AAEZ,KAAK,cAAc,GAAG;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,MAAM,EAAE,aAAa,CAAC;CACvB,CAAC;AAKF,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,MAAM,CAAC,QAAQ,CAAC,IAAI,6BAA6B;IAEjD,YAAY,SAAS,EAAE,MAAM,GAAG,SAAS,EAExC;CACF;AAyFD,wBAAsB,UAAU,CAC9B,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,EACnB,kBAAkB,EAAE,kBAAkB,GAAG,SAAS,EAClD,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,cAAc,CAAC,CAwIzB;AAoGD,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,GAClB,YAAY,CAuCd;AAQD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAgBf;AA8DD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,WAAW,GAAG,KAAK,EAC5B,MAAM,EAAE,MAAM,GACb,IAAI,EAAE,CAoBR"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/litestream/commands.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAIrD,OAAO,KAAK,EAAC,UAAU,EAAW,MAAM,kBAAkB,CAAC;AAI3D,OAAO,EAAC,KAAK,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AAUhE,OAAO,EAWL,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AAEtB,MAAM,MAAM,kBAAkB,GAAG;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,WAAW,GACX,iBAAiB,GACjB,OAAO,CAAC;AAEZ,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,MAAM,EAAE,aAAa,CAAC;CACvB,CAAC;AAKF,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,MAAM,CAAC,QAAQ,CAAC,IAAI,6BAA6B;IAEjD,YAAY,SAAS,EAAE,MAAM,GAAG,SAAS,EAExC;CACF;AAyFD,wBAAsB,UAAU,CAC9B,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,EACnB,kBAAkB,EAAE,kBAAkB,GAAG,SAAS,EAClD,IAAI,EAAE,cAAc,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CAwIzB;AAoGD,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,GAClB,YAAY,CAuCd;AAQD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAgBf;AA8DD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,WAAW,GAAG,KAAK,EAC5B,MAAM,EAAE,MAAM,GACb,IAAI,EAAE,CAoBR"}
@@ -47,7 +47,7 @@ function getLitestream(mode, config, replicaFile, logLevelOverride) {
47
47
  }
48
48
  };
49
49
  }
50
- async function tryRestore(lc, config, replicaFile, replicaConstraints, role) {
50
+ async function tryRestore(lc, config, replicaFile, replicaConstraints, role, signal) {
51
51
  const { backupURL } = config;
52
52
  const attrs = litestreamRestoreMetricAttrs(config, role, backupURL);
53
53
  let result = "error";
@@ -76,7 +76,8 @@ async function tryRestore(lc, config, replicaFile, replicaConstraints, role) {
76
76
  "pipe",
77
77
  "pipe"
78
78
  ],
79
- windowsHide: true
79
+ windowsHide: true,
80
+ signal
80
81
  });
81
82
  let stdout = "";
82
83
  let stderr = "";