@rocicorp/zero 1.11.0-canary.35 → 1.11.0-canary.37

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 (130) hide show
  1. package/out/zero/package.js +1 -1
  2. package/out/zero/package.js.map +1 -1
  3. package/out/zero-cache/src/config/zero-config.d.ts +28 -0
  4. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  5. package/out/zero-cache/src/config/zero-config.js +83 -0
  6. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  7. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  8. package/out/zero-cache/src/server/change-streamer.js +2 -1
  9. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  10. package/out/zero-cache/src/server/main.d.ts.map +1 -1
  11. package/out/zero-cache/src/server/main.js +5 -1
  12. package/out/zero-cache/src/server/main.js.map +1 -1
  13. package/out/zero-cache/src/server/replicator.d.ts +4 -0
  14. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  15. package/out/zero-cache/src/server/replicator.js +18 -4
  16. package/out/zero-cache/src/server/replicator.js.map +1 -1
  17. package/out/zero-cache/src/server/runner/run-worker.d.ts.map +1 -1
  18. package/out/zero-cache/src/server/runner/run-worker.js +12 -6
  19. package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
  20. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  21. package/out/zero-cache/src/server/syncer.js +17 -1
  22. package/out/zero-cache/src/server/syncer.js.map +1 -1
  23. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +1 -1
  24. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  25. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +27 -7
  26. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -1
  27. package/out/zero-cache/src/services/change-source/pg/replication-slots.js +124 -62
  28. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  29. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +9 -3
  30. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  31. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +19 -6
  32. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  33. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +8 -0
  34. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  35. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +131 -62
  36. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  37. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +124 -2
  38. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  39. package/out/zero-cache/src/services/change-streamer/change-streamer.js +33 -2
  40. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  41. package/out/zero-cache/src/services/change-streamer/drain-tracker.d.ts +24 -0
  42. package/out/zero-cache/src/services/change-streamer/drain-tracker.d.ts.map +1 -0
  43. package/out/zero-cache/src/services/change-streamer/drain-tracker.js +67 -0
  44. package/out/zero-cache/src/services/change-streamer/drain-tracker.js.map +1 -0
  45. package/out/zero-cache/src/services/change-streamer/restore-admission.d.ts +214 -0
  46. package/out/zero-cache/src/services/change-streamer/restore-admission.d.ts.map +1 -0
  47. package/out/zero-cache/src/services/change-streamer/restore-admission.js +294 -0
  48. package/out/zero-cache/src/services/change-streamer/restore-admission.js.map +1 -0
  49. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +13 -1
  50. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +1 -1
  51. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js +7 -6
  52. package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -1
  53. package/out/zero-cache/src/services/change-streamer/snapshot.d.ts +2 -2
  54. package/out/zero-cache/src/services/change-streamer/snapshot.d.ts.map +1 -1
  55. package/out/zero-cache/src/services/change-streamer/snapshot.js +2 -2
  56. package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
  57. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +39 -0
  58. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts.map +1 -1
  59. package/out/zero-cache/src/services/change-streamer/subscribe.js +19 -2
  60. package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -1
  61. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +22 -1
  62. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  63. package/out/zero-cache/src/services/change-streamer/subscriber.js +41 -15
  64. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  65. package/out/zero-cache/src/services/http-service.d.ts +6 -0
  66. package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
  67. package/out/zero-cache/src/services/http-service.js +14 -2
  68. package/out/zero-cache/src/services/http-service.js.map +1 -1
  69. package/out/zero-cache/src/services/life-cycle.d.ts +33 -0
  70. package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
  71. package/out/zero-cache/src/services/life-cycle.js +56 -10
  72. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  73. package/out/zero-cache/src/services/litestream/commands.d.ts +8 -0
  74. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  75. package/out/zero-cache/src/services/litestream/commands.js +22 -1
  76. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  77. package/out/zero-cache/src/services/replicator/drain-handler.d.ts +74 -0
  78. package/out/zero-cache/src/services/replicator/drain-handler.d.ts.map +1 -0
  79. package/out/zero-cache/src/services/replicator/drain-handler.js +185 -0
  80. package/out/zero-cache/src/services/replicator/drain-handler.js.map +1 -0
  81. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +2 -1
  82. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
  83. package/out/zero-cache/src/services/replicator/incremental-sync.js +66 -13
  84. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  85. package/out/zero-cache/src/services/replicator/replicator.d.ts +2 -1
  86. package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
  87. package/out/zero-cache/src/services/replicator/replicator.js +2 -2
  88. package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
  89. package/out/zero-cache/src/services/view-syncer/batch-pacer.d.ts +79 -0
  90. package/out/zero-cache/src/services/view-syncer/batch-pacer.d.ts.map +1 -0
  91. package/out/zero-cache/src/services/view-syncer/batch-pacer.js +249 -0
  92. package/out/zero-cache/src/services/view-syncer/batch-pacer.js.map +1 -0
  93. package/out/zero-cache/src/services/view-syncer/cvr.d.ts +27 -1
  94. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  95. package/out/zero-cache/src/services/view-syncer/cvr.js +107 -17
  96. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  97. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +85 -3
  98. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  99. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +301 -20
  100. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  101. package/out/zero-cache/src/services/view-syncer/push-cost.d.ts +132 -0
  102. package/out/zero-cache/src/services/view-syncer/push-cost.d.ts.map +1 -0
  103. package/out/zero-cache/src/services/view-syncer/push-cost.js +164 -0
  104. package/out/zero-cache/src/services/view-syncer/push-cost.js.map +1 -0
  105. package/out/zero-cache/src/services/view-syncer/push-cutoff.d.ts +55 -0
  106. package/out/zero-cache/src/services/view-syncer/push-cutoff.d.ts.map +1 -0
  107. package/out/zero-cache/src/services/view-syncer/push-cutoff.js +56 -0
  108. package/out/zero-cache/src/services/view-syncer/push-cutoff.js.map +1 -0
  109. package/out/zero-cache/src/services/view-syncer/query-stats.d.ts +7 -0
  110. package/out/zero-cache/src/services/view-syncer/query-stats.d.ts.map +1 -1
  111. package/out/zero-cache/src/services/view-syncer/query-stats.js +20 -1
  112. package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -1
  113. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +136 -5
  114. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  115. package/out/zero-cache/src/services/view-syncer/snapshotter.js +246 -18
  116. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  117. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +8 -2
  118. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  119. package/out/zero-cache/src/services/view-syncer/view-syncer.js +381 -16
  120. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  121. package/out/zero-cache/src/types/processes.d.ts +8 -0
  122. package/out/zero-cache/src/types/processes.d.ts.map +1 -1
  123. package/out/zero-cache/src/types/processes.js.map +1 -1
  124. package/out/zero-client/src/client/version.js +1 -1
  125. package/out/zero-react/src/use-query.js +1 -1
  126. package/out/zero-react/src/use-query.js.map +1 -1
  127. package/out/zero-server/src/queries/process-queries.d.ts.map +1 -1
  128. package/out/zero-server/src/queries/process-queries.js +1 -1
  129. package/out/zero-server/src/queries/process-queries.js.map +1 -1
  130. package/package.json +1 -1
@@ -9,7 +9,7 @@ import type { Service } from '../service.ts';
9
9
  import type { PreSerializedBatch } from './broadcast.ts';
10
10
  import * as ErrorType from './error-type-enum.ts';
11
11
  import type { SnapshotMessage } from './snapshot-message.ts';
12
- import type { SubscribeDownstream } from './subscribe.ts';
12
+ import type { LocalReplica, SubscribeDownstream } from './subscribe.ts';
13
13
  type ErrorType = Enum<typeof ErrorType>;
14
14
  export type ChangeTag = ChangeStreamData[1]['tag'];
15
15
  /**
@@ -109,6 +109,21 @@ export type SubscriberContext = {
109
109
  * subscribers as of protocol v8. `undefined` for older subscribers.
110
110
  */
111
111
  backfills?: BackfillRequest[] | undefined;
112
+ /**
113
+ * Whether the subscriber understands the `draining` status, i.e. it will
114
+ * switch over to another replication-manager when the current one shuts
115
+ * down. A draining replication-manager waits for drainable subscribers to
116
+ * leave before exiting.
117
+ */
118
+ drainable?: boolean | undefined;
119
+ /**
120
+ * Whether the subscriber is switching over from another subscription with
121
+ * a replica that it is currently serving. If the replica version is
122
+ * incompatible, such a subscriber is held (see {@link Status.restore})
123
+ * rather than being immediately rejected. Subscribers that are booting up
124
+ * never set this.
125
+ */
126
+ switchover?: boolean | undefined;
112
127
  };
