@powersync/service-core 1.25.0 → 1.26.0

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 (113) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/api/diagnostics.js +2 -0
  3. package/dist/api/diagnostics.js.map +1 -1
  4. package/dist/entry/commands/compact-action.js +28 -24
  5. package/dist/entry/commands/compact-action.js.map +1 -1
  6. package/dist/metrics/metrics-interfaces.d.ts +5 -1
  7. package/dist/metrics/metrics-interfaces.js.map +1 -1
  8. package/dist/metrics/open-telemetry/MetricAttributeFilteringExporter.d.ts +36 -0
  9. package/dist/metrics/open-telemetry/MetricAttributeFilteringExporter.js +70 -0
  10. package/dist/metrics/open-telemetry/MetricAttributeFilteringExporter.js.map +1 -0
  11. package/dist/metrics/open-telemetry/OpenTelemetryMetricsFactory.js +6 -1
  12. package/dist/metrics/open-telemetry/OpenTelemetryMetricsFactory.js.map +1 -1
  13. package/dist/metrics/open-telemetry/util.js +7 -3
  14. package/dist/metrics/open-telemetry/util.js.map +1 -1
  15. package/dist/replication/AbstractReplicator.js +16 -6
  16. package/dist/replication/AbstractReplicator.js.map +1 -1
  17. package/dist/routes/configure-fastify.d.ts +9 -0
  18. package/dist/routes/endpoints/admin.d.ts +18 -0
  19. package/dist/routes/endpoints/admin.js +3 -1
  20. package/dist/routes/endpoints/admin.js.map +1 -1
  21. package/dist/routes/endpoints/sync-rules.js +3 -1
  22. package/dist/routes/endpoints/sync-rules.js.map +1 -1
  23. package/dist/storage/BucketStorageBatch.d.ts +2 -1
  24. package/dist/storage/BucketStorageBatch.js.map +1 -1
  25. package/dist/storage/BucketStorageFactory.d.ts +28 -4
  26. package/dist/storage/BucketStorageFactory.js +8 -4
  27. package/dist/storage/BucketStorageFactory.js.map +1 -1
  28. package/dist/storage/PersistedSyncConfigContent.d.ts +2 -0
  29. package/dist/storage/PersistedSyncConfigContent.js +23 -10
  30. package/dist/storage/PersistedSyncConfigContent.js.map +1 -1
  31. package/dist/storage/ReplicationEventPayload.d.ts +6 -1
  32. package/dist/storage/SourceTable.d.ts +13 -5
  33. package/dist/storage/SourceTable.js +8 -4
  34. package/dist/storage/SourceTable.js.map +1 -1
  35. package/dist/storage/StorageVersionConfig.d.ts +4 -0
  36. package/dist/storage/StorageVersionConfig.js +10 -1
  37. package/dist/storage/StorageVersionConfig.js.map +1 -1
  38. package/dist/storage/SyncRulesBucketStorage.d.ts +39 -7
  39. package/dist/storage/SyncRulesBucketStorage.js.map +1 -1
  40. package/dist/storage/WriteCheckpointAPI.d.ts +25 -3
  41. package/dist/storage/WriteCheckpointAPI.js.map +1 -1
  42. package/dist/storage/implementation/BucketDefinitionMapping.d.ts +23 -7
  43. package/dist/storage/implementation/BucketDefinitionMapping.js +88 -14
  44. package/dist/storage/implementation/BucketDefinitionMapping.js.map +1 -1
  45. package/dist/storage/implementation/IncrementalReprocessingSyncConfigLog.js +9 -1
  46. package/dist/storage/implementation/IncrementalReprocessingSyncConfigLog.js.map +1 -1
  47. package/dist/storage/storage-metrics.js +120 -4
  48. package/dist/storage/storage-metrics.js.map +1 -1
  49. package/dist/sync/BucketChecksumState.d.ts +1 -1
  50. package/dist/sync/BucketChecksumState.js.map +1 -1
  51. package/dist/sync/sync.js +37 -9
  52. package/dist/sync/sync.js.map +1 -1
  53. package/dist/sync/util.js +10 -1
  54. package/dist/sync/util.js.map +1 -1
  55. package/dist/util/config/compound-config-collector.js +5 -0
  56. package/dist/util/config/compound-config-collector.js.map +1 -1
  57. package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js +1 -0
  58. package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js.map +1 -1
  59. package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js +1 -0
  60. package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -1
  61. package/dist/util/config/sync-rules/sync-rules-provider.d.ts +2 -0
  62. package/dist/util/config/sync-rules/sync-rules-provider.js +3 -0
  63. package/dist/util/config/sync-rules/sync-rules-provider.js.map +1 -1
  64. package/dist/util/config/types.d.ts +1 -0
  65. package/dist/util/protocol-types.d.ts +4 -4
  66. package/dist/util/protocol-types.js +3 -2
  67. package/dist/util/protocol-types.js.map +1 -1
  68. package/dist/util/utils.d.ts +1 -0
  69. package/dist/util/utils.js +15 -0
  70. package/dist/util/utils.js.map +1 -1
  71. package/package.json +6 -5
  72. package/src/api/diagnostics.ts +2 -0
  73. package/src/entry/commands/compact-action.ts +32 -24
  74. package/src/metrics/metrics-interfaces.ts +6 -1
  75. package/src/metrics/open-telemetry/MetricAttributeFilteringExporter.ts +85 -0
  76. package/src/metrics/open-telemetry/OpenTelemetryMetricsFactory.ts +7 -2
  77. package/src/metrics/open-telemetry/util.ts +7 -3
  78. package/src/replication/AbstractReplicator.ts +22 -6
  79. package/src/routes/endpoints/admin.ts +3 -1
  80. package/src/routes/endpoints/sync-rules.ts +3 -1
  81. package/src/storage/BucketStorageBatch.ts +2 -1
  82. package/src/storage/BucketStorageFactory.ts +44 -8
  83. package/src/storage/PersistedSyncConfigContent.ts +26 -11
  84. package/src/storage/ReplicationEventPayload.ts +6 -1
  85. package/src/storage/SourceTable.ts +17 -4
  86. package/src/storage/StorageVersionConfig.ts +11 -1
  87. package/src/storage/SyncRulesBucketStorage.ts +47 -7
  88. package/src/storage/WriteCheckpointAPI.ts +32 -3
  89. package/src/storage/implementation/BucketDefinitionMapping.ts +120 -17
  90. package/src/storage/implementation/IncrementalReprocessingSyncConfigLog.ts +22 -5
  91. package/src/storage/storage-metrics.ts +154 -8
  92. package/src/sync/BucketChecksumState.ts +3 -1
  93. package/src/sync/sync.ts +40 -11
  94. package/src/sync/util.ts +21 -8
  95. package/src/util/config/compound-config-collector.ts +6 -0
  96. package/src/util/config/sync-rules/impl/base64-sync-rules-collector.ts +1 -0
  97. package/src/util/config/sync-rules/impl/filesystem-sync-rules-collector.ts +1 -0
  98. package/src/util/config/sync-rules/sync-rules-provider.ts +5 -0
  99. package/src/util/config/types.ts +1 -0
  100. package/src/util/protocol-types.ts +3 -2
  101. package/src/util/utils.ts +14 -0
  102. package/test/src/AbstractReplicator.test.ts +30 -0
  103. package/test/src/MetricAttributeFilteringExporter.test.ts +96 -0
  104. package/test/src/PersistedSyncConfigContent.test.ts +152 -0
  105. package/test/src/config.test.ts +40 -0
  106. package/test/src/diagnostics.test.ts +25 -0
  107. package/test/src/routes/admin.test.ts +8 -2
  108. package/test/src/routes/stream.test.ts +123 -2
  109. package/test/src/storage/SourceTable.test.ts +4 -1
  110. package/test/src/storage-metrics.test.ts +148 -0
  111. package/test/src/sync/util.test.ts +15 -0
  112. package/test/src/util/utils.test.ts +23 -0
  113. package/tsconfig.tsbuildinfo +1 -1
