@rocicorp/zero 0.26.0 → 0.26.1-canary.10

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 (129) hide show
  1. package/out/analyze-query/src/run-ast.d.ts.map +1 -1
  2. package/out/analyze-query/src/run-ast.js +4 -1
  3. package/out/analyze-query/src/run-ast.js.map +1 -1
  4. package/out/replicache/src/btree/node.js +4 -4
  5. package/out/replicache/src/btree/node.js.map +1 -1
  6. package/out/replicache/src/btree/write.js +2 -2
  7. package/out/replicache/src/btree/write.js.map +1 -1
  8. package/out/replicache/src/dag/gc.js +5 -2
  9. package/out/replicache/src/dag/gc.js.map +1 -1
  10. package/out/replicache/src/db/write.d.ts.map +1 -1
  11. package/out/replicache/src/db/write.js +21 -6
  12. package/out/replicache/src/db/write.js.map +1 -1
  13. package/out/replicache/src/error-responses.d.ts.map +1 -1
  14. package/out/replicache/src/error-responses.js +4 -1
  15. package/out/replicache/src/error-responses.js.map +1 -1
  16. package/out/replicache/src/persist/clients.d.ts.map +1 -1
  17. package/out/replicache/src/persist/clients.js +4 -1
  18. package/out/replicache/src/persist/clients.js.map +1 -1
  19. package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
  20. package/out/replicache/src/persist/collect-idb-databases.js +2 -1
  21. package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
  22. package/out/replicache/src/persist/idb-databases-store.d.ts.map +1 -1
  23. package/out/replicache/src/persist/idb-databases-store.js +4 -1
  24. package/out/replicache/src/persist/idb-databases-store.js.map +1 -1
  25. package/out/replicache/src/process-scheduler.js +4 -1
  26. package/out/replicache/src/process-scheduler.js.map +1 -1
  27. package/out/replicache/src/replicache-impl.js +2 -2
  28. package/out/replicache/src/replicache-impl.js.map +1 -1
  29. package/out/replicache/src/subscriptions.d.ts.map +1 -1
  30. package/out/replicache/src/subscriptions.js +5 -2
  31. package/out/replicache/src/subscriptions.js.map +1 -1
  32. package/out/replicache/src/sync/diff.d.ts.map +1 -1
  33. package/out/replicache/src/sync/diff.js +4 -1
  34. package/out/replicache/src/sync/diff.js.map +1 -1
  35. package/out/replicache/src/sync/pull.d.ts.map +1 -1
  36. package/out/replicache/src/sync/pull.js +4 -1
  37. package/out/replicache/src/sync/pull.js.map +1 -1
  38. package/out/replicache/src/sync/push.d.ts.map +1 -1
  39. package/out/replicache/src/sync/push.js +5 -2
  40. package/out/replicache/src/sync/push.js.map +1 -1
  41. package/out/shared/src/asserts.d.ts +1 -1
  42. package/out/shared/src/asserts.d.ts.map +1 -1
  43. package/out/shared/src/asserts.js +1 -1
  44. package/out/shared/src/asserts.js.map +1 -1
  45. package/out/z2s/src/compiler.d.ts.map +1 -1
  46. package/out/z2s/src/compiler.js +8 -2
  47. package/out/z2s/src/compiler.js.map +1 -1
  48. package/out/zero/package.json.js +1 -1
  49. package/out/zero-cache/src/db/transaction-pool.d.ts.map +1 -1
  50. package/out/zero-cache/src/db/transaction-pool.js +17 -11
  51. package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
  52. package/out/zero-cache/src/observability/events.d.ts.map +1 -1
  53. package/out/zero-cache/src/observability/events.js +28 -9
  54. package/out/zero-cache/src/observability/events.js.map +1 -1
  55. package/out/zero-cache/src/services/analyze.js +1 -0
  56. package/out/zero-cache/src/services/analyze.js.map +1 -1
  57. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
  58. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +29 -14
  59. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  60. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +6 -1
  61. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  62. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +69 -25
  63. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  64. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
  65. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +6 -1
  66. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  67. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  68. package/out/zero-cache/src/services/change-source/pg/schema/init.js +12 -8
  69. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  70. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +26 -0
  71. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
  72. package/out/zero-cache/src/services/change-source/protocol/current/data.js +15 -3
  73. package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
  74. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +30 -0
  75. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
  76. package/out/zero-cache/src/services/change-source/protocol/current.js +2 -1
  77. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  78. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +8 -2
  79. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  80. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +10 -0
  81. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  82. package/out/zero-cache/src/services/replicator/change-processor.d.ts +2 -0
  83. package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
  84. package/out/zero-cache/src/services/replicator/change-processor.js +8 -6
  85. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  86. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
  87. package/out/zero-cache/src/services/replicator/incremental-sync.js +39 -1
  88. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  89. package/out/zero-cache/src/services/replicator/replication-status.d.ts +4 -3
  90. package/out/zero-cache/src/services/replicator/replication-status.d.ts.map +1 -1
  91. package/out/zero-cache/src/services/replicator/replication-status.js +25 -10
  92. package/out/zero-cache/src/services/replicator/replication-status.js.map +1 -1
  93. package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
  94. package/out/zero-cache/src/services/run-ast.js +22 -2
  95. package/out/zero-cache/src/services/run-ast.js.map +1 -1
  96. package/out/zero-cache/src/services/running-state.d.ts +1 -0
  97. package/out/zero-cache/src/services/running-state.d.ts.map +1 -1
  98. package/out/zero-cache/src/services/running-state.js +4 -0
  99. package/out/zero-cache/src/services/running-state.js.map +1 -1
  100. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  101. package/out/zero-cache/src/services/view-syncer/cvr.js +8 -2
  102. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  103. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  104. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +10 -1
  105. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  106. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +1 -1
  107. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  108. package/out/zero-cache/src/services/view-syncer/snapshotter.js +15 -7
  109. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  110. package/out/zero-cache/src/types/subscription.d.ts +3 -1
  111. package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
  112. package/out/zero-cache/src/types/subscription.js +29 -9
  113. package/out/zero-cache/src/types/subscription.js.map +1 -1
  114. package/out/zero-client/src/client/http-string.js.map +1 -1
  115. package/out/zero-client/src/client/version.js +1 -1
  116. package/out/zero-client/src/client/zero.js.map +1 -1
  117. package/out/zero-events/src/status.d.ts +8 -0
  118. package/out/zero-events/src/status.d.ts.map +1 -1
  119. package/out/zero-schema/src/permissions.d.ts.map +1 -1
  120. package/out/zero-schema/src/permissions.js +4 -1
  121. package/out/zero-schema/src/permissions.js.map +1 -1
  122. package/out/zero-server/src/process-mutations.d.ts.map +1 -1
  123. package/out/zero-server/src/process-mutations.js +13 -19
  124. package/out/zero-server/src/process-mutations.js.map +1 -1
  125. package/out/zql/src/builder/filter.d.ts.map +1 -1
  126. package/out/zql/src/builder/filter.js +5 -2
  127. package/out/zql/src/builder/filter.js.map +1 -1
  128. package/out/zql/src/ivm/constraint.js.map +1 -1
  129. package/package.json +1 -1
@@ -3,7 +3,7 @@ import { must } from "../../../../../../shared/src/must.js";
3
3
  import { literalUnion } from "../../../../../../shared/src/valita.js";
4
4
  import { tableSpec, columnSpec, indexSpec } from "../../../../db/specs.js";
5
5
  import { jsonObjectSchema } from "./json.js";