113
128
  /**
114
129
  * The StatusMessage payload for now is empty, but can be extended to
@@ -124,6 +139,33 @@ export declare const statusSchema: v.ObjectType<{
124
139
  } | undefined;
125
140
  nextSendTimeMs: number;
126
141
  }>;
142
+ /**
143
+ * Sent by a replication-manager that is shutting down to its (drainable)
144
+ * serving subscribers. The subscriber should switch over to another
145
+ * replication-manager (see {@link SubscriberContext.switchover}) while
146
+ * continuing to consume this subscription until the switchover succeeds.
147
+ */
148
+ draining: v.Optional<boolean>;
149
+ /**
150
+ * Sent on a {@link SubscriberContext.switchover switchover} subscription
151
+ * to a replication-manager running an incompatible replica version.
152
+ * Rather than being immediately rejected with
153
+ * {@link ErrorType.WrongReplicaVersion}, the subscriber is held until it
154
+ * is granted permission to restore the replication-manager's backup,
155
+ * which limits the number of subscribers that are restoring (and thus not
156
+ * serving) at the same time.
157
+ *
158
+ * * `queued`: the subscriber is waiting for permission to restore. It
159
+ * should continue consuming its existing subscription (if any).
160
+ * * `granted`: the subscriber should (gracefully) restart to restore a
161
+ * new replica.
162
+ */
163
+ restore: v.Optional<{
164
+ state: "queued";
165
+ position: number;
166
+ } | {
167
+ state: "granted";
168
+ }>;
127
169
  }, undefined>;
128
170
  export type Status = v.Infer<typeof statusSchema>;
129
171
  export declare const statusMessageSchema: v.TupleType<[v.Type<"status">, v.ObjectType<{
@@ -136,6 +178,33 @@ export declare const statusMessageSchema: v.TupleType<[v.Type<"status">, v.Objec
136
178
  } | undefined;
137
179
  nextSendTimeMs: number;
138
180
  }>;
181
+ /**
182
+ * Sent by a replication-manager that is shutting down to its (drainable)
183
+ * serving subscribers. The subscriber should switch over to another
184
+ * replication-manager (see {@link SubscriberContext.switchover}) while
185
+ * continuing to consume this subscription until the switchover succeeds.
186
+ */
187
+ draining: v.Optional<boolean>;
188
+ /**
189
+ * Sent on a {@link SubscriberContext.switchover switchover} subscription
190
+ * to a replication-manager running an incompatible replica version.
191
+ * Rather than being immediately rejected with
192
+ * {@link ErrorType.WrongReplicaVersion}, the subscriber is held until it
193
+ * is granted permission to restore the replication-manager's backup,
194
+ * which limits the number of subscribers that are restoring (and thus not
195
+ * serving) at the same time.
196
+ *
197
+ * * `queued`: the subscriber is waiting for permission to restore. It
198
+ * should continue consuming its existing subscription (if any).
199
+ * * `granted`: the subscriber should (gracefully) restart to restore a
200
+ * new replica.
201
+ */
202
+ restore: v.Optional<{
203
+ state: "queued";
204
+ position: number;
205
+ } | {
206
+ state: "granted";
207
+ }>;
139
208
  }, undefined>]>;
140
209
  /**
141
210
  * A StatusMessage will be immediately sent on a (v2+) subscription to
@@ -147,11 +216,23 @@ export type StatusMessage = v.Infer<typeof statusMessageSchema>;
147
216
  declare const subscriptionErrorSchema: v.ObjectType<{
148
217
  type: v.Type<number>;
149
218
  message: v.Optional<string>;
219
+ /**
220
+ * The replica version of the replication-manager, sent with
221
+ * {@link ErrorType.WrongReplicaVersion} so that a subscriber with a newer
222
+ * replica can avoid restoring an older one.
223
+ */
224
+ replicaVersion: v.Optional<string>;
150
225
  }, undefined>;
151
226
  export type SubscriptionError = v.Infer<typeof subscriptionErrorSchema>;
152
227
  declare const errorSchema: v.TupleType<[v.Type<"error">, v.ObjectType<{
153
228
  type: v.Type<number>;
154
229
  message: v.Optional<string>;
230
+ /**
231
+ * The replica version of the replication-manager, sent with
232
+ * {@link ErrorType.WrongReplicaVersion} so that a subscriber with a newer
233
+ * replica can avoid restoring an older one.
234
+ */
235
+ replicaVersion: v.Optional<string>;
155
236
  }, undefined>]>;
156
237
  export declare const downstreamSchema: v.UnionType<[v.TupleType<[v.Type<"status">, v.ObjectType<{
157
238
  tag: v.Type<"status">;
@@ -163,6 +244,33 @@ export declare const downstreamSchema: v.UnionType<[v.TupleType<[v.Type<"status"
163
244
  } | undefined;
164
245
  nextSendTimeMs: number;
165
246
  }>;
247
+ /**
248
+ * Sent by a replication-manager that is shutting down to its (drainable)
249
+ * serving subscribers. The subscriber should switch over to another
250
+ * replication-manager (see {@link SubscriberContext.switchover}) while
251
+ * continuing to consume this subscription until the switchover succeeds.
252
+ */
253
+ draining: v.Optional<boolean>;
254
+ /**
255
+ * Sent on a {@link SubscriberContext.switchover switchover} subscription
256
+ * to a replication-manager running an incompatible replica version.
257
+ * Rather than being immediately rejected with
258
+ * {@link ErrorType.WrongReplicaVersion}, the subscriber is held until it
259
+ * is granted permission to restore the replication-manager's backup,
260
+ * which limits the number of subscribers that are restoring (and thus not
261
+ * serving) at the same time.
262
+ *
263
+ * * `queued`: the subscriber is waiting for permission to restore. It
264
+ * should continue consuming its existing subscription (if any).
265
+ * * `granted`: the subscriber should (gracefully) restart to restore a
266
+ * new replica.
267
+ */
268
+ restore: v.Optional<{
269
+ state: "queued";
270
+ position: number;
271
+ } | {
272
+ state: "granted";
273
+ }>;
166
274
  }, undefined>]>, v.UnionType<[v.TupleType<[v.Type<"begin">, v.ObjectType<{
167
275
  tag: v.Type<"begin">;
168
276
  json: v.Optional<"p" | "s">;
@@ -412,6 +520,12 @@ export declare const downstreamSchema: v.UnionType<[v.TupleType<[v.Type<"status"
412
520
  }, undefined>]>]>, v.TupleType<[v.Type<"error">, v.ObjectType<{
413
521
  type: v.Type<number>;
414
522
  message: v.Optional<string>;
523
+ /**
524
+ * The replica version of the replication-manager, sent with
525
+ * {@link ErrorType.WrongReplicaVersion} so that a subscriber with a newer
526
+ * replica can avoid restoring an older one.
527
+ */
528
+ replicaVersion: v.Optional<string>;
415
529
  }, undefined>]>]>;
416
530
  export type Error = v.Infer<typeof errorSchema>;
417
531
  export declare function errorTypeToReadableName(val: ErrorType): "Unknown" | "WatermarkTooOld" | "WrongReplicaVersion";
@@ -447,7 +561,15 @@ export interface ChangeStreamerService extends Omit<ChangeStreamer, 'subscribe'>
447
561
  * until the client with that `taskID` subscribes, or if the snapshot
448
562
  * connection terminates.
449
563
  */
450
- startSnapshotReservation(taskID: string): Promise<Source<SnapshotMessage>>;
564
+ startSnapshotReservation(taskID: string, opts?: {
565
+ localReplica?: LocalReplica | undefined;
566
+ }): Promise<Source<SnapshotMessage>>;
567
+ /**
568
+ * Signals (drainable) serving subscribers to switch over to another
569
+ * replication-manager, and resolves when they have all disconnected.
570
+ * Subscribers continue to be served in the meantime.
571
+ */
572
+ drain?(): Promise<void>;
451
573
  /**
452
574
  * Informs the change-streamer of the watermark up to which the replica has
453
575
  * been backed up. This serves two purposes:
@@ -1 +1 @@
1
- {"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer.ts"],"names":[],"mappings":"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"}
@@ -11,12 +11,43 @@ var statusSchema = valita_exports.object({
11
11
  lagReport: valita_exports.object({
12
12
  lastTimings: changeSourceTimingsSchema.optional(),
13
13
  nextSendTimeMs: valita_exports.number()
14
- }).optional()
14
+ }).optional(),
15
+ /**
16
+ * Sent by a replication-manager that is shutting down to its (drainable)
17
+ * serving subscribers. The subscriber should switch over to another
18
+ * replication-manager (see {@link SubscriberContext.switchover}) while
19
+ * continuing to consume this subscription until the switchover succeeds.
20
+ */
21
+ draining: valita_exports.boolean().optional(),
22
+ /**
23
+ * Sent on a {@link SubscriberContext.switchover switchover} subscription
24
+ * to a replication-manager running an incompatible replica version.
25
+ * Rather than being immediately rejected with
26
+ * {@link ErrorType.WrongReplicaVersion}, the subscriber is held until it
27
+ * is granted permission to restore the replication-manager's backup,
28
+ * which limits the number of subscribers that are restoring (and thus not
29
+ * serving) at the same time.
30
+ *
31
+ * * `queued`: the subscriber is waiting for permission to restore. It
32
+ * should continue consuming its existing subscription (if any).
33
+ * * `granted`: the subscriber should (gracefully) restart to restore a
34
+ * new replica.
35
+ */
36
+ restore: valita_exports.union(valita_exports.object({
37
+ state: valita_exports.literal("queued"),
38
+ position: valita_exports.number()
39
+ }), valita_exports.object({ state: valita_exports.literal("granted") })).optional()
15
40
  });