@@ -37,10 +37,17 @@ export abstract class BucketStorageFactory
37
37
  const deploying = await this.getDeployingSyncConfig();
38
38
  const active = await this.getActiveSyncConfig();
39
39
 
40
- if (deploying?.content.sync_rules_content == options.config.yaml) {
40
+ if (
41
+ deploying?.content.sync_rules_content == options.config.yaml &&
42
+ deploying.content.version_label == options.version_label
43
+ ) {
41
44
  logger.info('Sync config unchanged');
42
45
  return { updated: false };
43
- } else if (deploying == null && active?.content.sync_rules_content == options.config.yaml) {
46
+ } else if (
47
+ deploying == null &&
48
+ active?.content.sync_rules_content == options.config.yaml &&
49
+ active.content.version_label == options.version_label
50
+ ) {
44
51
  logger.info('Sync config unchanged');
45
52
  return { updated: false };
46
53
  } else {
@@ -141,9 +148,32 @@ export interface StorageMetrics {
141
148
  * Size of current_data.
142
149
  */
143
150
  replication_size_bytes: number;
151
+
152
+ /**
153
+ * Size of active object-storage references, such as S3 bucket-data objects.
154
+ */
155
+ object_storage_size_bytes?: number;
156
+
157
+ /**
158
+ * Per-sync-config storage sizes, when the storage backend can provide them.
159
+ */
160
+ sync_config_metrics?: StorageSyncConfigMetrics[];
161
+ }
162
+
163
+ export interface StorageSyncConfigMetrics {
164
+ sync_config_id: string;
165
+ sync_config_state: string;
166
+ /** Optional operator-supplied label identifying this sync config version. */
167
+ version_label?: string;
168
+ attributed_bucket_data_bytes: number;
169
+ attributed_parameter_indexes_bytes: number;
170
+ attributed_source_records_bytes: number;
171
+ attributed_object_storage_bytes: number;
144
172
  }
145
173
 
146
174
  export interface UpdateSyncRulesOptions {
175
+ /** Optional operator-supplied label identifying this deployed sync config version. */
176
+ version_label?: string;
147
177
  config: {
148
178
  yaml: string;
149
179
  /**
@@ -161,6 +191,12 @@ export interface UpdateSyncRulesOptions {
161
191
  lock?: boolean;
162
192
  storageVersion?: number;
163
193
 
194
+ /**
195
+ * Start a replacement replication stream instead of incrementally reprocessing a
196
+ * compatible active stream. Used when the current stream needs a full reprocess.
197
+ */
198
+ forceNewReplicationStream?: boolean;
199
+
164
200
  /**
165
201
  * Only relevant if the result is used. This does not affect the persisted config.
166
202
  */
@@ -174,11 +210,10 @@ export interface SerializedSyncPlan {
174
210
  plan: RawSerializedSyncPlan;
175
211
  compatibility: SerializedCompatibilityContext;
176
212
  /**
177
- * Event descriptors are not currently represented in the sync plan because they don't use the sync streams compiler
178
- * yet.
213
+ * Raw event SQL persisted as a compatibility mirror for compiled {@link plan} events.
179
214
  *
180
- * We might revisit that in the future, but for now we store SQL text of their definitions here to be able to restore
181
- * them.
215
+ * Compiled events are an additive plan field. Older services ignore that field and restore these descriptors through
216
+ * the legacy evaluator. Keep dual-writing this field until a future plan version explicitly removes that support.
182
217
  */
183
218
  eventDescriptors: Record<string, string[]>;
184
219
  errors?: ReplicationError[];
@@ -206,13 +241,14 @@ export function updateSyncRulesFromConfig(
206
241
  const { config, errors } = parsed;
207
242
  if (config instanceof PrecompiledSyncConfig) {
208
243
  const eventDescriptors: Record<string, string[]> = {};
209
- for (const event of config.eventDescriptors) {
210
- eventDescriptors[event.name] = event.sourceQueries.map((q) => q.sql);
244
+ for (const event of config.plan.events) {
245
+ eventDescriptors[event.name] = event.sourceQueries.map((query) => query.sql);
211
246
  }
212
247
 
213
248
  plan = {
214
249
  compatibility: config.compatibility.serialize(),
215
250
  plan: serializeSyncPlan(config.plan),
251
+ // Dual-write raw SQL so older services can ignore additive compiled plan events without losing event behavior.
216
252
  eventDescriptors,
217
253
  errors: errors.map((e) => syncConfigYamlErrorToReplicationError(e))
218
254
  };
@@ -2,6 +2,7 @@ import { logger as defaultLogger, ErrorCode, ServiceError } from '@powersync/lib
2
2
  import {
3
3
  CompatibilityContext,
4
4
  CompatibilityOption,
5
+ compileEventDefinitions,
5
6
  DEFAULT_HYDRATION_STATE,
6
7
  deserializeSyncPlan,
7
8
  ErrorLocation,
@@ -9,7 +10,6 @@ import {
9
10
  HydrationState,
10
11
  nodeSqlite,
11
12
  PrecompiledSyncConfig,
12
- SqlEventDescriptor,
13
13
  SqlSyncRules,
14
14
  SyncConfigWithErrors,
15
15
  versionedHydrationState,
@@ -40,17 +40,28 @@ export function parsePersistedSyncConfigContent(options: ParsePersistedSyncConfi
40
40
 
41
41
  const plan = deserializeSyncPlan(compiledPlan.plan);
42
42
  const compatibility = CompatibilityContext.deserialize(compiledPlan.compatibility);
43
- const eventDefinitions: SqlEventDescriptor[] = [];
44
- for (const [name, queries] of Object.entries(compiledPlan.eventDescriptors)) {
45
- const descriptor = new SqlEventDescriptor(name, compatibility);
46
- for (const query of queries) {
47
- descriptor.addSourceQuery(query, parseOptions);
43
+ const errors: YamlError[] = [];
44
+ // Compiled events are additive to plan versions 1 and 2. New readers prefer them when present; when an older plan
45
+ // does not contain them, normalize the dual-written raw SQL at this loading boundary. This keeps legacy event
46
+ // evaluators out of PrecompiledSyncConfig while older binaries can continue reading the same persisted config.
47
+ if (compiledPlan.plan.events == null) {
48
+ const normalized = compileEventDefinitions(compiledPlan.eventDescriptors, {
49
+ ...parseOptions,
50
+ // The legacy evaluator ignored event payload filters. Preserve that behavior for plans deployed before compiled
51
+ // events existed; a redeploy compiles and validates those filters before the replacement config is activated.
52
+ compileEventPayloadFilters: false
53
+ });
54
+ const fatalErrors = normalized.errors.filter((error) => error.type == 'fatal');
55
+ if (fatalErrors.length != 0) {
56
+ throw new Error(
57
+ `Failed to compile persisted replication events: ${fatalErrors.map((error) => error.message).join(', ')}`
58
+ );
48
59
  }
49
-
50
- eventDefinitions.push(descriptor);
60
+ plan.events = normalized.events;
61
+ errors.push(...normalized.errors.map((error) => new YamlError(error)));
51
62
  }
52
63
 
53
- const precompiled = new PrecompiledSyncConfig(plan, compatibility, eventDefinitions, {
64
+ const precompiled = new PrecompiledSyncConfig(plan, compatibility, {
54
65
  defaultSchema: parseOptions.defaultSchema,
55
66
  sourceText: content
56
67
  });
@@ -59,7 +70,6 @@ export function parsePersistedSyncConfigContent(options: ParsePersistedSyncConfi
59
70
  // This means asUpdateOptions will not change the storage version, even if the default changes.
60
71
  precompiled.storageVersion = storageVersion;
61
72
 
62
- const errors: YamlError[] = [];
63
73
  if (compiledPlan.errors) {
64
74
  for (const error of compiledPlan.errors) {
65
75
  const location: ErrorLocation | undefined = error.location && {
@@ -93,6 +103,7 @@ export abstract class PersistedSyncConfigContent implements PersistedSyncConfigC
93
103
  readonly logger: Logger;
94
104
  readonly syncConfigId: PersistedSyncConfigId | null;
95
105
  readonly syncConfigState: SyncRuleState;
106
+ readonly version_label: string | undefined;
96
107
 
97
108
  constructor(data: PersistedSyncConfigContentData) {
98
109
  this.replicationStreamId = data.replicationStreamId;
@@ -102,7 +113,9 @@ export abstract class PersistedSyncConfigContent implements PersistedSyncConfigC
102
113
  this.storageVersion = data.storageVersion;
103
114
  this.syncConfigId = data.syncConfigId ?? null;
104
115
  this.syncConfigState = data.syncConfigState;
105
- this.logger = defaultLogger.child({ prefix: `[${this.replicationStreamName}] ` });
116
+ this.version_label = data.version_label;
117
+ const versionPrefix = this.version_label == null ? '' : `[${this.version_label}]`;
118
+ this.logger = defaultLogger.child({ prefix: `[${this.replicationStreamName}]${versionPrefix} ` });
106
119
  }
107
120
 
108
121
  /**
@@ -167,6 +180,7 @@ export abstract class PersistedSyncConfigContent implements PersistedSyncConfigC
167
180
  const parsed = this.parseSingleConfig({ defaultSchema: 'not_applicable' });
168
181
  return {
169
182
  config: { yaml: this.sync_rules_content, plan: this.compiled_plan, parsed },
183
+ version_label: this.version_label,
170
184
  ...options
171
185
  };
172
186
  }
@@ -185,6 +199,7 @@ export interface PersistedSyncConfigContentData {
185
199
 
186
200
  readonly syncConfigId?: PersistedSyncConfigId | null;
187
201
  readonly syncConfigState: SyncRuleState;
202
+ readonly version_label?: string;
188
203
  }
189
204
  export type PersistedSyncConfigId = string;
190
205
  export interface ParseSyncConfigOptions {
@@ -11,6 +11,11 @@ export type EventData = {
11
11
  export type ReplicationEventPayload = {
12
12
  batch: BucketStorageBatch;
13
13
  data: EventData;
14
- event: sync_rules.SqlEventDescriptor;
14
+ event: sync_rules.HydratedEventDescriptor;
15
+ /**
16
+ * Storage-assigned id for this event definition. Present when storage uses persisted event mappings, allowing event
17
+ * handlers to route custom checkpoints without deriving an id from compiled event content.
18
+ */
19
+ event_id?: sync_rules.EventDefinitionId;
15
20
  table: SourceTable;
16
21
  };
@@ -2,6 +2,7 @@ import {
2
2
  BucketDataSource,
3
3
  BucketDefinitionId,
4
4
  DEFAULT_TAG,
5
+ EventDefinitionId,
5
6
  ParameterIndexId,
6
7
  ParameterIndexLookupCreator,
7
8
  SourceTableRef
@@ -35,6 +36,13 @@ export interface SourceTableOptions {
35
36
  parameterLookupSources: ParameterIndexLookupCreator[];
36
37
  bucketDataSourceIds?: Set<BucketDefinitionId>;
37
38
  parameterLookupSourceIds?: Set<ParameterIndexId>;
39
+ /**
40
+ * Compiled event definitions assigned to this persisted source-table record.
41
+ *
42
+ * Undefined is the legacy/non-incremental representation where event selection is
43
+ * based on the table ref. V3 incremental storage always supplies this set.
44
+ */
45
+ eventDefinitionIds?: Set<EventDefinitionId>;
38
46
  /**
39
47
  * Source-specific metadata. Null when no metadata has been recorded.
40
48
  */
@@ -75,11 +83,11 @@ export class SourceTable {
75
83
  public syncParameters = true;
76
84
 
77
85
  /**
78
- * True if this table should fire events for row changes.
86
+ * True if this table should evaluate event definitions for row changes.
79
87
  *
80
- * This value is resolved externally, and cached here. When multiple SourceTables exist
81
- * for the same SourceTableRef (v3 storage), resolveTables designates exactly one of them
82
- * as the event carrier, so that a row change saved once per table fires each event once.
88
+ * This value is resolved externally, and cached here. V3 storage assigns disjoint
89
+ * event-definition ids to SourceTables for the same physical table. Multiple records
90
+ * may evaluate different events, but each event id is evaluated through at most one record.
83
91
  *
84
92
  * Defaults to true for tests.
85
93
  */
@@ -150,6 +158,10 @@ export class SourceTable {
150
158
  return this.options.parameterLookupSourceIds;
151
159
  }
152
160
 
161
+ get eventDefinitionIds() {
162
+ return this.options.eventDefinitionIds;
163
+ }
164
+
153
165
  get sourceMetadata() {
154
166
  return this.options.sourceMetadata ?? null;
155
167
  }
@@ -192,6 +204,7 @@ export class SourceTable {
192
204
  bucketDataSourceIds: this.bucketDataSourceIds == null ? undefined : new Set(this.bucketDataSourceIds),
193
205
  parameterLookupSourceIds:
194
206
  this.parameterLookupSourceIds == null ? undefined : new Set(this.parameterLookupSourceIds),
207
+ eventDefinitionIds: this.eventDefinitionIds == null ? undefined : new Set(this.eventDefinitionIds),
195
208
  sourceMetadata: structuredClone(sourceMetadata)
196
209
  });
197
210
  copy.syncData = this.syncData;
@@ -39,6 +39,11 @@ export const STORAGE_VERSION_2 = 2;
39
39
  */
40
40
  export const STORAGE_VERSION_3 = 3;
41
41
 
42
+ /**
43
+ * Same as version 3, but marked as stable.
44
+ */
45
+ export const STORAGE_VERSION_4 = 4;
46
+
42
47
  /**
43
48
  * Oldest supported storage version.
44
49
  */
@@ -54,7 +59,7 @@ export const CURRENT_STORAGE_VERSION = STORAGE_VERSION_2;
54
59
  *
55
60
  * This includes unstable versions.
56
61
  */
57
- export const SUPPORTED_STORAGE_VERSIONS = [STORAGE_VERSION_1, STORAGE_VERSION_2, STORAGE_VERSION_3];
62
+ export const SUPPORTED_STORAGE_VERSIONS = [STORAGE_VERSION_1, STORAGE_VERSION_2, STORAGE_VERSION_3, STORAGE_VERSION_4];
58
63
 
59
64
  /**
60
65
  * Shared storage-version behavior across storage implementations.
@@ -74,5 +79,10 @@ export const STORAGE_VERSION_CONFIG: Record<number, StorageVersionConfig | undef
74
79
  version: STORAGE_VERSION_3,
75
80
  versionedBuckets: true,
76
81
  softDeleteCurrentData: true
82
+ },
83
+ [STORAGE_VERSION_4]: {
84
+ version: STORAGE_VERSION_4,
85
+ versionedBuckets: true,
86
+ softDeleteCurrentData: true
77
87
  }
78
88
  };
@@ -97,12 +97,20 @@ export interface SyncRulesBucketStorage
97
97
  */
98
98
  reportError(e: any): Promise<void>;
99
99
 
100
+ /**
101
+ * Whether parameter compaction can run when {@link CompactOptions.incrementalOnly} is set.
102
+ *
103
+ * Storage implementations that do not support incremental parameter compaction must return
104
+ * false; the compact command will skip parameter compaction in that mode.
105
+ */
106
+ supportsIncrementalParameterCompaction(): boolean;
107
+
100
108
  compact(options?: CompactOptions): Promise<void>;
101
109
 
102
110
  /**
103
- * Lightweight "compact" process to populate the checksum cache, if any.
111
+ * Compact storage after initial replication, before the first checkpoint exists.
104
112
  */
105
- populatePersistentChecksumCache(options: PopulateChecksumCacheOptions): Promise<PopulateChecksumCacheResults>;
113
+ compactInitialReplication(options: CompactInitialReplicationOptions): Promise<CompactInitialReplicationResults>;
106
114
 
107
115
  // ## Read operations
108
116
 
@@ -277,6 +285,14 @@ export interface CreateWriterOptions extends ParseSyncConfigOptions {
277
285
  tracer?: PerformanceTracer<'storage' | 'evaluate'>;
278
286
 
279
287
  logger?: Logger;
288
+
289
+ /**
290
+ * Aborts long-running storage work started by this writer, such as uploads to object storage.
291
+ *
292
+ * This does not replace flushing or committing at the appropriate source boundary: it only
293
+ * cancels work that is still in flight when replication stops.
294
+ */
295
+ signal?: AbortSignal;
280
296
  }
281
297
 
282
298
  export interface StorageHooks {
@@ -321,6 +337,15 @@ export interface CompactOptions {
321
337
 
322
338
  compactParameterData?: boolean;
323
339
 
340
+ /**
341
+ * Only perform compaction that can be done incrementally.
342
+ *
343
+ * This includes full bucket compaction on MongoDB V3 storage.
344
+ *
345
+ * On MongoDB v1 and Postgres storage, this makes compacting a no-op.
346
+ */
347
+ incrementalOnly?: boolean;
348
+
324
349
  /**
325
350
  * Delete client-requested write checkpoints created before this time.
326
351
  *
@@ -354,6 +379,18 @@ export interface CompactOptions {
354
379
  */
355
380
  minChangeRatio?: number;
356
381
 
382
+ /** Minimum delay before a V3 bucket is checked for chunk compaction. Default: five minutes. */
383
+ minCompactChunkIntervalMs?: number;
384
+
385
+ /** Minimum elapsed write pressure before the v3 sliding-scale full compact. Default: two hours. */
386
+ minCompactFullIntervalMs?: number;
387
+
388
+ /** Maximum age of writes not covered by a v3 full compact. */
389
+ maxCompactFullIntervalMs?: number;
390
+
391
+ /** How long a v3 worker owns a claimed bucket before another worker may recover it. */
392
+ compactLeaseDurationMs?: number;
393
+
357
394
  /**
358
395
  * Internal/testing use: Cache size for compacting parameters.
359
396
  */
@@ -364,12 +401,12 @@ export interface CompactOptions {
364
401
  logger?: Logger;
365
402
  }
366
403
 
367
- export interface PopulateChecksumCacheOptions {
404
+ export interface CompactInitialReplicationOptions {
368
405
  /**
369
- * Compute checksums up to this op id.
406
+ * Compact data up to this op id.
370
407
  *
371
408
  * Defaults to the highest persisted op id for the replication stream, which covers
372
- * the common case of populating the cache right after initial replication, before
409
+ * the common case of compacting right after initial replication, before
373
410
  * the first checkpoint exists.
374
411
  */
375
412
  maxOpId?: util.InternalOpId;
@@ -377,9 +414,9 @@ export interface PopulateChecksumCacheOptions {
377
414
  signal?: AbortSignal;
378
415
  }
379
416
 
380
- export interface PopulateChecksumCacheResults {
417
+ export interface CompactInitialReplicationResults {
381
418
  /**
382
- * Number of buckets we have calculated checksums for.
419
+ * Number of buckets processed.
383
420
  */
384
421
  buckets: number;
385
422
  }
@@ -419,6 +456,9 @@ export interface BucketDataBatchOptions {
419
456
  /** Abort any in-progress work for this batch, including object-storage downloads. */
420
457
  signal?: AbortSignal;
421
458
 
459
+ /** Traces data reads performed by the storage implementation. */
460
+ tracer?: PerformanceTracer<string>;
461
+
422
462
  /** Limit number of documents returned. Defaults to 1000. */
423
463
  limit?: number;
424
464
 
@@ -1,3 +1,5 @@
1
+ import { EventDefinitionId } from '@powersync/service-sync-rules';
2
+
1
3
  export enum WriteCheckpointMode {
2
4
  /**
3
5
  * Raw mappings of `user_id` to `write_checkpoint`s should
@@ -31,7 +33,9 @@ export interface ClientRequestedCheckpointOptions {
31
33
  checkpoint_request_id?: bigint;
32
34
  }
33
35
 
34
- export interface CustomWriteCheckpointFilters extends BaseWriteCheckpointIdentifier {
36
+ export type SyncStorageCustomWriteCheckpointFilters = BaseWriteCheckpointIdentifier;
37
+
38
+ export interface CustomWriteCheckpointFilters extends SyncStorageCustomWriteCheckpointFilters {
35
39
  /**
36
40
  * Replication stream which was active when this checkpoint was created.
37
41
  */
@@ -44,6 +48,14 @@ export interface BatchedCustomWriteCheckpointOptions extends BaseWriteCheckpoint
44
48
  * "write checkpoint" in storage APIs for backwards compatibility.
45
49
  */
46
50
  checkpoint: bigint;
51
+ /**
52
+ * Compiled event definition which created the checkpoint.
53
+ *
54
+ * Incremental replication uses this to keep checkpoint records produced by
55
+ * active and processing sync configs separate. This is required by v3
56
+ * storage and unused by legacy storage versions.
57
+ */
58
+ event_id?: EventDefinitionId;
47
59
  /**
48
60
  * Required when this custom checkpoint was created from a client checkpoint
49
61
  * request and should be eligible for checkpoint request retention cleanup.
@@ -155,12 +167,29 @@ function shouldReplaceManagedWriteCheckpoint(
155
167
  return candidateRequestId > existingRequestId;
156
168
  }
157
169
 
158
- export type SyncStorageLastWriteCheckpointFilters = BaseWriteCheckpointIdentifier | ManagedWriteCheckpointFilters;
170
+ export type SyncStorageLastWriteCheckpointFilters =
171
+ | SyncStorageCustomWriteCheckpointFilters
172
+ | ManagedWriteCheckpointFilters;
159
173
  export type LastWriteCheckpointFilters = CustomWriteCheckpointFilters | ManagedWriteCheckpointFilters;
160
174
 
175
+ /** Configures checkpoint ownership and generation for a storage instance. */
176
+ export type WriteCheckpointModeConfig =
177
+ | {
178
+ mode: WriteCheckpointMode.CUSTOM;
179
+ /**
180
+ * Name of the event whose custom checkpoints are served. Storage resolves this name through the active sync
181
+ * config's persisted mapping; integrations must not construct or persist event ids themselves. Required by
182
+ * storage implementations that scope custom checkpoints by event definition.
183
+ */
184
+ eventName?: string;
185
+ }
186
+ | {
187
+ mode: WriteCheckpointMode.MANAGED;
188
+ };
189
+
161
190
  export interface BaseWriteCheckpointAPI {
162
191
  readonly writeCheckpointMode: WriteCheckpointMode;
163
- setWriteCheckpointMode(mode: WriteCheckpointMode): void;
192
+ setWriteCheckpointMode(config: WriteCheckpointModeConfig): void;
164
193
  createManagedWriteCheckpoints(
165
194
  checkpoints: ManagedWriteCheckpointOptions[]
166
195
  ): Promise<CreateManagedWriteCheckpointsResult>;