6
- import { object, boolean, literal, array, string, record, union } from "@badrap/valita";
6
+ import { object, boolean, literal, array, string, record, number, union } from "@badrap/valita";
7
7
  const beginSchema = object({
8
8
  tag: literal("begin"),
9
9
  // The format of values of "json"-typed columns (e.g. "JSON" and "JSONB").
@@ -173,6 +173,11 @@ const dropIndexSchema = object({
173
173
  tag: literal("drop-index"),
174
174
  id: identifierSchema
175
175
  });
176
+ const downloadStatusSchema = object({
177
+ rows: number(),
178
+ totalRows: number(),
179
+ totalBytes: number().optional()
180
+ });
176
181
  const backfillSchema = object({
177
182
  tag: literal("backfill"),
178
183
  relation: newRelationSchema,
@@ -194,7 +199,10 @@ const backfillSchema = object({
194
199
  // [...rowKeyValues, ...columnValues], // row 2
195
200
  // ]
196
201
  // ```
197
- rowValues: array(array(jsonValueSchema))
202
+ rowValues: array(array(jsonValueSchema)),
203
+ // Optionally includes the progress of the backfill operation,
204
+ // for display purposes.
205
+ status: downloadStatusSchema.optional()
198
206
  });
199
207
  const backfillCompletedSchema = object({
200
208
  tag: literal("backfill-completed"),
@@ -206,7 +214,10 @@ const backfillCompletedSchema = object({
206
214
  // generally will be different from the commit watermarks of the main change
207
215
  // stream, and in particular, the commit watermark of the backfill change's
208
216
  // enclosing transaction.
209
- watermark: string()
217
+ watermark: string(),
218
+ // Optionally includes the final status of the backfill operation,
219
+ // for display purposes.
220
+ status: downloadStatusSchema.optional()
210
221
  });
211
222
  const dataChangeSchema = union(
212
223
  insertSchema,
@@ -268,6 +279,7 @@ export {
268
279
  createTableSchema,
269
280
  dataChangeSchema,
270
281
  deleteSchema,
282
+ downloadStatusSchema,
271
283
  dropColumnSchema,
272
284
  dropIndexSchema,
273
285
  dropTableSchema,
@@ -1 +1 @@
1
- {"version":3,"file":"data.js","sources":["../../../../../../../../zero-cache/src/services/change-source/protocol/current/data.ts"],"sourcesContent":["/**\n * Data plane messages encapsulate changes that are sent by ChangeSources,\n * forwarded / fanned out to subscribers by the ChangeStreamerService, and\n * stored in the Change DB for catchup of old subscribers.\n */\n\nimport {\n jsonValueSchema,\n type JSONObject,\n} from '../../../../../../shared/src/bigint-json.ts';\nimport {must} from '../../../../../../shared/src/must.ts';\nimport * as v from '../../../../../../shared/src/valita.ts';\nimport {columnSpec, indexSpec, tableSpec} from '../../../../db/specs.ts';\nimport type {Satisfies} from '../../../../types/satisfies.ts';\nimport {jsonObjectSchema} from './json.ts';\n\nexport const beginSchema = v.object({\n tag: v.literal('begin'),\n // The format of values of \"json\"-typed columns (e.g. \"JSON\" and \"JSONB\").\n // - 'p' is for parsed JSON, which may include JSON values or JSON objects.\n // These values are parsed and stringified at every process boundary\n // between the change-source and the replica.\n // - 's' is for stringified JSON. These values skip the parsing and\n // stringification, and are directly ferried to the replica as a JSON\n // string. For JSON values this improves performance by 20~25% in the\n // change-streamer and 25~30% in the replicator.\n //\n // If absent, the format is assumed to be 'p' (parsed JSON objects/values).\n json: v.literalUnion('p', 's').optional(),\n\n // Directs the change-streamer to skip the ACK for the corresponding commit.\n skipAck: v.boolean().optional(),\n});\n\nexport const commitSchema = v.object({\n tag: v.literal('commit'),\n});\n\nexport const rollbackSchema = v.object({\n tag: v.literal('rollback'),\n});\n\nconst rowKeySchema = v.object({\n // The columns used to identify a row in insert, update, and delete changes.\n columns: v.array(v.string()),\n\n // An optional qualifier identifying how the key is chosen. Currently this\n // is postgres-specific, describing the REPLICA IDENTITY, for which replica\n // identity 'full' (FULL) is handled differently; the replicator handles\n // these tables by extracting a row key from the full row based on the\n // table's PRIMARY KEY or UNIQUE INDEX.\n type: v.literalUnion('default', 'nothing', 'full', 'index').optional(),\n});\n\nexport const relationSchema = v\n .object({\n schema: v.string(),\n name: v.string(),\n\n // This will become required.\n rowKey: rowKeySchema.optional(),\n\n /** Deprecated: set the rowKey.columns instead. */\n keyColumns: v.array(v.string()).optional(),\n /** Deprecated: set the rowKey.columns instead. */\n replicaIdentity: v\n .literalUnion('default', 'nothing', 'full', 'index')\n .optional(),\n })\n .map(rel => {\n const {rowKey, ...rest} = rel;\n if (rowKey) {\n return {...rest, rowKey};\n }\n return {\n ...rest,\n rowKey: {\n columns: must(rel.keyColumns),\n type: rel.replicaIdentity,\n },\n };\n });\n\n// The eventual fate of relationSchema\nexport const newRelationSchema = v.object({\n schema: v.string(),\n name: v.string(),\n\n rowKey: rowKeySchema,\n});\n\n// TableMetadata contains table-related configuration that does not affect the\n// actual data in the table, but rather how the table's change messages are\n// handled. The is an opaque object that clients must track (and update) based\n// on `create-table`, `add-column`, and `table-update-metadata` messages, and\n// pass in BackfillRequests when there are columns to be backfilled.\n//\n// Note that the backfill-related change-source implementation does, however,\n// rely on the rowKey (columns) being specified in the message.\nexport const tableMetadataSchema = v\n .object({rowKey: v.record(jsonValueSchema)})\n .rest(jsonValueSchema);\n\nexport type TableMetadata = v.Infer<typeof tableMetadataSchema>;\n\nexport const rowSchema = v.record(jsonValueSchema);\n\nexport const insertSchema = v.object({\n tag: v.literal('insert'),\n relation: relationSchema,\n new: rowSchema,\n});\n\nexport const updateSchema = v.object({\n tag: v.literal('update'),\n relation: relationSchema,\n // `key` is present if the update changed the key of the row, or if the\n // table's replicaIdentity === 'full'\n key: rowSchema.nullable(),\n // `new` is the full row (and not just the updated columns). This is\n // necessary for \"catchup\" replication scenarios such as adding tables\n // to a publication, or resharding.\n new: rowSchema,\n});\n\nexport const deleteSchema = v.object({\n tag: v.literal('delete'),\n relation: relationSchema,\n // key is the full row if replicaIdentity === 'full'\n key: rowSchema,\n});\n\nexport const truncateSchema = v.object({\n tag: v.literal('truncate'),\n relations: v.array(relationSchema),\n});\n\nexport const identifierSchema = v.object({\n schema: v.string(),\n name: v.string(),\n});\n\nexport type Identifier = v.Infer<typeof identifierSchema>;\n\n// A BackfillID is an upstream specific stable identifier for a column\n// that needs backfilling. This id is used to ensure that the schema, table,\n// and column names of a requested backfill still match the original\n// underlying upstream ID.\n//\n// The change-streamer stores these IDs as opaque values while a column is\n// being backfilled, and initiates new change-source streams with the IDs\n// in order to restart backfills that did not complete in previous sessions.\nexport const backfillIDSchema = jsonObjectSchema;\n\nexport type BackfillID = v.Infer<typeof backfillIDSchema>;\n\nexport const createTableSchema = v.object({\n tag: v.literal('create-table'),\n spec: tableSpec,\n\n // This must be set by change source implementations that support\n // table/column backfill.\n //\n // TODO: to simplify the protocol, see if we can make this required\n metadata: tableMetadataSchema.optional(),\n\n // Indicate that columns of the table require backfilling. These columns\n // should be created on the replica but not yet synced the clients.\n //\n // ## State Persistence\n //\n // To obviate the need for change-source implementations to persist state\n // related to backfill progress, the change-source only tracks backfills\n // **for the current session**. In the event that the session is interrupted\n // before columns have been fully backfilled, it is the responsibility of the\n // change-streamer to send {@link BackfillRequest}s when it reconnects.\n //\n // This means that the change-streamer must track and persist:\n // * the backfill IDs of the columns requiring backfilling\n // * the most current table metadata of the associated table(s)\n //\n // The change-streamer then uses this information to send backfill requests\n // when it reconnects.\n backfill: v.record(backfillIDSchema).optional(),\n});\n\nexport const renameTableSchema = v.object({\n tag: v.literal('rename-table'),\n old: identifierSchema,\n new: identifierSchema,\n});\n\nexport const updateTableMetadataSchema = v.object({\n tag: v.literal('update-table-metadata'),\n table: identifierSchema,\n old: tableMetadataSchema,\n new: tableMetadataSchema,\n});\n\nconst columnSchema = v.object({\n name: v.string(),\n spec: columnSpec,\n});\n\nexport const addColumnSchema = v.object({\n tag: v.literal('add-column'),\n table: identifierSchema,\n column: columnSchema,\n\n // This must be set by change source implementations that support\n // table/column backfill.\n //\n // TODO: to simplify the protocol, see if we can make this required\n tableMetadata: tableMetadataSchema.optional(),\n\n // See documentation for the `backfill` field of the `create-table` change.\n backfill: backfillIDSchema.optional(),\n});\n\nexport const updateColumnSchema = v.object({\n tag: v.literal('update-column'),\n table: identifierSchema,\n old: columnSchema,\n new: columnSchema,\n});\n\nexport const dropColumnSchema = v.object({\n tag: v.literal('drop-column'),\n table: identifierSchema,\n column: v.string(),\n});\n\nexport const dropTableSchema = v.object({\n tag: v.literal('drop-table'),\n id: identifierSchema,\n});\n\nexport const createIndexSchema = v.object({\n tag: v.literal('create-index'),\n spec: indexSpec,\n});\n\nexport const dropIndexSchema = v.object({\n tag: v.literal('drop-index'),\n id: identifierSchema,\n});\n\n// A batch of rows from a single table containing column values\n// to be backfilled.\nexport const backfillSchema = v.object({\n tag: v.literal('backfill'),\n\n relation: newRelationSchema,\n\n // The columns to be backfilled. `rowKey` columns are automatically excluded,\n // which means that this field may be empty.\n columns: v.array(v.string()),\n\n // The watermark at which the backfill data was queried. Note that this\n // generally will be different from the commit watermarks of the main change\n // stream, and in particular, the commit watermark of the backfill change's\n // enclosing transaction.\n watermark: v.string(),\n\n // A batch of row values, each row consisting of the `rowKey`\n // values, followed by the `column` values, in the same order in which\n // the column names appear in their respective fields, e.g.\n //\n // ```\n // [\n // [...rowKeyValues, ...columnValues], // row 1\n // [...rowKeyValues, ...columnValues], // row 2\n // ]\n // ```\n rowValues: v.array(v.array(jsonValueSchema)),\n});\n\n// Indicates that the backfill for the specified columns have\n// been successfully backfilled and can be published to clients.\nexport const backfillCompletedSchema = v.object({\n tag: v.literal('backfill-completed'),\n\n relation: newRelationSchema,\n\n // The columns to be backfilled. `rowKey` columns are automatically excluded,\n // which means that this field may be empty.\n columns: v.array(v.string()),\n\n // The watermark at which the backfill data was queried. Note that this\n // generally will be different from the commit watermarks of the main change\n // stream, and in particular, the commit watermark of the backfill change's\n // enclosing transaction.\n watermark: v.string(),\n});\n\nexport type MessageBegin = v.Infer<typeof beginSchema>;\nexport type MessageCommit = v.Infer<typeof commitSchema>;\nexport type MessageRollback = v.Infer<typeof rollbackSchema>;\n\nexport type MessageRelation = v.Infer<typeof relationSchema>;\nexport type MessageInsert = v.Infer<typeof insertSchema>;\nexport type MessageUpdate = v.Infer<typeof updateSchema>;\nexport type MessageDelete = v.Infer<typeof deleteSchema>;\nexport type MessageTruncate = v.Infer<typeof truncateSchema>;\n\nexport type MessageBackfill = v.Infer<typeof backfillSchema>;\n\nexport type TableCreate = v.Infer<typeof createTableSchema>;\nexport type TableRename = v.Infer<typeof renameTableSchema>;\nexport type TableUpdateMetadata = v.Infer<typeof updateTableMetadataSchema>;\nexport type ColumnAdd = v.Infer<typeof addColumnSchema>;\nexport type ColumnUpdate = v.Infer<typeof updateColumnSchema>;\nexport type ColumnDrop = v.Infer<typeof dropColumnSchema>;\nexport type TableDrop = v.Infer<typeof dropTableSchema>;\nexport type IndexCreate = v.Infer<typeof createIndexSchema>;\nexport type IndexDrop = v.Infer<typeof dropIndexSchema>;\nexport type BackfillCompleted = v.Infer<typeof backfillCompletedSchema>;\n\nexport const dataChangeSchema = v.union(\n insertSchema,\n updateSchema,\n deleteSchema,\n truncateSchema,\n backfillSchema,\n);\n\n// Note: keep in sync or the tag tests will fail\nconst dataChangeTags = [\n 'insert',\n 'update',\n 'delete',\n 'truncate',\n 'backfill',\n] as const;\n\nconst dataChangeTagsSchema = v.literalUnion(...dataChangeTags);\n\nexport type DataChange = Satisfies<\n JSONObject, // guarantees serialization over IPC or network\n v.Infer<typeof dataChangeSchema>\n>;\n\nexport type DataChangeTag = v.Infer<typeof dataChangeTagsSchema>;\n\nconst schemaChanges = [\n createTableSchema,\n renameTableSchema,\n updateTableMetadataSchema,\n addColumnSchema,\n updateColumnSchema,\n dropColumnSchema,\n dropTableSchema,\n createIndexSchema,\n dropIndexSchema,\n backfillCompletedSchema,\n] as const;\n\n// Note: keep in sync or the tag tests will fail\nconst schemaChangeTags = [\n 'create-table',\n 'rename-table',\n 'update-table-metadata',\n 'add-column',\n 'update-column',\n 'drop-column',\n 'drop-table',\n 'create-index',\n 'drop-index',\n 'backfill-completed',\n] as const;\n\nexport const schemaChangeSchema = v.union(...schemaChanges);\n\nconst schemaChangeTagsSchema = v.literalUnion(...schemaChangeTags);\n\nexport type SchemaChange = Satisfies<\n JSONObject,\n v.Infer<typeof schemaChangeSchema>\n>;\n\nexport type SchemaChangeTag = v.Infer<typeof schemaChangeTagsSchema>;\n\nexport type DataOrSchemaChange = DataChange | SchemaChange;\n\nexport type Change =\n | MessageBegin\n | DataOrSchemaChange\n | MessageCommit\n | MessageRollback;\n\nexport type ChangeTag = Change['tag'];\n\nconst schemaChangeTagSet = new Set<string>(schemaChangeTags);\n\nexport function isSchemaChange(change: Change): change is SchemaChange {\n return schemaChangeTagSet.has(change.tag);\n}\n\nconst dataChangeTagSet = new Set<string>(dataChangeTags);\n\nexport function isDataChange(change: Change): change is DataChange {\n return dataChangeTagSet.has(change.tag);\n}\n"],"names":["v.object","v.literal","v.literalUnion","v.boolean","v.array","v.string","v.record","v.union"],"mappings":"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"}
1
+ {"version":3,"file":"data.js","sources":["../../../../../../../../zero-cache/src/services/change-source/protocol/current/data.ts"],"sourcesContent":["/**\n * Data plane messages encapsulate changes that are sent by ChangeSources,\n * forwarded / fanned out to subscribers by the ChangeStreamerService, and\n * stored in the Change DB for catchup of old subscribers.\n */\n\nimport {\n jsonValueSchema,\n type JSONObject,\n} from '../../../../../../shared/src/bigint-json.ts';\nimport {must} from '../../../../../../shared/src/must.ts';\nimport * as v from '../../../../../../shared/src/valita.ts';\nimport {columnSpec, indexSpec, tableSpec} from '../../../../db/specs.ts';\nimport type {Satisfies} from '../../../../types/satisfies.ts';\nimport {jsonObjectSchema} from './json.ts';\n\nexport const beginSchema = v.object({\n tag: v.literal('begin'),\n // The format of values of \"json\"-typed columns (e.g. \"JSON\" and \"JSONB\").\n // - 'p' is for parsed JSON, which may include JSON values or JSON objects.\n // These values are parsed and stringified at every process boundary\n // between the change-source and the replica.\n // - 's' is for stringified JSON. These values skip the parsing and\n // stringification, and are directly ferried to the replica as a JSON\n // string. For JSON values this improves performance by 20~25% in the\n // change-streamer and 25~30% in the replicator.\n //\n // If absent, the format is assumed to be 'p' (parsed JSON objects/values).\n json: v.literalUnion('p', 's').optional(),\n\n // Directs the change-streamer to skip the ACK for the corresponding commit.\n skipAck: v.boolean().optional(),\n});\n\nexport const commitSchema = v.object({\n tag: v.literal('commit'),\n});\n\nexport const rollbackSchema = v.object({\n tag: v.literal('rollback'),\n});\n\nconst rowKeySchema = v.object({\n // The columns used to identify a row in insert, update, and delete changes.\n columns: v.array(v.string()),\n\n // An optional qualifier identifying how the key is chosen. Currently this\n // is postgres-specific, describing the REPLICA IDENTITY, for which replica\n // identity 'full' (FULL) is handled differently; the replicator handles\n // these tables by extracting a row key from the full row based on the\n // table's PRIMARY KEY or UNIQUE INDEX.\n type: v.literalUnion('default', 'nothing', 'full', 'index').optional(),\n});\n\nexport const relationSchema = v\n .object({\n schema: v.string(),\n name: v.string(),\n\n // This will become required.\n rowKey: rowKeySchema.optional(),\n\n /** Deprecated: set the rowKey.columns instead. */\n keyColumns: v.array(v.string()).optional(),\n /** Deprecated: set the rowKey.columns instead. */\n replicaIdentity: v\n .literalUnion('default', 'nothing', 'full', 'index')\n .optional(),\n })\n .map(rel => {\n const {rowKey, ...rest} = rel;\n if (rowKey) {\n return {...rest, rowKey};\n }\n return {\n ...rest,\n rowKey: {\n columns: must(rel.keyColumns),\n type: rel.replicaIdentity,\n },\n };\n });\n\n// The eventual fate of relationSchema\nexport const newRelationSchema = v.object({\n schema: v.string(),\n name: v.string(),\n\n rowKey: rowKeySchema,\n});\n\n// TableMetadata contains table-related configuration that does not affect the\n// actual data in the table, but rather how the table's change messages are\n// handled. The is an opaque object that clients must track (and update) based\n// on `create-table`, `add-column`, and `table-update-metadata` messages, and\n// pass in BackfillRequests when there are columns to be backfilled.\n//\n// Note that the backfill-related change-source implementation does, however,\n// rely on the rowKey (columns) being specified in the message.\nexport const tableMetadataSchema = v\n .object({rowKey: v.record(jsonValueSchema)})\n .rest(jsonValueSchema);\n\nexport type TableMetadata = v.Infer<typeof tableMetadataSchema>;\n\nexport const rowSchema = v.record(jsonValueSchema);\n\nexport const insertSchema = v.object({\n tag: v.literal('insert'),\n relation: relationSchema,\n new: rowSchema,\n});\n\nexport const updateSchema = v.object({\n tag: v.literal('update'),\n relation: relationSchema,\n // `key` is present if the update changed the key of the row, or if the\n // table's replicaIdentity === 'full'\n key: rowSchema.nullable(),\n // `new` is the full row (and not just the updated columns). This is\n // necessary for \"catchup\" replication scenarios such as adding tables\n // to a publication, or resharding.\n new: rowSchema,\n});\n\nexport const deleteSchema = v.object({\n tag: v.literal('delete'),\n relation: relationSchema,\n // key is the full row if replicaIdentity === 'full'\n key: rowSchema,\n});\n\nexport const truncateSchema = v.object({\n tag: v.literal('truncate'),\n relations: v.array(relationSchema),\n});\n\nexport const identifierSchema = v.object({\n schema: v.string(),\n name: v.string(),\n});\n\nexport type Identifier = v.Infer<typeof identifierSchema>;\n\n// A BackfillID is an upstream specific stable identifier for a column\n// that needs backfilling. This id is used to ensure that the schema, table,\n// and column names of a requested backfill still match the original\n// underlying upstream ID.\n//\n// The change-streamer stores these IDs as opaque values while a column is\n// being backfilled, and initiates new change-source streams with the IDs\n// in order to restart backfills that did not complete in previous sessions.\nexport const backfillIDSchema = jsonObjectSchema;\n\nexport type BackfillID = v.Infer<typeof backfillIDSchema>;\n\nexport const createTableSchema = v.object({\n tag: v.literal('create-table'),\n spec: tableSpec,\n\n // This must be set by change source implementations that support\n // table/column backfill.\n //\n // TODO: to simplify the protocol, see if we can make this required\n metadata: tableMetadataSchema.optional(),\n\n // Indicate that columns of the table require backfilling. These columns\n // should be created on the replica but not yet synced the clients.\n //\n // ## State Persistence\n //\n // To obviate the need for change-source implementations to persist state\n // related to backfill progress, the change-source only tracks backfills\n // **for the current session**. In the event that the session is interrupted\n // before columns have been fully backfilled, it is the responsibility of the\n // change-streamer to send {@link BackfillRequest}s when it reconnects.\n //\n // This means that the change-streamer must track and persist:\n // * the backfill IDs of the columns requiring backfilling\n // * the most current table metadata of the associated table(s)\n //\n // The change-streamer then uses this information to send backfill requests\n // when it reconnects.\n backfill: v.record(backfillIDSchema).optional(),\n});\n\nexport const renameTableSchema = v.object({\n tag: v.literal('rename-table'),\n old: identifierSchema,\n new: identifierSchema,\n});\n\nexport const updateTableMetadataSchema = v.object({\n tag: v.literal('update-table-metadata'),\n table: identifierSchema,\n old: tableMetadataSchema,\n new: tableMetadataSchema,\n});\n\nconst columnSchema = v.object({\n name: v.string(),\n spec: columnSpec,\n});\n\nexport const addColumnSchema = v.object({\n tag: v.literal('add-column'),\n table: identifierSchema,\n column: columnSchema,\n\n // This must be set by change source implementations that support\n // table/column backfill.\n //\n // TODO: to simplify the protocol, see if we can make this required\n tableMetadata: tableMetadataSchema.optional(),\n\n // See documentation for the `backfill` field of the `create-table` change.\n backfill: backfillIDSchema.optional(),\n});\n\nexport const updateColumnSchema = v.object({\n tag: v.literal('update-column'),\n table: identifierSchema,\n old: columnSchema,\n new: columnSchema,\n});\n\nexport const dropColumnSchema = v.object({\n tag: v.literal('drop-column'),\n table: identifierSchema,\n column: v.string(),\n});\n\nexport const dropTableSchema = v.object({\n tag: v.literal('drop-table'),\n id: identifierSchema,\n});\n\nexport const createIndexSchema = v.object({\n tag: v.literal('create-index'),\n spec: indexSpec,\n});\n\nexport const dropIndexSchema = v.object({\n tag: v.literal('drop-index'),\n id: identifierSchema,\n});\n\nexport const downloadStatusSchema = v.object({\n rows: v.number(),\n totalRows: v.number(),\n totalBytes: v.number().optional(),\n});\n\nexport type DownloadStatus = v.Infer<typeof downloadStatusSchema>;\n\n// A batch of rows from a single table containing column values\n// to be backfilled.\nexport const backfillSchema = v.object({\n tag: v.literal('backfill'),\n\n relation: newRelationSchema,\n\n // The columns to be backfilled. `rowKey` columns are automatically excluded,\n // which means that this field may be empty.\n columns: v.array(v.string()),\n\n // The watermark at which the backfill data was queried. Note that this\n // generally will be different from the commit watermarks of the main change\n // stream, and in particular, the commit watermark of the backfill change's\n // enclosing transaction.\n watermark: v.string(),\n\n // A batch of row values, each row consisting of the `rowKey`\n // values, followed by the `column` values, in the same order in which\n // the column names appear in their respective fields, e.g.\n //\n // ```\n // [\n // [...rowKeyValues, ...columnValues], // row 1\n // [...rowKeyValues, ...columnValues], // row 2\n // ]\n // ```\n rowValues: v.array(v.array(jsonValueSchema)),\n\n // Optionally includes the progress of the backfill operation,\n // for display purposes.\n status: downloadStatusSchema.optional(),\n});\n\n// Indicates that the backfill for the specified columns have\n// been successfully backfilled and can be published to clients.\nexport const backfillCompletedSchema = v.object({\n tag: v.literal('backfill-completed'),\n\n relation: newRelationSchema,\n\n // The columns to be backfilled. `rowKey` columns are automatically excluded,\n // which means that this field may be empty.\n columns: v.array(v.string()),\n\n // The watermark at which the backfill data was queried. Note that this\n // generally will be different from the commit watermarks of the main change\n // stream, and in particular, the commit watermark of the backfill change's\n // enclosing transaction.\n watermark: v.string(),\n\n // Optionally includes the final status of the backfill operation,\n // for display purposes.\n status: downloadStatusSchema.optional(),\n});\n\nexport type MessageBegin = v.Infer<typeof beginSchema>;\nexport type MessageCommit = v.Infer<typeof commitSchema>;\nexport type MessageRollback = v.Infer<typeof rollbackSchema>;\n\nexport type MessageRelation = v.Infer<typeof relationSchema>;\nexport type MessageInsert = v.Infer<typeof insertSchema>;\nexport type MessageUpdate = v.Infer<typeof updateSchema>;\nexport type MessageDelete = v.Infer<typeof deleteSchema>;\nexport type MessageTruncate = v.Infer<typeof truncateSchema>;\n\nexport type MessageBackfill = v.Infer<typeof backfillSchema>;\n\nexport type TableCreate = v.Infer<typeof createTableSchema>;\nexport type TableRename = v.Infer<typeof renameTableSchema>;\nexport type TableUpdateMetadata = v.Infer<typeof updateTableMetadataSchema>;\nexport type ColumnAdd = v.Infer<typeof addColumnSchema>;\nexport type ColumnUpdate = v.Infer<typeof updateColumnSchema>;\nexport type ColumnDrop = v.Infer<typeof dropColumnSchema>;\nexport type TableDrop = v.Infer<typeof dropTableSchema>;\nexport type IndexCreate = v.Infer<typeof createIndexSchema>;\nexport type IndexDrop = v.Infer<typeof dropIndexSchema>;\nexport type BackfillCompleted = v.Infer<typeof backfillCompletedSchema>;\n\nexport const dataChangeSchema = v.union(\n insertSchema,\n updateSchema,\n deleteSchema,\n truncateSchema,\n backfillSchema,\n);\n\n// Note: keep in sync or the tag tests will fail\nconst dataChangeTags = [\n 'insert',\n 'update',\n 'delete',\n 'truncate',\n 'backfill',\n] as const;\n\nconst dataChangeTagsSchema = v.literalUnion(...dataChangeTags);\n\nexport type DataChange = Satisfies<\n JSONObject, // guarantees serialization over IPC or network\n v.Infer<typeof dataChangeSchema>\n>;\n\nexport type DataChangeTag = v.Infer<typeof dataChangeTagsSchema>;\n\nconst schemaChanges = [\n createTableSchema,\n renameTableSchema,\n updateTableMetadataSchema,\n addColumnSchema,\n updateColumnSchema,\n dropColumnSchema,\n dropTableSchema,\n createIndexSchema,\n dropIndexSchema,\n backfillCompletedSchema,\n] as const;\n\n// Note: keep in sync or the tag tests will fail\nconst schemaChangeTags = [\n 'create-table',\n 'rename-table',\n 'update-table-metadata',\n 'add-column',\n 'update-column',\n 'drop-column',\n 'drop-table',\n 'create-index',\n 'drop-index',\n 'backfill-completed',\n] as const;\n\nexport const schemaChangeSchema = v.union(...schemaChanges);\n\nconst schemaChangeTagsSchema = v.literalUnion(...schemaChangeTags);\n\nexport type SchemaChange = Satisfies<\n JSONObject,\n v.Infer<typeof schemaChangeSchema>\n>;\n\nexport type SchemaChangeTag = v.Infer<typeof schemaChangeTagsSchema>;\n\nexport type DataOrSchemaChange = DataChange | SchemaChange;\n\nexport type Change =\n | MessageBegin\n | DataOrSchemaChange\n | MessageCommit\n | MessageRollback;\n\nexport type ChangeTag = Change['tag'];\n\nconst schemaChangeTagSet = new Set<string>(schemaChangeTags);\n\nexport function isSchemaChange(change: Change): change is SchemaChange {\n return schemaChangeTagSet.has(change.tag);\n}\n\nconst dataChangeTagSet = new Set<string>(dataChangeTags);\n\nexport function isDataChange(change: Change): change is DataChange {\n return dataChangeTagSet.has(change.tag);\n}\n"],"names":["v.object","v.literal","v.literalUnion","v.boolean","v.array","v.string","v.record","v.number","v.union"],"mappings":"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"}
@@ -71,6 +71,11 @@ declare const data: v.TupleType<[v.Type<"data">, v.UnionType<[v.UnionType<[v.Obj
71
71
  columns: v.ArrayType<v.Type<string>>;
72
72
  watermark: v.Type<string>;
73
73
  rowValues: v.ArrayType<v.ArrayType<v.Type<import("../../../../../../shared/src/bigint-json.ts").JSONValue>>>;
74
+ status: v.Optional<{
75
+ totalBytes?: number | undefined;
76
+ rows: number;
77
+ totalRows: number;
78
+ }>;
74
79
  }, undefined>]>, v.UnionType<[v.ObjectType<{
75
80
  tag: v.Type<"create-table">;
76
81
  spec: v.ObjectType<Omit<{
@@ -209,6 +214,11 @@ declare const data: v.TupleType<[v.Type<"data">, v.UnionType<[v.UnionType<[v.Obj
209
214
  }, undefined>;
210
215
  columns: v.ArrayType<v.Type<string>>;
211
216
  watermark: v.Type<string>;
217
+ status: v.Optional<{
218
+ totalBytes?: number | undefined;
219
+ rows: number;
220
+ totalRows: number;
221
+ }>;
212
222
  }, undefined>]>]>]>;
213
223
  declare const commit: v.TupleType<[v.Type<"commit">, v.ObjectType<{
214
224
  tag: v.Type<"commit">;
@@ -293,6 +303,11 @@ export declare const changeStreamDataSchema: v.UnionType<[v.TupleType<[v.Type<"b
293
303
  columns: v.ArrayType<v.Type<string>>;
294
304
  watermark: v.Type<string>;
295
305
  rowValues: v.ArrayType<v.ArrayType<v.Type<import("../../../../../../shared/src/bigint-json.ts").JSONValue>>>;
306
+ status: v.Optional<{
307
+ totalBytes?: number | undefined;
308
+ rows: number;
309
+ totalRows: number;
310
+ }>;
296
311
  }, undefined>]>, v.UnionType<[v.ObjectType<{
297
312
  tag: v.Type<"create-table">;
298
313
  spec: v.ObjectType<Omit<{
@@ -431,6 +446,11 @@ export declare const changeStreamDataSchema: v.UnionType<[v.TupleType<[v.Type<"b
431
446
  }, undefined>;
432
447
  columns: v.ArrayType<v.Type<string>>;
433
448
  watermark: v.Type<string>;
449
+ status: v.Optional<{
450
+ totalBytes?: number | undefined;
451
+ rows: number;
452
+ totalRows: number;
453
+ }>;
434
454
  }, undefined>]>]>]>, v.TupleType<[v.Type<"commit">, v.ObjectType<{
435
455
  tag: v.Type<"commit">;
436
456
  }, undefined>, v.ObjectType<{
@@ -517,6 +537,11 @@ export declare const changeStreamMessageSchema: v.UnionType<[v.UnionType<[v.Tupl
517
537
  columns: v.ArrayType<v.Type<string>>;
518
538
  watermark: v.Type<string>;
519
539
  rowValues: v.ArrayType<v.ArrayType<v.Type<import("../../../../../../shared/src/bigint-json.ts").JSONValue>>>;
540
+ status: v.Optional<{
541
+ totalBytes?: number | undefined;
542
+ rows: number;
543
+ totalRows: number;
544
+ }>;
520
545
  }, undefined>]>, v.UnionType<[v.ObjectType<{
521
546
  tag: v.Type<"create-table">;
522
547
  spec: v.ObjectType<Omit<{
@@ -655,6 +680,11 @@ export declare const changeStreamMessageSchema: v.UnionType<[v.UnionType<[v.Tupl
655
680
  }, undefined>;
656
681
  columns: v.ArrayType<v.Type<string>>;
657
682
  watermark: v.Type<string>;
683
+ status: v.Optional<{
684
+ totalBytes?: number | undefined;
685
+ rows: number;
686
+ totalRows: number;
687
+ }>;
658
688
  }, undefined>]>]>]>, v.TupleType<[v.Type<"commit">, v.ObjectType<{
659
689
  tag: v.Type<"commit">;
660
690
  }, undefined>, v.ObjectType<{
@@ -1 +1 @@
1
- {"version":3,"file":"downstream.d.ts","sourceRoot":"","sources":["../../../../../../../../zero-cache/src/services/change-source/protocol/current/downstream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,wCAAwC,CAAC;AAW5D,QAAA,MAAM,KAAK;;;;;;eAIT,CAAC;AACH,QAAA,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAGR,CAAC;AACH,QAAA,MAAM,MAAM;;;;eAIV,CAAC;AACH,QAAA,MAAM,QAAQ;;eAAmD,CAAC;AAElE,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,CAAC;AAC1C,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AACxC,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAC5C,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEhD,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAyC,CAAC;AAC7E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,eAAO,MAAM,yBAAyB;;;;eAGpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,4EAA4E;AAC5E,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAIrC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
1
+ {"version":3,"file":"downstream.d.ts","sourceRoot":"","sources":["../../../../../../../../zero-cache/src/services/change-source/protocol/current/downstream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,wCAAwC,CAAC;AAW5D,QAAA,MAAM,KAAK;;;;;;eAIT,CAAC;AACH,QAAA,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAGR,CAAC;AACH,QAAA,MAAM,MAAM;;;;eAIV,CAAC;AACH,QAAA,MAAM,QAAQ;;eAAmD,CAAC;AAElE,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,CAAC;AAC1C,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AACxC,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAC5C,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEhD,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAyC,CAAC;AAC7E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,eAAO,MAAM,yBAAyB;;;;eAGpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,4EAA4E;AAC5E,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAIrC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { resetRequiredSchema } from "./current/control.js";
2
- import { addColumnSchema, backfillCompletedSchema, backfillIDSchema, backfillSchema, beginSchema, commitSchema, createIndexSchema, createTableSchema, dataChangeSchema, deleteSchema, dropColumnSchema, dropIndexSchema, dropTableSchema, identifierSchema, insertSchema, isDataChange, isSchemaChange, newRelationSchema, relationSchema, renameTableSchema, rollbackSchema, rowSchema, schemaChangeSchema, tableMetadataSchema, truncateSchema, updateColumnSchema, updateSchema, updateTableMetadataSchema } from "./current/data.js";
2
+ import { addColumnSchema, backfillCompletedSchema, backfillIDSchema, backfillSchema, beginSchema, commitSchema, createIndexSchema, createTableSchema, dataChangeSchema, deleteSchema, downloadStatusSchema, dropColumnSchema, dropIndexSchema, dropTableSchema, identifierSchema, insertSchema, isDataChange, isSchemaChange, newRelationSchema, relationSchema, renameTableSchema, rollbackSchema, rowSchema, schemaChangeSchema, tableMetadataSchema, truncateSchema, updateColumnSchema, updateSchema, updateTableMetadataSchema } from "./current/data.js";
3
3
  import { changeStreamControlSchema, changeStreamDataSchema, changeStreamMessageSchema } from "./current/downstream.js";
4
4
  import { jsonObjectSchema, jsonValueSchema } from "./current/json.js";
5
5
  import { CHANGE_SOURCE_PATH } from "./current/path.js";
@@ -22,6 +22,7 @@ export {
22
22
  createTableSchema,
23
23
  dataChangeSchema,
24
24
  deleteSchema,
25
+ downloadStatusSchema,
25
26
  downstreamStatusMessageSchema,
26
27
  downstreamStatusSchema,
27
28
  dropColumnSchema,
@@ -1 +1 @@
1
- {"version":3,"file":"change-streamer-service.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAUjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,uBAAuB,CAAC;AAGnD,OAAO,KAAK,EACV,YAAY,EAEb,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,iDAAiD,CAAC;AAEzD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,2CAA2C,CAAC;AAMjF,OAAO,EACL,KAAK,qBAAqB,EAG3B,MAAM,sBAAsB,CAAC;AAY9B;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,EACxB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,YAAY,EAC1B,iBAAiB,EAAE,iBAAiB,EACpC,SAAS,EAAE,OAAO,EAClB,+BAA+B,EAAE,MAAM,EACvC,YAAY,oBAAa,GACxB,OAAO,CAAC,qBAAqB,CAAC,CAyBhC;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC"}
1
+ {"version":3,"file":"change-streamer-service.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAYjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,uBAAuB,CAAC;AAGnD,OAAO,KAAK,EACV,YAAY,EAEb,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,iDAAiD,CAAC;AAKzD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,2CAA2C,CAAC;AAMjF,OAAO,EACL,KAAK,qBAAqB,EAG3B,MAAM,sBAAsB,CAAC;AAY9B;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,EACxB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,YAAY,EAC1B,iBAAiB,EAAE,iBAAiB,EACpC,SAAS,EAAE,OAAO,EAClB,+BAA+B,EAAE,MAAM,EACvC,YAAY,oBAAa,GACxB,OAAO,CAAC,qBAAqB,CAAC,CAyBhC;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC"}
@@ -1,11 +1,13 @@
1
1
  import { resolver } from "@rocicorp/resolver";
2
2
  import { getDefaultHighWaterMark } from "node:stream";
3
3
  import { unreachable } from "../../../../shared/src/asserts.js";
4
+ import { promiseVoid } from "../../../../shared/src/resolved-promises.js";
5
+ import { publishCriticalEvent } from "../../observability/events.js";
4
6
  import { getOrCreateCounter } from "../../observability/metrics.js";
5
7
  import { min } from "../../types/lexi-version.js";
6
8
  import { Subscription } from "../../types/subscription.js";
7
9
  import "../change-source/protocol/current/downstream.js";
8
- import { publishReplicationError } from "../replicator/replication-status.js";
10
+ import { replicationStatusError, publishReplicationError } from "../replicator/replication-status.js";
9
11
  import { RunningState, UnrecoverableError, DEFAULT_MAX_RETRY_DELAY_MS } from "../running-state.js";
10
12
  import "./change-streamer.js";
11
13
  import { WrongReplicaVersion } from "./error-type-enum.js";
@@ -167,7 +169,11 @@ class ChangeStreamerImpl {
167
169
  }
168
170
  await Promise.all([
169
171
  this.#storer.stop(),
170
- this.#state.backoff(this.#lc, err)
172
+ this.#state.backoff(this.#lc, err),
173
+ this.#state.retryDelay > 5e3 ? publishCriticalEvent(
174
+ this.#lc,
175
+ replicationStatusError(this.#lc, "Replicating", err)
176
+ ) : promiseVoid
171
177
  ]);
172
178
  }
173
179
  this.#lc.info?.("ChangeStreamer stopped");
@@ -1 +1 @@
1
- {"version":3,"file":"change-streamer-service.js","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-service.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {getDefaultHighWaterMark} from 'node:stream';\nimport {unreachable} from '../../../../shared/src/asserts.ts';\nimport {getOrCreateCounter} from '../../observability/metrics.ts';\nimport {\n min,\n type AtLeastOne,\n type LexiVersion,\n} from '../../types/lexi-version.ts';\nimport type {PostgresDB} from '../../types/pg.ts';\nimport type {ShardID} from '../../types/shards.ts';\nimport type {Source} from '../../types/streams.ts';\nimport {Subscription} from '../../types/subscription.ts';\nimport type {\n ChangeSource,\n ChangeStream,\n} from '../change-source/change-source.ts';\nimport {\n type ChangeStreamControl,\n type ChangeStreamData,\n} from '../change-source/protocol/current/downstream.ts';\nimport {publishReplicationError} from '../replicator/replication-status.ts';\nimport type {SubscriptionState} from '../replicator/schema/replication-state.ts';\nimport {\n DEFAULT_MAX_RETRY_DELAY_MS,\n RunningState,\n UnrecoverableError,\n} from '../running-state.ts';\nimport {\n type ChangeStreamerService,\n type Downstream,\n type SubscriberContext,\n} from './change-streamer.ts';\nimport * as ErrorType from './error-type-enum.ts';\nimport {Forwarder} from './forwarder.ts';\nimport {initChangeStreamerSchema} from './schema/init.ts';\nimport {\n AutoResetSignal,\n ensureReplicationConfig,\n markResetRequired,\n} from './schema/tables.ts';\nimport {Storer} from './storer.ts';\nimport {Subscriber} from './subscriber.ts';\n\n/**\n * Performs initialization and schema migrations to initialize a ChangeStreamerImpl.\n */\nexport async function initializeStreamer(\n lc: LogContext,\n shard: ShardID,\n taskID: string,\n discoveryAddress: string,\n discoveryProtocol: string,\n changeDB: PostgresDB,\n changeSource: ChangeSource,\n subscriptionState: SubscriptionState,\n autoReset: boolean,\n backPressureLimitHeapProportion: number,\n setTimeoutFn = setTimeout,\n): Promise<ChangeStreamerService> {\n // Make sure the ChangeLog DB is set up.\n await initChangeStreamerSchema(lc, changeDB, shard);\n await ensureReplicationConfig(\n lc,\n changeDB,\n subscriptionState,\n shard,\n autoReset,\n );\n\n const {replicaVersion} = subscriptionState;\n return new ChangeStreamerImpl(\n lc,\n shard,\n taskID,\n discoveryAddress,\n discoveryProtocol,\n changeDB,\n replicaVersion,\n changeSource,\n autoReset,\n backPressureLimitHeapProportion,\n setTimeoutFn,\n );\n}\n\n/**\n * Internally all Downstream messages (not just commits) are given a watermark.\n * These are used for internal ordering for:\n * 1. Replaying new changes in the Storer\n * 2. Filtering old changes in the Subscriber\n *\n * However, only the watermark for `Commit` messages are exposed to\n * subscribers, as that is the only semantically correct watermark to\n * use for tracking a position in a replication stream.\n */\nexport type WatermarkedChange = [watermark: string, ChangeStreamData];\n\n/**\n * Upstream-agnostic dispatch of messages in a {@link ChangeStreamMessage} to a\n * {@link Forwarder} and {@link Storer} to execute the forward-store-ack\n * procedure described in {@link ChangeStreamer}.\n *\n * ### Subscriber Catchup\n *\n * Connecting clients first need to be \"caught up\" to the current watermark\n * (from stored change log entries) before new entries are forwarded to\n * them. This is non-trivial because the replication stream may be in the\n * middle of a pending streamed Transaction for which some entries have\n * already been forwarded but are not yet committed to the store.\n *\n *\n * ```\n * ------------------------------- - - - - - - - - - - - - - - - - - - -\n * | Historic changes in storage | Pending (streamed) tx | Next tx\n * ------------------------------- - - - - - - - - - - - - - - - - - - -\n * Replication stream\n * > > > > > > > > >\n * ^ ---> required catchup ---> ^\n * Subscriber watermark Subscription begins\n * ```\n *\n * Preemptively buffering the changes of every pending transaction\n * would be wasteful and consume too much memory for large transactions.\n *\n * Instead, the streamer synchronously dispatches changes and subscriptions\n * to the {@link Forwarder} and the {@link Storer} such that the two\n * components are aligned as to where in the stream the subscription started.\n * The two components then coordinate catchup and handoff via the\n * {@link Subscriber} object with the following algorithm:\n *\n * * If the streamer is in the middle of a pending Transaction, the\n * Subscriber is \"queued\" on both the Forwarder and the Storer. In this\n * state, new changes are *not* forwarded to the Subscriber, and catchup\n * is not yet executed.\n * * Once the commit message for the pending Transaction is processed\n * by the Storer, it begins catchup on the Subscriber (with a READONLY\n * snapshot so that it does not block subsequent storage operations).\n * This catchup is thus guaranteed to load the change log entries of\n * that last Transaction.\n * * When the Forwarder processes that same commit message, it moves the\n * Subscriber from the \"queued\" to the \"active\" set of clients such that\n * the Subscriber begins receiving new changes, starting from the next\n * Transaction.\n * * The Subscriber does not forward those changes, however, if its catchup\n * is not complete. Until then, it buffers the changes in memory.\n * * Once catchup is complete, the buffered changes are immediately sent\n * and the Subscriber henceforth forwards changes as they are received.\n *\n * In the (common) case where the streamer is not in the middle of a pending\n * transaction when a subscription begins, the Storer begins catchup\n * immediately and the Forwarder directly adds the Subscriber to its active\n * set. However, the Subscriber still buffers any forwarded messages until\n * its catchup is complete.\n *\n * ### Watermarks and ordering\n *\n * The ChangeStreamerService depends on its {@link ChangeSource} to send\n * changes in contiguous [`begin`, `data` ..., `data`, `commit`] sequences\n * in commit order. This follows Postgres's Logical Replication Protocol\n * Message Flow:\n *\n * https://www.postgresql.org/docs/16/protocol-logical-replication.html#PROTOCOL-LOGICAL-MESSAGES-FLOW\n *\n * > The logical replication protocol sends individual transactions one by one.\n * > This means that all messages between a pair of Begin and Commit messages belong to the same transaction.\n *\n * In order to correctly replay (new) and filter (old) messages to subscribers\n * at different points in the replication stream, these changes must be assigned\n * watermarks such that they preserve the order in which they were received\n * from the ChangeSource.\n *\n * A previous implementation incorrectly derived these watermarks from the Postgres\n * Log Sequence Numbers (LSN) of each message. However, LSNs from concurrent,\n * non-conflicting transactions can overlap, which can result in a `begin` message\n * with an earlier LSN arriving after a `commit` message. For example, the\n * changes for these transactions:\n *\n * ```\n * LSN: 1 2 3 4 5 6 7 8 9 10\n * tx1: begin data data data commit\n * tx2: begin data data data commit\n * ```\n *\n * will arrive as:\n *\n * ```\n * begin1, data2, data4, data6, commit8, begin3, data5, data7, data9, commit10\n * ```\n *\n * Thus, LSN of non-commit messages are not suitable for tracking the sorting\n * order of the replication stream.\n *\n * Instead, the ChangeStreamer uses the following algorithm for deterministic\n * catchup and filtering of changes:\n *\n * * A `commit` message is assigned to a watermark corresponding to its LSN.\n * These are guaranteed to be in commit order by definition.\n *\n * * `begin` and `data` messages are assigned to the watermark of the\n * preceding `commit` (the previous transaction, or the replication\n * slot's starting LSN) plus 1. This guarantees that they will be sorted\n * after the previously commit transaction even if their LSNs came before it.\n * This is referred to as the `preCommitWatermark`.\n *\n * * In the ChangeLog DB, messages have a secondary sort column `pos`, which is\n * the position of the message within its transaction, with the `begin` message\n * starting at `0`. This guarantees that `begin` and `data` messages will be\n * fetched in the original ChangeSource order during catchup.\n *\n * `begin` and `data` messages share the same watermark, but this is sufficient for\n * Subscriber filtering because subscribers only know about the `commit` watermarks\n * exposed in the `Downstream` `Commit` message. The Subscriber object thus compares\n * the internal watermarks of the incoming messages against the commit watermark of\n * the caller, updating the watermark at every `Commit` message that is forwarded.\n *\n * ### Cleanup\n *\n * As mentioned in the {@link ChangeStreamer} documentation: \"the ChangeStreamer\n * uses a combination of [the \"initial\", i.e. backup-derived watermark and] ACK\n * responses from connected subscribers to determine the watermark up\n * to which it is safe to purge old change log entries.\"\n *\n * More concretely:\n *\n * * The `initial`, backup-derived watermark is the earliest to which cleanup\n * should ever happen.\n *\n * * However, it is possible for the replica backup to be *ahead* of a connected\n * subscriber; and if a network error causes that subscriber to retry from its\n * last watermark, the change streamer must support it.\n *\n * Thus, before cleaning up to an `initial` backup-derived watermark, the change\n * streamer first confirms that all connected subscribers have also passed\n * that watermark.\n */\nclass ChangeStreamerImpl implements ChangeStreamerService {\n readonly id: string;\n readonly #lc: LogContext;\n readonly #shard: ShardID;\n readonly #changeDB: PostgresDB;\n readonly #replicaVersion: string;\n readonly #source: ChangeSource;\n readonly #storer: Storer;\n readonly #forwarder: Forwarder;\n\n readonly #autoReset: boolean;\n readonly #state: RunningState;\n readonly #initialWatermarks = new Set<string>();\n\n // Starting the (Postgres) ChangeStream results in killing the previous\n // Postgres subscriber, potentially creating a gap in which the old\n // change-streamer has shut down and the new change-streamer has not yet\n // been recognized as \"healthy\" (and thus does not get any requests).\n //\n // To minimize this gap, delay starting the ChangeStream until the first\n // request from a `serving` replicator, indicating that higher level\n // load-balancing / routing logic has begun routing requests to this task.\n readonly #serving = resolver();\n\n readonly #txCounter = getOrCreateCounter(\n 'replication',\n 'transactions',\n 'Count of replicated transactions',\n );\n\n #stream: ChangeStream | undefined;\n\n constructor(\n lc: LogContext,\n shard: ShardID,\n taskID: string,\n discoveryAddress: string,\n discoveryProtocol: string,\n changeDB: PostgresDB,\n replicaVersion: string,\n source: ChangeSource,\n autoReset: boolean,\n backPressureLimitHeapProportion: number,\n setTimeoutFn = setTimeout,\n ) {\n this.id = `change-streamer`;\n this.#lc = lc.withContext('component', 'change-streamer');\n this.#shard = shard;\n this.#changeDB = changeDB;\n this.#replicaVersion = replicaVersion;\n this.#source = source;\n this.#storer = new Storer(\n lc,\n shard,\n taskID,\n discoveryAddress,\n discoveryProtocol,\n changeDB,\n replicaVersion,\n consumed => this.#stream?.acks.push(['status', consumed[1], consumed[2]]),\n err => this.stop(err),\n backPressureLimitHeapProportion,\n );\n this.#forwarder = new Forwarder();\n this.#autoReset = autoReset;\n this.#state = new RunningState(this.id, undefined, setTimeoutFn);\n }\n\n async run() {\n this.#lc.info?.('starting change stream');\n\n // Once this change-streamer acquires \"ownership\" of the change DB,\n // it is safe to start the storer.\n await this.#storer.assumeOwnership();\n\n // The threshold in (estimated number of) bytes to send() on subscriber\n // websockets before `await`-ing the I/O buffers to be ready for more.\n const flushBytesThreshold = getDefaultHighWaterMark(false);\n\n while (this.#state.shouldRun()) {\n let err: unknown;\n let watermark: string | null = null;\n let unflushedBytes = 0;\n try {\n const {lastWatermark, backfillRequests} =\n await this.#storer.getStartStreamInitializationParameters();\n const stream = await this.#source.startStream(\n lastWatermark,\n backfillRequests,\n );\n this.#storer.run().catch(e => stream.changes.cancel(e));\n\n this.#stream = stream;\n this.#state.resetBackoff();\n watermark = null;\n\n for await (const change of stream.changes) {\n const [type, msg] = change;\n switch (type) {\n case 'status':\n if (msg.ack) {\n this.#storer.status(change); // storer acks once it gets through its queue\n }\n continue;\n case 'control':\n await this.#handleControlMessage(msg);\n continue; // control messages are not stored/forwarded\n case 'begin':\n watermark = change[2].commitWatermark;\n break;\n case 'commit':\n if (watermark !== change[2].watermark) {\n throw new UnrecoverableError(\n `commit watermark ${change[2].watermark} does not match 'begin' watermark ${watermark}`,\n );\n }\n this.#txCounter.add(1);\n break;\n default:\n if (watermark === null) {\n throw new UnrecoverableError(\n `${type} change (${msg.tag}) received before 'begin' message`,\n );\n }\n break;\n }\n\n unflushedBytes += this.#storer.store([watermark, change]);\n const sent = this.#forwarder.forward([watermark, change]);\n if (unflushedBytes >= flushBytesThreshold) {\n // Wait for messages to clear socket buffers to ensure that they\n // make their way to subscribers. Without this `await`, the\n // messages end up being buffered in this process, which:\n // (1) results in memory pressure and increased GC activity\n // (2) prevents subscribers from processing the messages as they\n // arrive, instead getting them in a large batch after being\n // idle while they were queued (causing further delays).\n await sent;\n unflushedBytes = 0;\n }\n\n if (type === 'commit' || type === 'rollback') {\n watermark = null;\n }\n\n // Allow the storer to exert back pressure.\n const readyForMore = this.#storer.readyForMore();\n if (readyForMore) {\n await readyForMore;\n }\n }\n } catch (e) {\n err = e;\n } finally {\n this.#stream?.changes.cancel();\n this.#stream = undefined;\n }\n\n // When the change stream is interrupted, abort any pending transaction.\n if (watermark) {\n this.#lc.warn?.(`aborting interrupted transaction ${watermark}`);\n this.#storer.abort();\n await this.#forwarder.forward([\n watermark,\n ['rollback', {tag: 'rollback'}],\n ]);\n }\n\n // Backoff and drain any pending entries in the storer before reconnecting.\n await Promise.all([\n this.#storer.stop(),\n this.#state.backoff(this.#lc, err),\n ]);\n }\n this.#lc.info?.('ChangeStreamer stopped');\n }\n\n async #handleControlMessage(msg: ChangeStreamControl[1]) {\n this.#lc.info?.('received control message', msg);\n const {tag} = msg;\n\n switch (tag) {\n case 'reset-required':\n await markResetRequired(this.#changeDB, this.#shard);\n await publishReplicationError(\n this.#lc,\n 'Replicating',\n msg.message ?? 'Resync required',\n msg.errorDetails,\n );\n if (this.#autoReset) {\n this.#lc.warn?.('shutting down for auto-reset');\n await this.stop(new AutoResetSignal());\n }\n break;\n default:\n unreachable(tag);\n }\n }\n\n subscribe(ctx: SubscriberContext): Promise<Source<Downstream>> {\n const {protocolVersion, id, mode, replicaVersion, watermark, initial} = ctx;\n if (mode === 'serving') {\n this.#serving.resolve();\n }\n const downstream = Subscription.create<Downstream>({\n cleanup: () => this.#forwarder.remove(subscriber),\n });\n const subscriber = new Subscriber(\n protocolVersion,\n id,\n watermark,\n downstream,\n );\n if (replicaVersion !== this.#replicaVersion) {\n this.#lc.warn?.(\n `rejecting subscriber at replica version ${replicaVersion}`,\n );\n subscriber.close(\n ErrorType.WrongReplicaVersion,\n `current replica version is ${\n this.#replicaVersion\n } (requested ${replicaVersion})`,\n );\n } else {\n this.#lc.debug?.(`adding subscriber ${subscriber.id}`);\n\n this.#forwarder.add(subscriber);\n this.#storer.catchup(subscriber, mode);\n\n if (initial) {\n this.scheduleCleanup(watermark);\n }\n }\n return Promise.resolve(downstream);\n }\n\n scheduleCleanup(watermark: string) {\n const origSize = this.#initialWatermarks.size;\n this.#initialWatermarks.add(watermark);\n\n if (origSize === 0) {\n this.#state.setTimeout(() => this.#purgeOldChanges(), CLEANUP_DELAY_MS);\n }\n }\n\n async getChangeLogState(): Promise<{\n replicaVersion: string;\n minWatermark: string;\n }> {\n const minWatermark = await this.#storer.getMinWatermarkForCatchup();\n if (!minWatermark) {\n this.#lc.warn?.(\n `Unexpected empty changeLog. Resync if \"Local replica watermark\" errors arise`,\n );\n }\n return {\n replicaVersion: this.#replicaVersion,\n minWatermark: minWatermark ?? this.#replicaVersion,\n };\n }\n\n /**\n * Makes a best effort to purge the change log. In the event of a database\n * error, exceptions will be logged and swallowed, so this method is safe\n * to run in a timeout.\n */\n async #purgeOldChanges(): Promise<void> {\n const initial = [...this.#initialWatermarks];\n if (initial.length === 0) {\n this.#lc.warn?.('No initial watermarks to check for cleanup'); // Not expected.\n return;\n }\n const current = [...this.#forwarder.getAcks()];\n if (current.length === 0) {\n // Also not expected, but possible (e.g. subscriber connects, then disconnects).\n // Bail to be safe.\n this.#lc.warn?.('No subscribers to confirm cleanup');\n return;\n }\n try {\n const earliestInitial = min(...(initial as AtLeastOne<LexiVersion>));\n const earliestCurrent = min(...(current as AtLeastOne<LexiVersion>));\n if (earliestCurrent < earliestInitial) {\n this.#lc.info?.(\n `At least one client is behind backup (${earliestCurrent} < ${earliestInitial})`,\n );\n } else {\n this.#lc.info?.(`Purging changes before ${earliestInitial} ...`);\n const start = performance.now();\n const deleted = await this.#storer.purgeRecordsBefore(earliestInitial);\n const elapsed = (performance.now() - start).toFixed(2);\n this.#lc.info?.(\n `Purged ${deleted} changes before ${earliestInitial} (${elapsed} ms)`,\n );\n this.#initialWatermarks.delete(earliestInitial);\n }\n } catch (e) {\n this.#lc.warn?.(`error purging change log`, e);\n } finally {\n if (this.#initialWatermarks.size) {\n // If there are unpurged watermarks to check, schedule the next purge.\n this.#state.setTimeout(() => this.#purgeOldChanges(), CLEANUP_DELAY_MS);\n }\n }\n }\n\n async stop(err?: unknown) {\n this.#state.stop(this.#lc, err);\n this.#stream?.changes.cancel();\n await this.#storer.stop();\n }\n}\n\n// The delay between receiving an initial, backup-based watermark\n// and performing a check of whether to purge records before it.\n// This delay should be long enough to handle situations like the following:\n//\n// 1. `litestream restore` downloads a backup for the `replication-manager`\n// 2. `replication-manager` starts up and runs this `change-streamer`\n// 3. `zero-cache`s that are running on a different replica connect to this\n// `change-streamer` after exponential backoff retries.\n//\n// It is possible for a `zero-cache`[3] to be behind the backup restored [1].\n// This cleanup delay (30 seconds) is thus set to be a value comfortably\n// longer than the max delay for exponential backoff (10 seconds) in\n// `services/running-state.ts`. This allows the `zero-cache` [3] to reconnect\n// so that the `change-streamer` can track its progress and know when it has\n// surpassed the initial watermark of the backup [1].\nconst CLEANUP_DELAY_MS = DEFAULT_MAX_RETRY_DELAY_MS * 3;\n"],"names":["ErrorType.WrongReplicaVersion"],"mappings":"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"}
1
+ {"version":3,"file":"change-streamer-service.js","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-service.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {getDefaultHighWaterMark} from 'node:stream';\nimport {unreachable} from '../../../../shared/src/asserts.ts';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport {publishCriticalEvent} from '../../observability/events.ts';\nimport {getOrCreateCounter} from '../../observability/metrics.ts';\nimport {\n min,\n type AtLeastOne,\n type LexiVersion,\n} from '../../types/lexi-version.ts';\nimport type {PostgresDB} from '../../types/pg.ts';\nimport type {ShardID} from '../../types/shards.ts';\nimport type {Source} from '../../types/streams.ts';\nimport {Subscription} from '../../types/subscription.ts';\nimport type {\n ChangeSource,\n ChangeStream,\n} from '../change-source/change-source.ts';\nimport {\n type ChangeStreamControl,\n type ChangeStreamData,\n} from '../change-source/protocol/current/downstream.ts';\nimport {\n publishReplicationError,\n replicationStatusError,\n} from '../replicator/replication-status.ts';\nimport type {SubscriptionState} from '../replicator/schema/replication-state.ts';\nimport {\n DEFAULT_MAX_RETRY_DELAY_MS,\n RunningState,\n UnrecoverableError,\n} from '../running-state.ts';\nimport {\n type ChangeStreamerService,\n type Downstream,\n type SubscriberContext,\n} from './change-streamer.ts';\nimport * as ErrorType from './error-type-enum.ts';\nimport {Forwarder} from './forwarder.ts';\nimport {initChangeStreamerSchema} from './schema/init.ts';\nimport {\n AutoResetSignal,\n ensureReplicationConfig,\n markResetRequired,\n} from './schema/tables.ts';\nimport {Storer} from './storer.ts';\nimport {Subscriber} from './subscriber.ts';\n\n/**\n * Performs initialization and schema migrations to initialize a ChangeStreamerImpl.\n */\nexport async function initializeStreamer(\n lc: LogContext,\n shard: ShardID,\n taskID: string,\n discoveryAddress: string,\n discoveryProtocol: string,\n changeDB: PostgresDB,\n changeSource: ChangeSource,\n subscriptionState: SubscriptionState,\n autoReset: boolean,\n backPressureLimitHeapProportion: number,\n setTimeoutFn = setTimeout,\n): Promise<ChangeStreamerService> {\n // Make sure the ChangeLog DB is set up.\n await initChangeStreamerSchema(lc, changeDB, shard);\n await ensureReplicationConfig(\n lc,\n changeDB,\n subscriptionState,\n shard,\n autoReset,\n );\n\n const {replicaVersion} = subscriptionState;\n return new ChangeStreamerImpl(\n lc,\n shard,\n taskID,\n discoveryAddress,\n discoveryProtocol,\n changeDB,\n replicaVersion,\n changeSource,\n autoReset,\n backPressureLimitHeapProportion,\n setTimeoutFn,\n );\n}\n\n/**\n * Internally all Downstream messages (not just commits) are given a watermark.\n * These are used for internal ordering for:\n * 1. Replaying new changes in the Storer\n * 2. Filtering old changes in the Subscriber\n *\n * However, only the watermark for `Commit` messages are exposed to\n * subscribers, as that is the only semantically correct watermark to\n * use for tracking a position in a replication stream.\n */\nexport type WatermarkedChange = [watermark: string, ChangeStreamData];\n\n/**\n * Upstream-agnostic dispatch of messages in a {@link ChangeStreamMessage} to a\n * {@link Forwarder} and {@link Storer} to execute the forward-store-ack\n * procedure described in {@link ChangeStreamer}.\n *\n * ### Subscriber Catchup\n *\n * Connecting clients first need to be \"caught up\" to the current watermark\n * (from stored change log entries) before new entries are forwarded to\n * them. This is non-trivial because the replication stream may be in the\n * middle of a pending streamed Transaction for which some entries have\n * already been forwarded but are not yet committed to the store.\n *\n *\n * ```\n * ------------------------------- - - - - - - - - - - - - - - - - - - -\n * | Historic changes in storage | Pending (streamed) tx | Next tx\n * ------------------------------- - - - - - - - - - - - - - - - - - - -\n * Replication stream\n * > > > > > > > > >\n * ^ ---> required catchup ---> ^\n * Subscriber watermark Subscription begins\n * ```\n *\n * Preemptively buffering the changes of every pending transaction\n * would be wasteful and consume too much memory for large transactions.\n *\n * Instead, the streamer synchronously dispatches changes and subscriptions\n * to the {@link Forwarder} and the {@link Storer} such that the two\n * components are aligned as to where in the stream the subscription started.\n * The two components then coordinate catchup and handoff via the\n * {@link Subscriber} object with the following algorithm:\n *\n * * If the streamer is in the middle of a pending Transaction, the\n * Subscriber is \"queued\" on both the Forwarder and the Storer. In this\n * state, new changes are *not* forwarded to the Subscriber, and catchup\n * is not yet executed.\n * * Once the commit message for the pending Transaction is processed\n * by the Storer, it begins catchup on the Subscriber (with a READONLY\n * snapshot so that it does not block subsequent storage operations).\n * This catchup is thus guaranteed to load the change log entries of\n * that last Transaction.\n * * When the Forwarder processes that same commit message, it moves the\n * Subscriber from the \"queued\" to the \"active\" set of clients such that\n * the Subscriber begins receiving new changes, starting from the next\n * Transaction.\n * * The Subscriber does not forward those changes, however, if its catchup\n * is not complete. Until then, it buffers the changes in memory.\n * * Once catchup is complete, the buffered changes are immediately sent\n * and the Subscriber henceforth forwards changes as they are received.\n *\n * In the (common) case where the streamer is not in the middle of a pending\n * transaction when a subscription begins, the Storer begins catchup\n * immediately and the Forwarder directly adds the Subscriber to its active\n * set. However, the Subscriber still buffers any forwarded messages until\n * its catchup is complete.\n *\n * ### Watermarks and ordering\n *\n * The ChangeStreamerService depends on its {@link ChangeSource} to send\n * changes in contiguous [`begin`, `data` ..., `data`, `commit`] sequences\n * in commit order. This follows Postgres's Logical Replication Protocol\n * Message Flow:\n *\n * https://www.postgresql.org/docs/16/protocol-logical-replication.html#PROTOCOL-LOGICAL-MESSAGES-FLOW\n *\n * > The logical replication protocol sends individual transactions one by one.\n * > This means that all messages between a pair of Begin and Commit messages belong to the same transaction.\n *\n * In order to correctly replay (new) and filter (old) messages to subscribers\n * at different points in the replication stream, these changes must be assigned\n * watermarks such that they preserve the order in which they were received\n * from the ChangeSource.\n *\n * A previous implementation incorrectly derived these watermarks from the Postgres\n * Log Sequence Numbers (LSN) of each message. However, LSNs from concurrent,\n * non-conflicting transactions can overlap, which can result in a `begin` message\n * with an earlier LSN arriving after a `commit` message. For example, the\n * changes for these transactions:\n *\n * ```\n * LSN: 1 2 3 4 5 6 7 8 9 10\n * tx1: begin data data data commit\n * tx2: begin data data data commit\n * ```\n *\n * will arrive as:\n *\n * ```\n * begin1, data2, data4, data6, commit8, begin3, data5, data7, data9, commit10\n * ```\n *\n * Thus, LSN of non-commit messages are not suitable for tracking the sorting\n * order of the replication stream.\n *\n * Instead, the ChangeStreamer uses the following algorithm for deterministic\n * catchup and filtering of changes:\n *\n * * A `commit` message is assigned to a watermark corresponding to its LSN.\n * These are guaranteed to be in commit order by definition.\n *\n * * `begin` and `data` messages are assigned to the watermark of the\n * preceding `commit` (the previous transaction, or the replication\n * slot's starting LSN) plus 1. This guarantees that they will be sorted\n * after the previously commit transaction even if their LSNs came before it.\n * This is referred to as the `preCommitWatermark`.\n *\n * * In the ChangeLog DB, messages have a secondary sort column `pos`, which is\n * the position of the message within its transaction, with the `begin` message\n * starting at `0`. This guarantees that `begin` and `data` messages will be\n * fetched in the original ChangeSource order during catchup.\n *\n * `begin` and `data` messages share the same watermark, but this is sufficient for\n * Subscriber filtering because subscribers only know about the `commit` watermarks\n * exposed in the `Downstream` `Commit` message. The Subscriber object thus compares\n * the internal watermarks of the incoming messages against the commit watermark of\n * the caller, updating the watermark at every `Commit` message that is forwarded.\n *\n * ### Cleanup\n *\n * As mentioned in the {@link ChangeStreamer} documentation: \"the ChangeStreamer\n * uses a combination of [the \"initial\", i.e. backup-derived watermark and] ACK\n * responses from connected subscribers to determine the watermark up\n * to which it is safe to purge old change log entries.\"\n *\n * More concretely:\n *\n * * The `initial`, backup-derived watermark is the earliest to which cleanup\n * should ever happen.\n *\n * * However, it is possible for the replica backup to be *ahead* of a connected\n * subscriber; and if a network error causes that subscriber to retry from its\n * last watermark, the change streamer must support it.\n *\n * Thus, before cleaning up to an `initial` backup-derived watermark, the change\n * streamer first confirms that all connected subscribers have also passed\n * that watermark.\n */\nclass ChangeStreamerImpl implements ChangeStreamerService {\n readonly id: string;\n readonly #lc: LogContext;\n readonly #shard: ShardID;\n readonly #changeDB: PostgresDB;\n readonly #replicaVersion: string;\n readonly #source: ChangeSource;\n readonly #storer: Storer;\n readonly #forwarder: Forwarder;\n\n readonly #autoReset: boolean;\n readonly #state: RunningState;\n readonly #initialWatermarks = new Set<string>();\n\n // Starting the (Postgres) ChangeStream results in killing the previous\n // Postgres subscriber, potentially creating a gap in which the old\n // change-streamer has shut down and the new change-streamer has not yet\n // been recognized as \"healthy\" (and thus does not get any requests).\n //\n // To minimize this gap, delay starting the ChangeStream until the first\n // request from a `serving` replicator, indicating that higher level\n // load-balancing / routing logic has begun routing requests to this task.\n readonly #serving = resolver();\n\n readonly #txCounter = getOrCreateCounter(\n 'replication',\n 'transactions',\n 'Count of replicated transactions',\n );\n\n #stream: ChangeStream | undefined;\n\n constructor(\n lc: LogContext,\n shard: ShardID,\n taskID: string,\n discoveryAddress: string,\n discoveryProtocol: string,\n changeDB: PostgresDB,\n replicaVersion: string,\n source: ChangeSource,\n autoReset: boolean,\n backPressureLimitHeapProportion: number,\n setTimeoutFn = setTimeout,\n ) {\n this.id = `change-streamer`;\n this.#lc = lc.withContext('component', 'change-streamer');\n this.#shard = shard;\n this.#changeDB = changeDB;\n this.#replicaVersion = replicaVersion;\n this.#source = source;\n this.#storer = new Storer(\n lc,\n shard,\n taskID,\n discoveryAddress,\n discoveryProtocol,\n changeDB,\n replicaVersion,\n consumed => this.#stream?.acks.push(['status', consumed[1], consumed[2]]),\n err => this.stop(err),\n backPressureLimitHeapProportion,\n );\n this.#forwarder = new Forwarder();\n this.#autoReset = autoReset;\n this.#state = new RunningState(this.id, undefined, setTimeoutFn);\n }\n\n async run() {\n this.#lc.info?.('starting change stream');\n\n // Once this change-streamer acquires \"ownership\" of the change DB,\n // it is safe to start the storer.\n await this.#storer.assumeOwnership();\n\n // The threshold in (estimated number of) bytes to send() on subscriber\n // websockets before `await`-ing the I/O buffers to be ready for more.\n const flushBytesThreshold = getDefaultHighWaterMark(false);\n\n while (this.#state.shouldRun()) {\n let err: unknown;\n let watermark: string | null = null;\n let unflushedBytes = 0;\n try {\n const {lastWatermark, backfillRequests} =\n await this.#storer.getStartStreamInitializationParameters();\n const stream = await this.#source.startStream(\n lastWatermark,\n backfillRequests,\n );\n this.#storer.run().catch(e => stream.changes.cancel(e));\n\n this.#stream = stream;\n this.#state.resetBackoff();\n watermark = null;\n\n for await (const change of stream.changes) {\n const [type, msg] = change;\n switch (type) {\n case 'status':\n if (msg.ack) {\n this.#storer.status(change); // storer acks once it gets through its queue\n }\n continue;\n case 'control':\n await this.#handleControlMessage(msg);\n continue; // control messages are not stored/forwarded\n case 'begin':\n watermark = change[2].commitWatermark;\n break;\n case 'commit':\n if (watermark !== change[2].watermark) {\n throw new UnrecoverableError(\n `commit watermark ${change[2].watermark} does not match 'begin' watermark ${watermark}`,\n );\n }\n this.#txCounter.add(1);\n break;\n default:\n if (watermark === null) {\n throw new UnrecoverableError(\n `${type} change (${msg.tag}) received before 'begin' message`,\n );\n }\n break;\n }\n\n unflushedBytes += this.#storer.store([watermark, change]);\n const sent = this.#forwarder.forward([watermark, change]);\n if (unflushedBytes >= flushBytesThreshold) {\n // Wait for messages to clear socket buffers to ensure that they\n // make their way to subscribers. Without this `await`, the\n // messages end up being buffered in this process, which:\n // (1) results in memory pressure and increased GC activity\n // (2) prevents subscribers from processing the messages as they\n // arrive, instead getting them in a large batch after being\n // idle while they were queued (causing further delays).\n await sent;\n unflushedBytes = 0;\n }\n\n if (type === 'commit' || type === 'rollback') {\n watermark = null;\n }\n\n // Allow the storer to exert back pressure.\n const readyForMore = this.#storer.readyForMore();\n if (readyForMore) {\n await readyForMore;\n }\n }\n } catch (e) {\n err = e;\n } finally {\n this.#stream?.changes.cancel();\n this.#stream = undefined;\n }\n\n // When the change stream is interrupted, abort any pending transaction.\n if (watermark) {\n this.#lc.warn?.(`aborting interrupted transaction ${watermark}`);\n this.#storer.abort();\n await this.#forwarder.forward([\n watermark,\n ['rollback', {tag: 'rollback'}],\n ]);\n }\n\n // Backoff and drain any pending entries in the storer before reconnecting.\n await Promise.all([\n this.#storer.stop(),\n this.#state.backoff(this.#lc, err),\n this.#state.retryDelay > 5000\n ? publishCriticalEvent(\n this.#lc,\n replicationStatusError(this.#lc, 'Replicating', err),\n )\n : promiseVoid,\n ]);\n }\n this.#lc.info?.('ChangeStreamer stopped');\n }\n\n async #handleControlMessage(msg: ChangeStreamControl[1]) {\n this.#lc.info?.('received control message', msg);\n const {tag} = msg;\n\n switch (tag) {\n case 'reset-required':\n await markResetRequired(this.#changeDB, this.#shard);\n await publishReplicationError(\n this.#lc,\n 'Replicating',\n msg.message ?? 'Resync required',\n msg.errorDetails,\n );\n if (this.#autoReset) {\n this.#lc.warn?.('shutting down for auto-reset');\n await this.stop(new AutoResetSignal());\n }\n break;\n default:\n unreachable(tag);\n }\n }\n\n subscribe(ctx: SubscriberContext): Promise<Source<Downstream>> {\n const {protocolVersion, id, mode, replicaVersion, watermark, initial} = ctx;\n if (mode === 'serving') {\n this.#serving.resolve();\n }\n const downstream = Subscription.create<Downstream>({\n cleanup: () => this.#forwarder.remove(subscriber),\n });\n const subscriber = new Subscriber(\n protocolVersion,\n id,\n watermark,\n downstream,\n );\n if (replicaVersion !== this.#replicaVersion) {\n this.#lc.warn?.(\n `rejecting subscriber at replica version ${replicaVersion}`,\n );\n subscriber.close(\n ErrorType.WrongReplicaVersion,\n `current replica version is ${\n this.#replicaVersion\n } (requested ${replicaVersion})`,\n );\n } else {\n this.#lc.debug?.(`adding subscriber ${subscriber.id}`);\n\n this.#forwarder.add(subscriber);\n this.#storer.catchup(subscriber, mode);\n\n if (initial) {\n this.scheduleCleanup(watermark);\n }\n }\n return Promise.resolve(downstream);\n }\n\n scheduleCleanup(watermark: string) {\n const origSize = this.#initialWatermarks.size;\n this.#initialWatermarks.add(watermark);\n\n if (origSize === 0) {\n this.#state.setTimeout(() => this.#purgeOldChanges(), CLEANUP_DELAY_MS);\n }\n }\n\n async getChangeLogState(): Promise<{\n replicaVersion: string;\n minWatermark: string;\n }> {\n const minWatermark = await this.#storer.getMinWatermarkForCatchup();\n if (!minWatermark) {\n this.#lc.warn?.(\n `Unexpected empty changeLog. Resync if \"Local replica watermark\" errors arise`,\n );\n }\n return {\n replicaVersion: this.#replicaVersion,\n minWatermark: minWatermark ?? this.#replicaVersion,\n };\n }\n\n /**\n * Makes a best effort to purge the change log. In the event of a database\n * error, exceptions will be logged and swallowed, so this method is safe\n * to run in a timeout.\n */\n async #purgeOldChanges(): Promise<void> {\n const initial = [...this.#initialWatermarks];\n if (initial.length === 0) {\n this.#lc.warn?.('No initial watermarks to check for cleanup'); // Not expected.\n return;\n }\n const current = [...this.#forwarder.getAcks()];\n if (current.length === 0) {\n // Also not expected, but possible (e.g. subscriber connects, then disconnects).\n // Bail to be safe.\n this.#lc.warn?.('No subscribers to confirm cleanup');\n return;\n }\n try {\n const earliestInitial = min(...(initial as AtLeastOne<LexiVersion>));\n const earliestCurrent = min(...(current as AtLeastOne<LexiVersion>));\n if (earliestCurrent < earliestInitial) {\n this.#lc.info?.(\n `At least one client is behind backup (${earliestCurrent} < ${earliestInitial})`,\n );\n } else {\n this.#lc.info?.(`Purging changes before ${earliestInitial} ...`);\n const start = performance.now();\n const deleted = await this.#storer.purgeRecordsBefore(earliestInitial);\n const elapsed = (performance.now() - start).toFixed(2);\n this.#lc.info?.(\n `Purged ${deleted} changes before ${earliestInitial} (${elapsed} ms)`,\n );\n this.#initialWatermarks.delete(earliestInitial);\n }\n } catch (e) {\n this.#lc.warn?.(`error purging change log`, e);\n } finally {\n if (this.#initialWatermarks.size) {\n // If there are unpurged watermarks to check, schedule the next purge.\n this.#state.setTimeout(() => this.#purgeOldChanges(), CLEANUP_DELAY_MS);\n }\n }\n }\n\n async stop(err?: unknown) {\n this.#state.stop(this.#lc, err);\n this.#stream?.changes.cancel();\n await this.#storer.stop();\n }\n}\n\n// The delay between receiving an initial, backup-based watermark\n// and performing a check of whether to purge records before it.\n// This delay should be long enough to handle situations like the following:\n//\n// 1. `litestream restore` downloads a backup for the `replication-manager`\n// 2. `replication-manager` starts up and runs this `change-streamer`\n// 3. `zero-cache`s that are running on a different replica connect to this\n// `change-streamer` after exponential backoff retries.\n//\n// It is possible for a `zero-cache`[3] to be behind the backup restored [1].\n// This cleanup delay (30 seconds) is thus set to be a value comfortably\n// longer than the max delay for exponential backoff (10 seconds) in\n// `services/running-state.ts`. This allows the `zero-cache` [3] to reconnect\n// so that the `change-streamer` can track its progress and know when it has\n// surpassed the initial watermark of the backup [1].\nconst CLEANUP_DELAY_MS = DEFAULT_MAX_RETRY_DELAY_MS * 3;\n"],"names":["ErrorType.WrongReplicaVersion"],"mappings":"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"}
@@ -193,6 +193,11 @@ export declare const downstreamSchema: v.UnionType<[v.TupleType<[v.Type<"status"
193
193
  columns: v.ArrayType<v.Type<string>>;
194
194
  watermark: v.Type<string>;
195
195
  rowValues: v.ArrayType<v.ArrayType<v.Type<import("../../../../shared/src/bigint-json.ts").JSONValue>>>;
196
+ status: v.Optional<{
197
+ totalBytes?: number | undefined;
198
+ rows: number;
199
+ totalRows: number;
200
+ }>;
196
201
  }, undefined>]>, v.UnionType<[v.ObjectType<{
197
202
  tag: v.Type<"create-table">;
198
203
  spec: v.ObjectType<Omit<{
@@ -331,6 +336,11 @@ export declare const downstreamSchema: v.UnionType<[v.TupleType<[v.Type<"status"
331
336
  }, undefined>;
332
337
  columns: v.ArrayType<v.Type<string>>;
333
338
  watermark: v.Type<string>;
339
+ status: v.Optional<{
340
+ totalBytes?: number | undefined;
341
+ rows: number;
342
+ totalRows: number;
343
+ }>;
334
344
  }, undefined>]>]>]>, v.TupleType<[v.Type<"commit">, v.ObjectType<{
335
345
  tag: v.Type<"commit">;
336
346
  }, undefined>, v.ObjectType<{
@@ -1 +1 @@
1
- {"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,kCAAkC,CAAC;AACtD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,2CAA2C,CAAC;AAEtE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,SAAS,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;CAChE;AAqBD,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;OAEG;IACH,eAAe,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAEtB;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;;;OAIG;IACH,IAAI,EAAE,cAAc,CAAC;IAErB;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY;;aAEvB,CAAC;AAEH,eAAO,MAAM,mBAAmB;;eAA+C,CAAC;AAEhF;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,QAAA,MAAM,uBAAuB;;;aAG3B,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,QAAA,MAAM,WAAW;;;eAAyD,CAAC;AAE3E,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAI5B,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,MAAM,WAAW,qBAAsB,SAAQ,cAAc,EAAE,OAAO;IACpE;;;;OAIG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzC,iBAAiB,IAAI,OAAO,CAAC;QAC3B,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;CACJ"}
1
+ {"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,kCAAkC,CAAC;AACtD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,2CAA2C,CAAC;AAEtE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,SAAS,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;CAChE;AAqBD,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;OAEG;IACH,eAAe,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAEtB;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;;;OAIG;IACH,IAAI,EAAE,cAAc,CAAC;IAErB;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY;;aAEvB,CAAC;AAEH,eAAO,MAAM,mBAAmB;;eAA+C,CAAC;AAEhF;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,QAAA,MAAM,uBAAuB;;;aAG3B,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,QAAA,MAAM,WAAW;;;eAAyD,CAAC;AAE3E,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAI5B,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,MAAM,WAAW,qBAAsB,SAAQ,cAAc,EAAE,OAAO;IACpE;;;;OAIG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzC,iBAAiB,IAAI,OAAO,CAAC;QAC3B,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;CACJ"}
@@ -1,10 +1,12 @@
1
1
  import type { LogContext } from '@rocicorp/logger';
2
+ import type { DownloadStatus } from '../../../../zero-events/src/status.ts';
2
3
  import type { StatementRunner } from '../../db/statements.ts';
3
4
  import type { ChangeStreamData } from '../change-source/protocol/current/downstream.ts';
4
5
  import type { ReplicatorMode } from './replicator.ts';
5
6
  export type ChangeProcessorMode = ReplicatorMode | 'initial-sync';
6
7
  export type CommitResult = {
7
8
  watermark: string;
9
+ completedBackfill: DownloadStatus | undefined;
8
10
  schemaUpdated: boolean;
9
11
  changeLogUpdated: boolean;
10
12
  };
@@ -1 +1 @@
1
- {"version":3,"file":"change-processor.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/replicator/change-processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAuBjD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,wBAAwB,CAAC;AAgC5D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iDAAiD,CAAC;AACtF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,iBAAiB,CAAC;AASpD,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG,cAAc,CAAC;AAElE,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;;gBAiBxB,EAAE,EAAE,eAAe,EACnB,IAAI,EAAE,mBAAmB,EACzB,WAAW,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI;IAuBrD,KAAK,CAAC,EAAE,EAAE,UAAU;IAIpB,8CAA8C;IAC9C,cAAc,CACZ,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,gBAAgB,GAC3B,YAAY,GAAG,IAAI;CA6JvB"}
1
+ {"version":3,"file":"change-processor.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/replicator/change-processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAMjD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uCAAuC,CAAC;AAkB1E,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,wBAAwB,CAAC;AAgC5D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iDAAiD,CAAC;AACtF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,iBAAiB,CAAC;AASpD,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG,cAAc,CAAC;AAElE,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,cAAc,GAAG,SAAS,CAAC;IAC9C,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;;gBAiBxB,EAAE,EAAE,eAAe,EACnB,IAAI,EAAE,mBAAmB,EACzB,WAAW,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI;IAuBrD,KAAK,CAAC,EAAE,EAAE,UAAU;IAIpB,8CAA8C;IAC9C,cAAc,CACZ,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,gBAAgB,GAC3B,YAAY,GAAG,IAAI;CAyJvB"}
@@ -109,11 +109,7 @@ class ChangeProcessor {
109
109
  if (msg.tag === "commit") {
110
110
  this.#currentTx = null;
111
111
  assert(watermark, "watermark is required for commit messages");
112
- const { schemaUpdated, changeLogUpdated } = tx.processCommit(
113
- msg,
114
- watermark
115
- );
116
- return { watermark, schemaUpdated, changeLogUpdated };
112
+ return tx.processCommit(msg, watermark);
117
113
  }
118
114
  if (msg.tag === "rollback") {
119
115
  this.#currentTx?.abort(lc);
@@ -554,7 +550,8 @@ class TransactionProcessor {
554
550
  `backfilled ${backfilled} rows (skipped ${skipped}) into ${tableName}`
555
551
  );
556
552
  }
557
- processBackfillCompleted({ relation, columns }) {
553
+ #completedBackfill;
554
+ processBackfillCompleted({ relation, columns, status }) {
558
555
  const tableName = liteTableName(relation);
559
556
  const rowKeyCols = relation.rowKey.columns;
560
557
  const cols = [...rowKeyCols, ...columns];
@@ -563,6 +560,9 @@ class TransactionProcessor {
563
560
  columnMetadata.clearBackfilling(tableName, col);
564
561
  }
565
562
  this.#bumpVersions(tableName);
563
+ if (status) {
564
+ this.#completedBackfill = { table: tableName, columns: cols, ...status };
565
+ }
566
566
  this.#lc.info?.(`finished backfilling ${tableName}`);
567
567
  }
568
568
  processCommit(commit, watermark) {
@@ -585,6 +585,8 @@ class TransactionProcessor {
585
585
  const elapsedMs = Date.now() - this.#startMs;
586
586
  this.#lc.debug?.(`Committed tx@${this.#version} (${elapsedMs} ms)`);
587
587
  return {
588
+ watermark,
589
+ completedBackfill: this.#completedBackfill,
588
590
  schemaUpdated: this.#schemaChanged,
589
591
  changeLogUpdated: this.#numChangeLogEntries > 0
590
592
  };