16
41
  var statusMessageSchema = valita_exports.tuple([valita_exports.literal("status"), statusSchema]);
17
42
  var subscriptionErrorSchema = valita_exports.object({
18
43
  type: valita_exports.number(),
19
- message: valita_exports.string().optional()
44
+ message: valita_exports.string().optional(),
45
+ /**
46
+ * The replica version of the replication-manager, sent with
47
+ * {@link ErrorType.WrongReplicaVersion} so that a subscriber with a newer
48
+ * replica can avoid restoring an older one.
49
+ */
50
+ replicaVersion: valita_exports.string().optional()
20
51
  });
21
52
  var errorSchema = valita_exports.tuple([valita_exports.literal("error"), subscriptionErrorSchema]);
22
53
  var downstreamSchema = valita_exports.union(statusMessageSchema, changeStreamDataSchema, errorSchema);
@@ -1 +1 @@
1
- {"version":3,"file":"change-streamer.js","names":[],"sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer.ts"],"sourcesContent":["import type {Enum} from '../../../../shared/src/enum.ts';\nimport * as v from '../../../../shared/src/valita.ts';\nimport type {PreSerialized, Sized, Source} from '../../types/streams.ts';\nimport type {Subscription} from '../../types/subscription.ts';\nimport {\n changeStreamDataSchema,\n type ChangeStreamData,\n} from '../change-source/protocol/current/downstream.ts';\nimport type {BackfillRequest} from '../change-source/protocol/current/upstream.ts';\nimport type {ReplicatorMode} from '../replicator/replicator.ts';\nimport {changeSourceTimingsSchema} from '../replicator/reporter/report-schema.ts';\nimport type {Service} from '../service.ts';\nimport type {PreSerializedBatch} from './broadcast.ts';\nimport * as ErrorType from './error-type-enum.ts';\nimport type {SnapshotMessage} from './snapshot-message.ts';\nimport type {SubscribeDownstream} from './subscribe.ts';\n\ntype ErrorType = Enum<typeof ErrorType>;\n\nexport type ChangeTag = ChangeStreamData[1]['tag'];\n\n/**\n * Internally all downstream messages (not just commits) are given a watermark.\n * These are used for internal ordering while replaying stored changes and\n * filtering changes already seen by a subscriber.\n *\n * Only commit watermarks are exposed to subscribers. The JSON string is the\n * canonical serialization shared by storage and live forwarding.\n */\nexport type WatermarkedChange = [\n watermark: string,\n tag: ChangeTag,\n json: string,\n];\n\n/**\n * The ChangeStreamer is the component between replicators (\"subscribers\")\n * and a canonical upstream source of changes (e.g. a Postgres logical\n * replication slot). It facilitates multiple subscribers without incurring\n * the associated upstream expense (e.g. PG replication slots are resource\n * intensive) with a \"forward-store-ack\" procedure.\n *\n * * Changes from the upstream source are immediately **forwarded** to\n * connected subscribers to minimize latency.\n *\n * * They are then **stored** in a separate DB to facilitate catchup\n * of connecting subscribers that are behind.\n *\n * * **Acknowledgements** are sent upstream after they are successfully\n * stored.\n *\n * Unlike Postgres replication slots, in which the progress of a static\n * subscriber is tracked in the replication slot, the ChangeStreamer\n * supports a dynamic set of subscribers (i.e.. zero-caches) that can\n * can continually change.\n *\n * However, it is not the case that the ChangeStreamer needs to support\n * arbitrarily old subscribers. Because the replica is continually\n * backed up to a global location and used to initialize new subscriber\n * tasks, an initial subscription request from a subscriber constitutes\n * a signal for how \"behind\" a new subscriber task can be. This is\n * reflected in the {@link SubscriberContext}, which indicates whether\n * the watermark corresponds to an \"initial\" watermark derived from the\n * replica at task startup.\n *\n * The ChangeStreamer uses a combination of this signal with ACK\n * responses from connected subscribers to determine the watermark up\n * to which it is safe to purge old change log entries.\n */\nexport interface ChangeStreamer {\n /**\n * Subscribes to changes based on the supplied subscriber `ctx`,\n * which indicates the watermark at which the subscriber is up to\n * date. Each result contains parsed, validated data and its approximate\n * serialized transport size.\n */\n subscribe(\n ctx: SubscriberContext,\n ): Promise<Source<Sized<SubscribeDownstream>>>;\n}\n\n// v1: v0.18\n// - Client-side support for JSON_FORMAT. Introduced in 0.18.\n// v2: v0.19\n// - Adds the \"status\" message which is initially used to signal that the\n// subscription is valid (i.e. starting at the requested watermark).\n// v3: v0.20\n// - Adds the \"taskID\" to the subscription context, and support for\n// the backup monitor-mediated \"/snapshot\" request.\n// v4: v0.25\n// - Adds the \"replicaVersion\" and \"minWatermark\" fields to the \"/snapshot\"\n// status request so that a subscriber can verify whether its replica,\n// whether it be restored or existing in a permanent volume, is compatible\n// with the change-streamer.\n// v5: v0.26\n// - Moves relation.keyColumns and relation.replicaIdentity to\n// relation.rowKey: { columns, type }.\n// - Adds `metadata` to `create-table` message\n// - Adds `tableMetadata` to `add-column` message\n// - Adds `table-update-metadata` message\n// v6: v0.26\n// - Adds support for `backfill` messages\n// v6: v1.0.1 (backwards compatible, no version change)\n// - Adds lag reporting to status messages\n// v6: (backwards compatible, no version change)\n// - Adds the optional `commitTimeMs` field to `commit` messages, carrying\n// the upstream commit timestamp for end-to-end serving lag measurement.\n// The stream is parsed in 'passthrough' mode, so an older peer ignores the\n// field, and a newer peer treats its absence (including in changes\n// replayed from the Change DB) as \"no commit time reported\".\n// v7: v1.12\n// - Merges the separate `/snapshot` and `/changes` connections into a single\n// bidirectional `/subscribe` connection (see subscribe.ts). The view-syncer\n// drives the connection with upstream `reserve-snapshot` / `start-subscription`\n// application messages; the change-streamer replies with a `reserved`\n// message and then the change stream on the same socket. This guarantees the\n// replication-manager that reserves the change log is the one that serves the\n// subscription. The legacy `/snapshot` + `/changes` endpoints remain for\n// v4-v6 subscribers during rollout.\n// - Drops the legacy \"initial\" and \"logsChangeStream\" query parameters from the\n// subscriber context\n// v8:\n// - Adds `backfills` to the `start-subscription` context, with which the\n// subscriber reports its pending backfills (and their progress).\n// - Adds `progressMarks` to `backfill` and `backfill-completed` messages,\n// which subscribers use to ignore backfill data that would otherwise\n// create a gap (see `acceptBackfill()`).\n// Both fields are optional in the schemas so that v7 peers (and changes\n// persisted in the change-log by v7 replication-managers) remain compatible,\n// but they should be considered non-optional as of v8.\n\nexport const PROTOCOL_VERSION = 8;\n\nexport type SubscriberContext = {\n /**\n * The supported change-streamer protocol version.\n */\n protocolVersion: number;\n\n /**\n * Task ID. This is used to link the request with a preceding snapshot\n * reservation.\n */\n taskID: string;\n\n /**\n * Subscriber id. This is only used for debugging.\n */\n id: string;\n\n /**\n * The ReplicatorMode of the subscriber. 'backup' indicates that the\n * subscriber is local to the `change-streamer` in the `replication-manager`,\n * while 'serving' indicates that user-facing requests depend on the subscriber.\n */\n mode: ReplicatorMode;\n\n /**\n * The ChangeStreamer will return an Error if the subscriber is\n * on a different replica version (i.e. the initial snapshot associated\n * with the replication slot).\n */\n replicaVersion: string;\n\n /**\n * The watermark up to which the subscriber is up to date.\n * Only changes after the watermark will be streamed.\n */\n watermark: string;\n\n /**\n * Whether the subscriber supports batched WebSocket frames.\n */\n wsBatched?: boolean | undefined;\n\n /**\n * The subscriber's pending backfills as of its `watermark`. Reported by\n * subscribers as of protocol v8. `undefined` for older subscribers.\n */\n backfills?: BackfillRequest[] | undefined;\n};\n\n/**\n * The StatusMessage payload for now is empty, but can be extended to\n * include meta-level information in the future.\n */\nexport const statusSchema = v.object({\n tag: v.literal('status'),\n\n lagReport: v\n .object({\n lastTimings: changeSourceTimingsSchema.optional(),\n nextSendTimeMs: v.number(),\n })\n .optional(),\n});\n\nexport type Status = v.Infer<typeof statusSchema>;\n\nexport const statusMessageSchema = v.tuple([v.literal('status'), statusSchema]);\n\n/**\n * A StatusMessage will be immediately sent on a (v2+) subscription to\n * indicate that the subscription is valid (i.e. starting at the requested\n * watermark). Invalid subscriptions will instead result in a\n * SubscriptionError as the first message.\n */\nexport type StatusMessage = v.Infer<typeof statusMessageSchema>;\n\nconst subscriptionErrorSchema = v.object({\n type: v.number(), // ErrorType\n message: v.string().optional(),\n});\n\nexport type SubscriptionError = v.Infer<typeof subscriptionErrorSchema>;\n\nconst errorSchema = v.tuple([v.literal('error'), subscriptionErrorSchema]);\n\nexport const downstreamSchema = v.union(\n statusMessageSchema,\n changeStreamDataSchema,\n errorSchema,\n);\n\nexport type Error = v.Infer<typeof errorSchema>;\n\nexport function errorTypeToReadableName(val: ErrorType) {\n switch (val) {\n case ErrorType.WrongReplicaVersion:\n return 'WrongReplicaVersion';\n case ErrorType.WatermarkTooOld:\n return 'WatermarkTooOld';\n case ErrorType.Unknown:\n return 'Unknown';\n default:\n return 'Unknown';\n }\n}\n\n/**\n * A stream of transactions, each starting with a {@link Begin} message,\n * containing one or more {@link Data} messages, and ending with a\n * {@link Commit} or {@link Rollback} message. The 'commit' tuple\n * includes a `watermark` that should be stored with the committed\n * data and used for resuming a subscription (e.g. in the\n * {@link SubscriberContext}).\n *\n * A {@link SubscriptionError} indicates an unrecoverable error that requires\n * manual intervention (e.g. configuration / operational error).\n */\nexport type Downstream = v.Infer<typeof downstreamSchema>;\n\n/** A downstream message and its approximate serialized transport size. */\nexport type SizedDownstream = Sized<Downstream>;\n\nexport interface ChangeStreamerService\n extends Omit<ChangeStreamer, 'subscribe'>, Service {\n /**\n * The server-side interface overrides `subscribe()` to return a stream\n * of already-stringified {@link Downstream} payloads or pre-serialized Buffers.\n *\n * If a `downstream` subscription is supplied it is used directly as the\n * returned Source. Otherwise, a new one is created.\n */\n subscribe(\n ctx: SubscriberContext,\n downstream?: Subscription<string | PreSerializedBatch>,\n ): Promise<Source<string | PreSerialized>>;\n\n /**\n * Starts a snapshot reservation to preserve change-log entries while\n * a soon-to-be-subscriber downloads the current replica backup. Once\n * the change-streamer knows that a backup is available\n * (via {@link trackBackupWatermark}), it sends a confirmation\n * SnapshotMessage and reserves the corresponding entries in the change-log\n * until the client with that `taskID` subscribes, or if the snapshot\n * connection terminates.\n */\n startSnapshotReservation(taskID: string): Promise<Source<SnapshotMessage>>;\n\n /**\n * Informs the change-streamer of the watermark up to which the replica has\n * been backed up. This serves two purposes:\n * - It serves as the maximum watermark up to which the change-log can be\n * purged. Note that the change-streamer also takes into account the\n * positions of its subscribers and snapshot reservations when purging\n * changes in the change-log.\n * - In RMv2, this ACK's the upstream change-source to allow its buffer of\n * changes (e.g. the replication_slot) to advance.\n */\n trackBackupWatermark(watermark: string): void;\n}\n"],"mappings":";;;;;;;;AA0LA,IAAa,eAAe,eAAE,OAAO;CACnC,KAAK,eAAE,QAAQ,QAAQ;CAEvB,WAAW,eACR,OAAO;EACN,aAAa,0BAA0B,SAAS;EAChD,gBAAgB,eAAE,OAAO;CAC3B,CAAC,EACA,SAAS;AACd,CAAC;AAID,IAAa,sBAAsB,eAAE,MAAM,CAAC,eAAE,QAAQ,QAAQ,GAAG,YAAY,CAAC;AAU9E,IAAM,0BAA0B,eAAE,OAAO;CACvC,MAAM,eAAE,OAAO;CACf,SAAS,eAAE,OAAO,EAAE,SAAS;AAC/B,CAAC;AAID,IAAM,cAAc,eAAE,MAAM,CAAC,eAAE,QAAQ,OAAO,GAAG,uBAAuB,CAAC;AAEzE,IAAa,mBAAmB,eAAE,MAChC,qBACA,wBACA,WACF;AAIA,SAAgB,wBAAwB,KAAgB;CACtD,QAAQ,KAAR;EACE,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,SACE,OAAO;CACX;AACF"}
1
+ {"version":3,"file":"change-streamer.js","names":[],"sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer.ts"],"sourcesContent":["import type {Enum} from '../../../../shared/src/enum.ts';\nimport * as v from '../../../../shared/src/valita.ts';\nimport type {PreSerialized, Sized, Source} from '../../types/streams.ts';\nimport type {Subscription} from '../../types/subscription.ts';\nimport {\n changeStreamDataSchema,\n type ChangeStreamData,\n} from '../change-source/protocol/current/downstream.ts';\nimport type {BackfillRequest} from '../change-source/protocol/current/upstream.ts';\nimport type {ReplicatorMode} from '../replicator/replicator.ts';\nimport {changeSourceTimingsSchema} from '../replicator/reporter/report-schema.ts';\nimport type {Service} from '../service.ts';\nimport type {PreSerializedBatch} from './broadcast.ts';\nimport * as ErrorType from './error-type-enum.ts';\nimport type {SnapshotMessage} from './snapshot-message.ts';\nimport type {LocalReplica, SubscribeDownstream} from './subscribe.ts';\n\ntype ErrorType = Enum<typeof ErrorType>;\n\nexport type ChangeTag = ChangeStreamData[1]['tag'];\n\n/**\n * Internally all downstream messages (not just commits) are given a watermark.\n * These are used for internal ordering while replaying stored changes and\n * filtering changes already seen by a subscriber.\n *\n * Only commit watermarks are exposed to subscribers. The JSON string is the\n * canonical serialization shared by storage and live forwarding.\n */\nexport type WatermarkedChange = [\n watermark: string,\n tag: ChangeTag,\n json: string,\n];\n\n/**\n * The ChangeStreamer is the component between replicators (\"subscribers\")\n * and a canonical upstream source of changes (e.g. a Postgres logical\n * replication slot). It facilitates multiple subscribers without incurring\n * the associated upstream expense (e.g. PG replication slots are resource\n * intensive) with a \"forward-store-ack\" procedure.\n *\n * * Changes from the upstream source are immediately **forwarded** to\n * connected subscribers to minimize latency.\n *\n * * They are then **stored** in a separate DB to facilitate catchup\n * of connecting subscribers that are behind.\n *\n * * **Acknowledgements** are sent upstream after they are successfully\n * stored.\n *\n * Unlike Postgres replication slots, in which the progress of a static\n * subscriber is tracked in the replication slot, the ChangeStreamer\n * supports a dynamic set of subscribers (i.e.. zero-caches) that can\n * can continually change.\n *\n * However, it is not the case that the ChangeStreamer needs to support\n * arbitrarily old subscribers. Because the replica is continually\n * backed up to a global location and used to initialize new subscriber\n * tasks, an initial subscription request from a subscriber constitutes\n * a signal for how \"behind\" a new subscriber task can be. This is\n * reflected in the {@link SubscriberContext}, which indicates whether\n * the watermark corresponds to an \"initial\" watermark derived from the\n * replica at task startup.\n *\n * The ChangeStreamer uses a combination of this signal with ACK\n * responses from connected subscribers to determine the watermark up\n * to which it is safe to purge old change log entries.\n */\nexport interface ChangeStreamer {\n /**\n * Subscribes to changes based on the supplied subscriber `ctx`,\n * which indicates the watermark at which the subscriber is up to\n * date. Each result contains parsed, validated data and its approximate\n * serialized transport size.\n */\n subscribe(\n ctx: SubscriberContext,\n ): Promise<Source<Sized<SubscribeDownstream>>>;\n}\n\n// v1: v0.18\n// - Client-side support for JSON_FORMAT. Introduced in 0.18.\n// v2: v0.19\n// - Adds the \"status\" message which is initially used to signal that the\n// subscription is valid (i.e. starting at the requested watermark).\n// v3: v0.20\n// - Adds the \"taskID\" to the subscription context, and support for\n// the backup monitor-mediated \"/snapshot\" request.\n// v4: v0.25\n// - Adds the \"replicaVersion\" and \"minWatermark\" fields to the \"/snapshot\"\n// status request so that a subscriber can verify whether its replica,\n// whether it be restored or existing in a permanent volume, is compatible\n// with the change-streamer.\n// v5: v0.26\n// - Moves relation.keyColumns and relation.replicaIdentity to\n// relation.rowKey: { columns, type }.\n// - Adds `metadata` to `create-table` message\n// - Adds `tableMetadata` to `add-column` message\n// - Adds `table-update-metadata` message\n// v6: v0.26\n// - Adds support for `backfill` messages\n// v6: v1.0.1 (backwards compatible, no version change)\n// - Adds lag reporting to status messages\n// v6: (backwards compatible, no version change)\n// - Adds the optional `commitTimeMs` field to `commit` messages, carrying\n// the upstream commit timestamp for end-to-end serving lag measurement.\n// The stream is parsed in 'passthrough' mode, so an older peer ignores the\n// field, and a newer peer treats its absence (including in changes\n// replayed from the Change DB) as \"no commit time reported\".\n// v7: v1.12\n// - Merges the separate `/snapshot` and `/changes` connections into a single\n// bidirectional `/subscribe` connection (see subscribe.ts). The view-syncer\n// drives the connection with upstream `reserve-snapshot` / `start-subscription`\n// application messages; the change-streamer replies with a `reserved`\n// message and then the change stream on the same socket. This guarantees the\n// replication-manager that reserves the change log is the one that serves the\n// subscription. The legacy `/snapshot` + `/changes` endpoints remain for\n// v4-v6 subscribers during rollout.\n// - Drops the legacy \"initial\" and \"logsChangeStream\" query parameters from the\n// subscriber context\n// v8:\n// - Adds `backfills` to the `start-subscription` context, with which the\n// subscriber reports its pending backfills (and their progress).\n// - Adds `progressMarks` to `backfill` and `backfill-completed` messages,\n// which subscribers use to ignore backfill data that would otherwise\n// create a gap (see `acceptBackfill()`).\n// Both fields are optional in the schemas so that v7 peers (and changes\n// persisted in the change-log by v7 replication-managers) remain compatible,\n// but they should be considered non-optional as of v8.\n\nexport const PROTOCOL_VERSION = 8;\n\nexport type SubscriberContext = {\n /**\n * The supported change-streamer protocol version.\n */\n protocolVersion: number;\n\n /**\n * Task ID. This is used to link the request with a preceding snapshot\n * reservation.\n */\n taskID: string;\n\n /**\n * Subscriber id. This is only used for debugging.\n */\n id: string;\n\n /**\n * The ReplicatorMode of the subscriber. 'backup' indicates that the\n * subscriber is local to the `change-streamer` in the `replication-manager`,\n * while 'serving' indicates that user-facing requests depend on the subscriber.\n */\n mode: ReplicatorMode;\n\n /**\n * The ChangeStreamer will return an Error if the subscriber is\n * on a different replica version (i.e. the initial snapshot associated\n * with the replication slot).\n */\n replicaVersion: string;\n\n /**\n * The watermark up to which the subscriber is up to date.\n * Only changes after the watermark will be streamed.\n */\n watermark: string;\n\n /**\n * Whether the subscriber supports batched WebSocket frames.\n */\n wsBatched?: boolean | undefined;\n\n /**\n * The subscriber's pending backfills as of its `watermark`. Reported by\n * subscribers as of protocol v8. `undefined` for older subscribers.\n */\n backfills?: BackfillRequest[] | undefined;\n\n /**\n * Whether the subscriber understands the `draining` status, i.e. it will\n * switch over to another replication-manager when the current one shuts\n * down. A draining replication-manager waits for drainable subscribers to\n * leave before exiting.\n */\n drainable?: boolean | undefined;\n\n /**\n * Whether the subscriber is switching over from another subscription with\n * a replica that it is currently serving. If the replica version is\n * incompatible, such a subscriber is held (see {@link Status.restore})\n * rather than being immediately rejected. Subscribers that are booting up\n * never set this.\n */\n switchover?: boolean | undefined;\n};\n\n/**\n * The StatusMessage payload for now is empty, but can be extended to\n * include meta-level information in the future.\n */\nexport const statusSchema = v.object({\n tag: v.literal('status'),\n\n lagReport: v\n .object({\n lastTimings: changeSourceTimingsSchema.optional(),\n nextSendTimeMs: v.number(),\n })\n .optional(),\n\n /**\n * Sent by a replication-manager that is shutting down to its (drainable)\n * serving subscribers. The subscriber should switch over to another\n * replication-manager (see {@link SubscriberContext.switchover}) while\n * continuing to consume this subscription until the switchover succeeds.\n */\n draining: v.boolean().optional(),\n\n /**\n * Sent on a {@link SubscriberContext.switchover switchover} subscription\n * to a replication-manager running an incompatible replica version.\n * Rather than being immediately rejected with\n * {@link ErrorType.WrongReplicaVersion}, the subscriber is held until it\n * is granted permission to restore the replication-manager's backup,\n * which limits the number of subscribers that are restoring (and thus not\n * serving) at the same time.\n *\n * * `queued`: the subscriber is waiting for permission to restore. It\n * should continue consuming its existing subscription (if any).\n * * `granted`: the subscriber should (gracefully) restart to restore a\n * new replica.\n */\n restore: v\n .union(\n v.object({state: v.literal('queued'), position: v.number()}),\n v.object({state: v.literal('granted')}),\n )\n .optional(),\n});\n\nexport type Status = v.Infer<typeof statusSchema>;\n\nexport const statusMessageSchema = v.tuple([v.literal('status'), statusSchema]);\n\n/**\n * A StatusMessage will be immediately sent on a (v2+) subscription to\n * indicate that the subscription is valid (i.e. starting at the requested\n * watermark). Invalid subscriptions will instead result in a\n * SubscriptionError as the first message.\n */\nexport type StatusMessage = v.Infer<typeof statusMessageSchema>;\n\nconst subscriptionErrorSchema = v.object({\n type: v.number(), // ErrorType\n message: v.string().optional(),\n /**\n * The replica version of the replication-manager, sent with\n * {@link ErrorType.WrongReplicaVersion} so that a subscriber with a newer\n * replica can avoid restoring an older one.\n */\n replicaVersion: v.string().optional(),\n});\n\nexport type SubscriptionError = v.Infer<typeof subscriptionErrorSchema>;\n\nconst errorSchema = v.tuple([v.literal('error'), subscriptionErrorSchema]);\n\nexport const downstreamSchema = v.union(\n statusMessageSchema,\n changeStreamDataSchema,\n errorSchema,\n);\n\nexport type Error = v.Infer<typeof errorSchema>;\n\nexport function errorTypeToReadableName(val: ErrorType) {\n switch (val) {\n case ErrorType.WrongReplicaVersion:\n return 'WrongReplicaVersion';\n case ErrorType.WatermarkTooOld:\n return 'WatermarkTooOld';\n case ErrorType.Unknown:\n return 'Unknown';\n default:\n return 'Unknown';\n }\n}\n\n/**\n * A stream of transactions, each starting with a {@link Begin} message,\n * containing one or more {@link Data} messages, and ending with a\n * {@link Commit} or {@link Rollback} message. The 'commit' tuple\n * includes a `watermark` that should be stored with the committed\n * data and used for resuming a subscription (e.g. in the\n * {@link SubscriberContext}).\n *\n * A {@link SubscriptionError} indicates an unrecoverable error that requires\n * manual intervention (e.g. configuration / operational error).\n */\nexport type Downstream = v.Infer<typeof downstreamSchema>;\n\n/** A downstream message and its approximate serialized transport size. */\nexport type SizedDownstream = Sized<Downstream>;\n\nexport interface ChangeStreamerService\n extends Omit<ChangeStreamer, 'subscribe'>, Service {\n /**\n * The server-side interface overrides `subscribe()` to return a stream\n * of already-stringified {@link Downstream} payloads or pre-serialized Buffers.\n *\n * If a `downstream` subscription is supplied it is used directly as the\n * returned Source. Otherwise, a new one is created.\n */\n subscribe(\n ctx: SubscriberContext,\n downstream?: Subscription<string | PreSerializedBatch>,\n ): Promise<Source<string | PreSerialized>>;\n\n /**\n * Starts a snapshot reservation to preserve change-log entries while\n * a soon-to-be-subscriber downloads the current replica backup. Once\n * the change-streamer knows that a backup is available\n * (via {@link trackBackupWatermark}), it sends a confirmation\n * SnapshotMessage and reserves the corresponding entries in the change-log\n * until the client with that `taskID` subscribes, or if the snapshot\n * connection terminates.\n */\n startSnapshotReservation(\n taskID: string,\n opts?: {localReplica?: LocalReplica | undefined},\n ): Promise<Source<SnapshotMessage>>;\n\n /**\n * Signals (drainable) serving subscribers to switch over to another\n * replication-manager, and resolves when they have all disconnected.\n * Subscribers continue to be served in the meantime.\n */\n drain?(): Promise<void>;\n\n /**\n * Informs the change-streamer of the watermark up to which the replica has\n * been backed up. This serves two purposes:\n * - It serves as the maximum watermark up to which the change-log can be\n * purged. Note that the change-streamer also takes into account the\n * positions of its subscribers and snapshot reservations when purging\n * changes in the change-log.\n * - In RMv2, this ACK's the upstream change-source to allow its buffer of\n * changes (e.g. the replication_slot) to advance.\n */\n trackBackupWatermark(watermark: string): void;\n}\n"],"mappings":";;;;;;;;AA2MA,IAAa,eAAe,eAAE,OAAO;CACnC,KAAK,eAAE,QAAQ,QAAQ;CAEvB,WAAW,eACR,OAAO;EACN,aAAa,0BAA0B,SAAS;EAChD,gBAAgB,eAAE,OAAO;CAC3B,CAAC,EACA,SAAS;;;;;;;CAQZ,UAAU,eAAE,QAAQ,EAAE,SAAS;;;;;;;;;;;;;;;CAgB/B,SAAS,eACN,MACC,eAAE,OAAO;EAAC,OAAO,eAAE,QAAQ,QAAQ;EAAG,UAAU,eAAE,OAAO;CAAC,CAAC,GAC3D,eAAE,OAAO,EAAC,OAAO,eAAE,QAAQ,SAAS,EAAC,CAAC,CACxC,EACC,SAAS;AACd,CAAC;AAID,IAAa,sBAAsB,eAAE,MAAM,CAAC,eAAE,QAAQ,QAAQ,GAAG,YAAY,CAAC;AAU9E,IAAM,0BAA0B,eAAE,OAAO;CACvC,MAAM,eAAE,OAAO;CACf,SAAS,eAAE,OAAO,EAAE,SAAS;;;;;;CAM7B,gBAAgB,eAAE,OAAO,EAAE,SAAS;AACtC,CAAC;AAID,IAAM,cAAc,eAAE,MAAM,CAAC,eAAE,QAAQ,OAAO,GAAG,uBAAuB,CAAC;AAEzE,IAAa,mBAAmB,eAAE,MAChC,qBACA,wBACA,WACF;AAIA,SAAgB,wBAAwB,KAAgB;CACtD,QAAQ,KAAR;EACE,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,SACE,OAAO;CACX;AACF"}
@@ -0,0 +1,24 @@
1
+ import type { LogContext } from '@rocicorp/logger';
2
+ import type { Subscription } from '../../types/subscription.ts';
3
+ import type { PreSerializedBatch } from './broadcast.ts';
4
+ import type { Subscriber } from './subscriber.ts';
5
+ /**
6
+ * Tracks the {@link Subscriber.drainable drainable} subscribers of the
7
+ * change-streamer, i.e. those that switch over to another
8
+ * replication-manager when this one drains.
9
+ *
10
+ * Upon {@link drain}, drainable subscribers are sent a `draining` status
11
+ * (as are drainable subscribers that subscribe while draining), and the
12
+ * returned promise resolves once they have all disconnected. Subscribers
13
+ * continue to be served in the meantime.
14
+ */
15
+ export declare class DrainTracker {
16
+ #private;
17
+ constructor(lc: LogContext, setIntervalFn?: typeof setInterval, clearIntervalFn?: typeof clearInterval);
18
+ get draining(): boolean;
19
+ register(subscriber: Subscriber, downstream: Subscription<string | PreSerializedBatch>): void;
20
+ drain(): Promise<void>;
21
+ /** Resolves any pending drain, e.g. when the change-streamer stops. */
22
+ stop(): void;
23
+ }
24
+ //# sourceMappingURL=drain-tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drain-tracker.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/drain-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAC;AAIhD;;;;;;;;;GASG;AACH,qBAAa,YAAY;;IAOvB,YACE,EAAE,EAAE,UAAU,EAEd,aAAa,GAAE,OAAO,WAAyB,EAC/C,eAAe,GAAE,OAAO,aAA6B,EAKtD;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,QAAQ,CACN,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,YAAY,CAAC,MAAM,GAAG,kBAAkB,CAAC,QActD;IAED,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAuBrB;IAED,uEAAuE;IACvE,IAAI,SAEH;CAQF"}
@@ -0,0 +1,67 @@
1
+ import { resolver } from "@rocicorp/resolver";
2
+ //#region ../zero-cache/src/services/change-streamer/drain-tracker.ts
3
+ var PROGRESS_LOG_INTERVAL_MS = 3e4;
4
+ /**
5
+ * Tracks the {@link Subscriber.drainable drainable} subscribers of the
6
+ * change-streamer, i.e. those that switch over to another
7
+ * replication-manager when this one drains.
8
+ *
9
+ * Upon {@link drain}, drainable subscribers are sent a `draining` status
10
+ * (as are drainable subscribers that subscribe while draining), and the
11
+ * returned promise resolves once they have all disconnected. Subscribers
12
+ * continue to be served in the meantime.
13
+ */
14
+ var DrainTracker = class {
15
+ #lc;
16
+ #subscribers = /* @__PURE__ */ new Set();
17
+ #setInterval;
18
+ #clearInterval;
19
+ #draining;
20
+ constructor(lc, setIntervalFn = setInterval, clearIntervalFn = clearInterval) {
21
+ this.#lc = lc.withContext("component", "drain-tracker");
22
+ this.#setInterval = setIntervalFn;
23
+ this.#clearInterval = clearIntervalFn;
24
+ }
25
+ get draining() {
26
+ return this.#draining !== void 0;
27
+ }
28
+ register(subscriber, downstream) {
29
+ if (!subscriber.drainable) return;
30
+ this.#subscribers.add(subscriber);
31
+ if (this.#draining) subscriber.sendControlStatus({
32
+ tag: "status",
33
+ draining: true
34
+ });
35
+ downstream.addCloseHandler(() => {
36
+ if (this.#subscribers.delete(subscriber)) this.#checkDrained();
37
+ });
38
+ }
39
+ drain() {
40
+ if (!this.#draining) {
41
+ const draining = this.#draining = resolver();
42
+ this.#lc.info?.(`draining: signaling ${this.#subscribers.size} subscribers to switch over`);
43
+ for (const sub of this.#subscribers) sub.sendControlStatus({
44
+ tag: "status",
45
+ draining: true
46
+ });
47
+ const progress = this.#setInterval(() => this.#lc.info?.(`draining: waiting for ${this.#subscribers.size} subscribers to switch over`, Array.from(this.#subscribers, (sub) => sub.id)), PROGRESS_LOG_INTERVAL_MS);
48
+ draining.promise.finally(() => this.#clearInterval(progress));
49
+ this.#checkDrained();
50
+ }
51
+ return this.#draining.promise;
52
+ }
53
+ /** Resolves any pending drain, e.g. when the change-streamer stops. */
54
+ stop() {
55
+ this.#draining?.resolve();
56
+ }
57
+ #checkDrained() {
58
+ if (this.#draining && this.#subscribers.size === 0) {
59
+ this.#lc.info?.(`drained all subscribers`);
60
+ this.#draining.resolve();
61
+ }
62
+ }
63
+ };
64
+ //#endregion
65
+ export { DrainTracker };
66
+
67
+ //# sourceMappingURL=drain-tracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drain-tracker.js","names":["#lc","#subscribers","#setInterval","#clearInterval","#draining","#checkDrained"],"sources":["../../../../../../zero-cache/src/services/change-streamer/drain-tracker.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {resolver, type Resolver} from '@rocicorp/resolver';\nimport type {Subscription} from '../../types/subscription.ts';\nimport type {PreSerializedBatch} from './broadcast.ts';\nimport type {Subscriber} from './subscriber.ts';\n\nconst PROGRESS_LOG_INTERVAL_MS = 30_000;\n\n/**\n * Tracks the {@link Subscriber.drainable drainable} subscribers of the\n * change-streamer, i.e. those that switch over to another\n * replication-manager when this one drains.\n *\n * Upon {@link drain}, drainable subscribers are sent a `draining` status\n * (as are drainable subscribers that subscribe while draining), and the\n * returned promise resolves once they have all disconnected. Subscribers\n * continue to be served in the meantime.\n */\nexport class DrainTracker {\n readonly #lc: LogContext;\n readonly #subscribers = new Set<Subscriber>();\n readonly #setInterval: typeof setInterval;\n readonly #clearInterval: typeof clearInterval;\n #draining: Resolver<void> | undefined;\n\n constructor(\n lc: LogContext,\n // Injectable timers, for deterministic testing.\n setIntervalFn: typeof setInterval = setInterval,\n clearIntervalFn: typeof clearInterval = clearInterval,\n ) {\n this.#lc = lc.withContext('component', 'drain-tracker');\n this.#setInterval = setIntervalFn;\n this.#clearInterval = clearIntervalFn;\n }\n\n get draining(): boolean {\n return this.#draining !== undefined;\n }\n\n register(\n subscriber: Subscriber,\n downstream: Subscription<string | PreSerializedBatch>,\n ) {\n if (!subscriber.drainable) {\n return;\n }\n this.#subscribers.add(subscriber);\n if (this.#draining) {\n subscriber.sendControlStatus({tag: 'status', draining: true});\n }\n downstream.addCloseHandler(() => {\n if (this.#subscribers.delete(subscriber)) {\n this.#checkDrained();\n }\n });\n }\n\n drain(): Promise<void> {\n if (!this.#draining) {\n const draining = (this.#draining = resolver<void>());\n this.#lc.info?.(\n `draining: signaling ${this.#subscribers.size} subscribers ` +\n `to switch over`,\n );\n for (const sub of this.#subscribers) {\n sub.sendControlStatus({tag: 'status', draining: true});\n }\n const progress = this.#setInterval(\n () =>\n this.#lc.info?.(\n `draining: waiting for ${this.#subscribers.size} ` +\n `subscribers to switch over`,\n Array.from(this.#subscribers, sub => sub.id),\n ),\n PROGRESS_LOG_INTERVAL_MS,\n );\n void draining.promise.finally(() => this.#clearInterval(progress));\n this.#checkDrained();\n }\n return this.#draining.promise;\n }\n\n /** Resolves any pending drain, e.g. when the change-streamer stops. */\n stop() {\n this.#draining?.resolve();\n }\n\n #checkDrained() {\n if (this.#draining && this.#subscribers.size === 0) {\n this.#lc.info?.(`drained all subscribers`);\n this.#draining.resolve();\n }\n }\n}\n"],"mappings":";;AAMA,IAAM,2BAA2B;;;;;;;;;;;AAYjC,IAAa,eAAb,MAA0B;CACxB;CACA,+BAAwB,IAAI,IAAgB;CAC5C;CACA;CACA;CAEA,YACE,IAEA,gBAAoC,aACpC,kBAAwC,eACxC;EACA,KAAKA,MAAM,GAAG,YAAY,aAAa,eAAe;EACtD,KAAKE,eAAe;EACpB,KAAKC,iBAAiB;CACxB;CAEA,IAAI,WAAoB;EACtB,OAAO,KAAKC,cAAc,KAAA;CAC5B;CAEA,SACE,YACA,YACA;EACA,IAAI,CAAC,WAAW,WACd;EAEF,KAAKH,aAAa,IAAI,UAAU;EAChC,IAAI,KAAKG,WACP,WAAW,kBAAkB;GAAC,KAAK;GAAU,UAAU;EAAI,CAAC;EAE9D,WAAW,sBAAsB;GAC/B,IAAI,KAAKH,aAAa,OAAO,UAAU,GACrC,KAAKI,cAAc;EAEvB,CAAC;CACH;CAEA,QAAuB;EACrB,IAAI,CAAC,KAAKD,WAAW;GACnB,MAAM,WAAY,KAAKA,YAAY,SAAe;GAClD,KAAKJ,IAAI,OACP,uBAAuB,KAAKC,aAAa,KAAK,4BAEhD;GACA,KAAK,MAAM,OAAO,KAAKA,cACrB,IAAI,kBAAkB;IAAC,KAAK;IAAU,UAAU;GAAI,CAAC;GAEvD,MAAM,WAAW,KAAKC,mBAElB,KAAKF,IAAI,OACP,yBAAyB,KAAKC,aAAa,KAAK,8BAEhD,MAAM,KAAK,KAAKA,eAAc,QAAO,IAAI,EAAE,CAC7C,GACF,wBACF;GACA,SAAc,QAAQ,cAAc,KAAKE,eAAe,QAAQ,CAAC;GACjE,KAAKE,cAAc;EACrB;EACA,OAAO,KAAKD,UAAU;CACxB;;CAGA,OAAO;EACL,KAAKA,WAAW,QAAQ;CAC1B;CAEA,gBAAgB;EACd,IAAI,KAAKA,aAAa,KAAKH,aAAa,SAAS,GAAG;GAClD,KAAKD,IAAI,OAAO,yBAAyB;GACzC,KAAKI,UAAU,QAAQ;EACzB;CACF;AACF"}
@@ -0,0 +1,214 @@
1
+ import type { LogContext } from '@rocicorp/logger';
2
+ import type { LocalReplica } from './subscribe.ts';
3
+ /**
4
+ * The default time to wait for a subscriber that was granted permission to
5
+ * restore to reconnect (i.e. to open a replacement reservation) after its
6
+ * held connection closes, or after its replacement reservation closes
7
+ * without subscribing (e.g. to retry a failed restore). This covers the
8
+ * subscriber's process exit and restart (e.g. a container restart, the
9
+ * backoff of which starts at 10 seconds in Kubernetes), but not the restore
10
+ * itself, which is tracked by the liveness of its snapshot reservation.
11
+ */
12
+ export declare const DEFAULT_PENDING_GRANT_TTL_MS = 90000;
13
+ export type RestoreAdmissionOptions = {
14
+ /** The replica version of the replication-manager. */
15
+ replicaVersion: string;
16
+ /**
17
+ * The maximum number of subscribers that may be restoring (or granted
18
+ * permission to restore) at the same time.
19
+ */
20
+ maxConcurrentRestores: number;
21
+ pendingGrantTTLMs?: number | undefined;
22
+ setTimeoutFn?: typeof setTimeout | undefined;
23
+ clearTimeoutFn?: typeof clearTimeout | undefined;
24
+ };
25
+ export type AdmissionRequest = {
26
+ /**
27
+ * Called when the request is queued, and again whenever its (1-based)
28
+ * position in the queue changes.
29
+ */
30
+ onQueued: (position: number) => void;
31
+ /** Called (once) when the request is granted. */
32
+ onGranted: () => void;
33
+ };
34
+ export type AdmissionTicket = {
35
+ /**
36
+ * Must be called when the requesting connection closes. A queued request
37
+ * is removed from the queue. For a granted request, this starts the
38
+ * timer within which the subscriber is expected to reconnect and open a
39
+ * replacement reservation.
40
+ */
41
+ release(): void;
42
+ };
43
+ /**
44
+ * Limits the number of view-syncers that replace their replica at the same
45
+ * time, so that view-syncers switching over from an incompatible
46
+ * replication-manager (e.g. after a resync) do not all stop serving at once.
47
+ *
48
+ * Capacity is consumed by:
49
+ * * Replacement restores: snapshot reservations of subscribers that are
50
+ * replacing an (incompatible or outdated) local replica, i.e. subscribers
51
+ * that were serving. Restores onto an empty disk (e.g. a new pod) do not
52
+ * take a serving subscriber out of service, and are not counted. The
53
+ * reservation's connection is held open for the duration of the restore,
54
+ * and the subscription that follows it is held open until the restored
55
+ * subscriber has caught up, so their liveness tracks that of the
56
+ * replacement without a timeout.
57
+ * * Outstanding grants: permission to restore that has not yet been
58
+ * followed by a replacement reservation (e.g. while the subscriber drains
59
+ * and restarts). Grants are not matched to reservations by identity:
60
+ * any replacement reservation consumes an outstanding grant, and a grant
61
+ * that is not consumed expires a short time after the granted connection
62
+ * closes (e.g. if the subscriber reconnected to a different
63
+ * replication-manager or was deleted).
64
+ *
65
+ * ## Life cycle of a switchover restore
66
+ *
67
+ * The following traces a view-syncer (VS) with a replica at version `R1`,
68
+ * served by an old replication-manager (RM) at `R1`, while a new RM (e.g.
69
+ * after a resync) is at `R2 > R1`. The RestoreAdmission is that of the new
70
+ * RM. Terms in `code` are those used in the code.
71
+ *
72
+ * 1. **Drain.** The old RM receives a SIGTERM, and its `DrainTracker` sends
73
+ * `status {draining: true}` to its `drainable` (i.e. serving) subscribers.
74
+ * It continues to stream changes to them, and exits once they have all
75
+ * disconnected.
76
+ *
77
+ * 2. **Switchover.** The VS's `DrainHandler` opens a second `/subscribe`
78
+ * with `switchover: true` at the watermark of its last committed
79
+ * transaction, which is routed to some RM. The VS continues to consume
80
+ * the old RM's stream. If the RM is compatible (i.e. another old RM),
81
+ * the subscription is accepted and the VS simply moves to it. If the RM
82
+ * is the new one, the subscriber's replica version is older, and the
83
+ * subscription proceeds to step 3.
84
+ *
85
+ * 3. **Queued.** `ChangeStreamerImpl.subscribe()` holds the subscription
86
+ * (it is not added to the forwarder) and calls {@link request}. The
87
+ * request is `queued` and `onQueued(position)` sends
88
+ * `status {restore: {state: 'queued', position}}`, again whenever the
89
+ * position changes. If the held connection closes while queued (e.g.
90
+ * the VS's old stream ended and it gave up on the switchover), the
91
+ * ticket is `release()`d and the request leaves the queue.
92
+ *
93
+ * 4. **Granted.** When the number in flight (`replacing` + `granted`) is
94
+ * below `maxConcurrentRestores`, the head of the queue is `granted`: a
95
+ * {@link Grant} is added (counting toward capacity), and `onGranted()`
96
+ * sends `status {restore: {state: 'granted'}}`. The VS keeps the granted
97
+ * connection open, and initiates a self-drain (`drainRequested`): it
98
+ * fails its health checks, drains its clients, and keeps replicating
99
+ * from the old RM in the meantime.
100
+ *
101
+ * 5. **Restart.** The VS process exits, closing the granted connection.
102
+ * The ticket is `release()`d, marking the grant as `released` and
103
+ * starting the pending grant TTL ({@link DEFAULT_PENDING_GRANT_TTL_MS}),
104
+ * within which a replacement reservation is expected. The drain itself
105
+ * does not count against the TTL.
106
+ *
107
+ * 6. **Reservation opened.** The restarted VS sends
108
+ * `reserve-snapshot {taskID, localReplica}`, and
109
+ * `ChangeStreamerImpl.startSnapshotReservation()` calls
110
+ * {@link reservationOpened}:
111
+ * * A `localReplica` of a different replica version (e.g. `R1`) is
112
+ * `replacing`: the reservation is counted, and consumes an outstanding
113
+ * grant (preferring a `released` one), so the number in flight is
114
+ * unchanged.
115
+ * * A `localReplica` of the same replica version is `unclassified` until
116
+ * the reservation is confirmed ({@link reservationConfirmed}) with the
117
+ * backup's minimum watermark. It is `replacing` if its watermark is
118
+ * older (i.e. it cannot catch up from the change log).
119
+ * * No `localReplica` (an empty disk) is not counted.
120
+ *
121
+ * If the VS reached a different RM instead, the grant here expires after
122
+ * the TTL and frees its capacity, while the replacement is counted (as
123
+ * an ungranted one) by the RM that it reached.
124
+ *
125
+ * 7. **Restoring.** The VS restores the backup while holding the
126
+ * reservation's connection open. A retried reservation from the same
127
+ * task that arrives while the previous one is still open replaces it
128
+ * (`SnapshotReservations` `onClose`) but does not end it (`onEnd`), so it
129
+ * remains `replacing`. If the reservation instead closes without
130
+ * subscribing (e.g. a failed or disconnected restore, after which the VS
131
+ * retries), {@link reservationClosed} converts its capacity into a
132
+ * `released` grant, which the retry's replacement reservation consumes
133
+ * (step 6), or which expires after the pending grant TTL (e.g. if the VS
134
+ * was deleted).
135
+ *
136
+ * 8. **Subscribed.** Once restored, the VS subscribes on the same
137
+ * connection at `R2`, which is accepted. `ChangeStreamerImpl.subscribe()`
138
+ * calls {@link reservationSubscribed}, which moves the replacement from
139
+ * its reservation (the key leaves `replacing`) to the subscription
140
+ * (`catchingUp`), and then closes the task's reservation. Its `onEnd`
141
+ * calls {@link reservationClosed}, which is a no-op for the moved key.
142
+ *
143
+ * 9. **Caught up.** The VS catches up on the change stream from the
144
+ * backup's watermark. Once it has acked all changes up to the head of
145
+ * the stream (`Subscriber.caughtUp`), or if it disconnects first, the
146
+ * replacement is released and the next queued request is granted.
147
+ *
148
+ * A VS that restarts outside of a switchover with an incompatible replica
149
+ * (e.g. after a crash) enters at step 6 without a grant, and its restore
150
+ * consumes capacity all the same, delaying queued requests.
151
+ *
152
+ * ## States
153
+ *
154
+ * | Term | Where | Meaning |
155
+ * | ------------- | ------------------------ | -------------------------------- |
156
+ * | queued | `Entry.state` | Held, waiting for capacity |
157
+ * | granted | `Entry.state`, `#grants` | Permitted to restore, pending a |
158
+ * | | | replacement reservation |
159
+ * | released | `Entry.state` | The held connection closed |
160
+ * | released | `Grant.released` | The granted connection, or an |
161
+ * | | | unsubscribed replacement |
162
+ * | | | reservation, closed; the TTL is |
163
+ * | | | running |
164
+ * | unclassified | `#unclassified` | Same-version reservation, not |
165
+ * | | | yet confirmed |
166
+ * | replacing | `#replacing` | Open replacement reservation |
167
+ * | catching up | `#catchingUp` | Subscribed replacement, not yet |
168
+ * | | | caught up |
169
+ * | in flight | `#inFlight` | `replacing` + `catchingUp` + |
170
+ * | | | `granted`, bounded by |
171
+ * | | | `maxConcurrentRestores` |
172
+ */
173
+ export declare class RestoreAdmission {
174
+ #private;
175
+ constructor(lc: LogContext, opts: RestoreAdmissionOptions);
176
+ /** For testing and logging. */
177
+ get stats(): {
178
+ replacing: number;
179
+ granted: number;
180
+ queued: number;
181
+ };
182
+ /**
183
+ * Called when a snapshot reservation is opened by a subscriber with the
184
+ * given `localReplica` (if any). A subscriber with a local replica of a
185
+ * different replica version is replacing it. One with the same replica
186
+ * version is replacing it if it is outdated, which is determined by
187
+ * {@link reservationConfirmed}. Idempotent for the same `key` (e.g. a
188
+ * retried reservation).
189
+ */
190
+ reservationOpened(key: string, localReplica: LocalReplica | undefined): void;
191
+ /**
192
+ * Called when a reservation is confirmed with the minimum watermark of the
193
+ * replica to restore.
194
+ */
195
+ reservationConfirmed(key: string, minWatermark: string): void;
196
+ /**
197
+ * Called when a reservation ends. A replacement reservation normally ends
198
+ * after its task subscribes ({@link reservationSubscribed}), in which case
199
+ * this is a no-op. A replacement reservation that ends without subscribing
200
+ * (e.g. a failed or disconnected restore) is most likely followed by a
201
+ * retry, so its capacity is retained as a released grant (which the retry
202
+ * consumes) rather than released immediately.
203
+ */
204
+ reservationClosed(key: string): void;
205
+ /**
206
+ * Called when the task of a reservation subscribes (before the reservation
207
+ * is closed). If the reservation is a replacement, it continues to be
208
+ * counted until the returned function is called, i.e. when the subscriber
209
+ * has caught up or disconnected.
210
+ */
211
+ reservationSubscribed(key: string): (() => void) | undefined;
212
+ request(request: AdmissionRequest): AdmissionTicket;
213
+ }
214
+ //# sourceMappingURL=restore-admission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restore-admission.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/restore-admission.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAEjD;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,MAAM,MAAM,uBAAuB,GAAG;IACpC,sDAAsD;IACtD,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,qBAAqB,EAAE,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEvC,YAAY,CAAC,EAAE,OAAO,UAAU,GAAG,SAAS,CAAC;IAC7C,cAAc,CAAC,EAAE,OAAO,YAAY,GAAG,SAAS,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAErC,iDAAiD;IACjD,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;;OAKG;IACH,OAAO,IAAI,IAAI,CAAC;CACjB,CAAC;AAeF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiIG;AACH,qBAAa,gBAAgB;;IAoB3B,YAAY,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,uBAAuB,EAmBxD;IAMD,+BAA+B;IAC/B,IAAI,KAAK;QAEL,SAAS;QACT,OAAO;QACP,MAAM;MAET;IAED;;;;;;;OAOG;IACH,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,SAAS,QAUpE;IAED;;;OAGG;IACH,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,QAMrD;IAED;;;;;;;OAOG;IACH,iBAAiB,CAAC,GAAG,EAAE,MAAM,QAO5B;IAED;;;;;OAKG;IACH,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAY3D;IAiBD,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAKlD;CAoDF"}