@powersync/service-core 0.0.0-dev-20260602073133 → 0.0.0-dev-20260909133214
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.
- package/CHANGELOG.md +152 -4
- package/dist/api/RouteAPI.d.ts +42 -14
- package/dist/api/diagnostics.d.ts +12 -1
- package/dist/api/diagnostics.js +36 -22
- package/dist/api/diagnostics.js.map +1 -1
- package/dist/entry/commands/compact-action.js +30 -22
- package/dist/entry/commands/compact-action.js.map +1 -1
- package/dist/metrics/metrics-interfaces.d.ts +5 -1
- package/dist/metrics/metrics-interfaces.js.map +1 -1
- package/dist/metrics/open-telemetry/MetricAttributeFilteringExporter.d.ts +36 -0
- package/dist/metrics/open-telemetry/MetricAttributeFilteringExporter.js +70 -0
- package/dist/metrics/open-telemetry/MetricAttributeFilteringExporter.js.map +1 -0
- package/dist/metrics/open-telemetry/OpenTelemetryMetricsFactory.js +6 -1
- package/dist/metrics/open-telemetry/OpenTelemetryMetricsFactory.js.map +1 -1
- package/dist/metrics/open-telemetry/util.js +7 -3
- package/dist/metrics/open-telemetry/util.js.map +1 -1
- package/dist/modules/AbstractModule.d.ts +6 -2
- package/dist/modules/AbstractModule.js.map +1 -1
- package/dist/replication/AbstractReplicator.d.ts +31 -7
- package/dist/replication/AbstractReplicator.js +168 -74
- package/dist/replication/AbstractReplicator.js.map +1 -1
- package/dist/replication/ReplicationModule.d.ts +3 -3
- package/dist/replication/ReplicationModule.js.map +1 -1
- package/dist/routes/RouterEngine.d.ts +1 -0
- package/dist/routes/RouterEngine.js +6 -1
- package/dist/routes/RouterEngine.js.map +1 -1
- package/dist/routes/configure-fastify.d.ts +104 -56
- package/dist/routes/configure-fastify.js +15 -2
- package/dist/routes/configure-fastify.js.map +1 -1
- package/dist/routes/endpoints/admin.d.ts +42 -24
- package/dist/routes/endpoints/admin.js +49 -36
- package/dist/routes/endpoints/admin.js.map +1 -1
- package/dist/routes/endpoints/checkpointing.d.ts +62 -0
- package/dist/routes/endpoints/checkpointing.js +65 -7
- package/dist/routes/endpoints/checkpointing.js.map +1 -1
- package/dist/routes/endpoints/socket-route.js +3 -2
- package/dist/routes/endpoints/socket-route.js.map +1 -1
- package/dist/routes/endpoints/sync-rules.js +25 -14
- package/dist/routes/endpoints/sync-rules.js.map +1 -1
- package/dist/routes/endpoints/sync-stream.js +1 -1
- package/dist/routes/endpoints/sync-stream.js.map +1 -1
- package/dist/runner/teardown.js +8 -8
- package/dist/runner/teardown.js.map +1 -1
- package/dist/storage/BucketStorageBatch.d.ts +64 -17
- package/dist/storage/BucketStorageBatch.js.map +1 -1
- package/dist/storage/BucketStorageFactory.d.ts +50 -31
- package/dist/storage/BucketStorageFactory.js +10 -20
- package/dist/storage/BucketStorageFactory.js.map +1 -1
- package/dist/storage/CheckpointChecksumInvalidatedError.d.ts +12 -0
- package/dist/storage/CheckpointChecksumInvalidatedError.js +17 -0
- package/dist/storage/CheckpointChecksumInvalidatedError.js.map +1 -0
- package/dist/storage/ChecksumCache.d.ts +11 -6
- package/dist/storage/ChecksumCache.js +5 -4
- package/dist/storage/ChecksumCache.js.map +1 -1
- package/dist/storage/ParsedSyncConfigSet.d.ts +23 -0
- package/dist/storage/ParsedSyncConfigSet.js +2 -0
- package/dist/storage/ParsedSyncConfigSet.js.map +1 -0
- package/dist/storage/PersistedReplicationStream.d.ts +32 -0
- package/dist/storage/PersistedReplicationStream.js +26 -0
- package/dist/storage/PersistedReplicationStream.js.map +1 -0
- package/dist/storage/PersistedSyncConfigContent.d.ts +65 -0
- package/dist/storage/PersistedSyncConfigContent.js +138 -0
- package/dist/storage/PersistedSyncConfigContent.js.map +1 -0
- package/dist/storage/PersistedSyncConfigStatus.d.ts +13 -0
- package/dist/storage/PersistedSyncConfigStatus.js +2 -0
- package/dist/storage/PersistedSyncConfigStatus.js.map +1 -0
- package/dist/storage/ReplicationEventPayload.d.ts +6 -1
- package/dist/storage/SourceEntity.d.ts +13 -2
- package/dist/storage/SourceTable.d.ts +53 -4
- package/dist/storage/SourceTable.js +62 -7
- package/dist/storage/SourceTable.js.map +1 -1
- package/dist/storage/SourceTableReconciler.d.ts +83 -0
- package/dist/storage/SourceTableReconciler.js +95 -0
- package/dist/storage/SourceTableReconciler.js.map +1 -0
- package/dist/storage/StorageVersionConfig.d.ts +4 -0
- package/dist/storage/StorageVersionConfig.js +10 -1
- package/dist/storage/StorageVersionConfig.js.map +1 -1
- package/dist/storage/SyncRulesBucketStorage.d.ts +162 -26
- package/dist/storage/SyncRulesBucketStorage.js +3 -0
- package/dist/storage/SyncRulesBucketStorage.js.map +1 -1
- package/dist/storage/WriteCheckpointAPI.d.ts +85 -6
- package/dist/storage/WriteCheckpointAPI.js +33 -0
- package/dist/storage/WriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/BucketDefinitionMapping.d.ts +133 -0
- package/dist/storage/implementation/BucketDefinitionMapping.js +351 -0
- package/dist/storage/implementation/BucketDefinitionMapping.js.map +1 -0
- package/dist/storage/implementation/IncrementalReprocessingCompatibility.d.ts +6 -0
- package/dist/storage/implementation/IncrementalReprocessingCompatibility.js +31 -0
- package/dist/storage/implementation/IncrementalReprocessingCompatibility.js.map +1 -0
- package/dist/storage/implementation/IncrementalReprocessingSyncConfigLog.d.ts +17 -0
- package/dist/storage/implementation/IncrementalReprocessingSyncConfigLog.js +70 -0
- package/dist/storage/implementation/IncrementalReprocessingSyncConfigLog.js.map +1 -0
- package/dist/storage/implementation/ReplicationStreamStorageIds.d.ts +15 -0
- package/dist/storage/implementation/ReplicationStreamStorageIds.js +20 -0
- package/dist/storage/implementation/ReplicationStreamStorageIds.js.map +1 -0
- package/dist/storage/storage-index.d.ts +10 -1
- package/dist/storage/storage-index.js +10 -1
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/storage/storage-metrics.js +120 -4
- package/dist/storage/storage-metrics.js.map +1 -1
- package/dist/sync/BucketChecksumState.d.ts +12 -2
- package/dist/sync/BucketChecksumState.js +69 -21
- package/dist/sync/BucketChecksumState.js.map +1 -1
- package/dist/sync/RequestTracker.d.ts +7 -0
- package/dist/sync/RequestTracker.js +12 -0
- package/dist/sync/RequestTracker.js.map +1 -1
- package/dist/sync/sync.js +184 -43
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/util.d.ts +5 -0
- package/dist/sync/util.js +19 -1
- package/dist/sync/util.js.map +1 -1
- package/dist/system/ServiceContext.d.ts +2 -0
- package/dist/system/ServiceContext.js +2 -0
- package/dist/system/ServiceContext.js.map +1 -1
- package/dist/tracing/PerformanceTracer.d.ts +23 -1
- package/dist/tracing/PerformanceTracer.js +11 -0
- package/dist/tracing/PerformanceTracer.js.map +1 -1
- package/dist/util/checkpointing.d.ts +4 -8
- package/dist/util/checkpointing.js +1 -16
- package/dist/util/checkpointing.js.map +1 -1
- package/dist/util/config/compound-config-collector.js +23 -2
- package/dist/util/config/compound-config-collector.js.map +1 -1
- package/dist/util/config/defaults.d.ts +2 -0
- package/dist/util/config/defaults.js +2 -0
- package/dist/util/config/defaults.js.map +1 -1
- package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js +1 -0
- package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js.map +1 -1
- package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js +1 -0
- package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -1
- package/dist/util/config/sync-rules/sync-rules-provider.d.ts +2 -0
- package/dist/util/config/sync-rules/sync-rules-provider.js +3 -0
- package/dist/util/config/sync-rules/sync-rules-provider.js.map +1 -1
- package/dist/util/config/types.d.ts +4 -1
- package/dist/util/lsn.d.ts +4 -0
- package/dist/util/lsn.js +10 -0
- package/dist/util/lsn.js.map +1 -1
- package/dist/util/protocol-types.d.ts +4 -4
- package/dist/util/protocol-types.js +3 -2
- package/dist/util/protocol-types.js.map +1 -1
- package/dist/util/util-index.d.ts +1 -0
- package/dist/util/util-index.js +1 -0
- package/dist/util/util-index.js.map +1 -1
- package/dist/util/utils.d.ts +3 -2
- package/dist/util/utils.js +17 -2
- package/dist/util/utils.js.map +1 -1
- package/dist/util/write-checkpoint-batcher.d.ts +19 -0
- package/dist/util/write-checkpoint-batcher.js +100 -0
- package/dist/util/write-checkpoint-batcher.js.map +1 -0
- package/package.json +7 -7
- package/src/api/RouteAPI.ts +43 -14
- package/src/api/diagnostics.ts +45 -26
- package/src/entry/commands/compact-action.ts +37 -23
- package/src/metrics/metrics-interfaces.ts +6 -1
- package/src/metrics/open-telemetry/MetricAttributeFilteringExporter.ts +85 -0
- package/src/metrics/open-telemetry/OpenTelemetryMetricsFactory.ts +7 -2
- package/src/metrics/open-telemetry/util.ts +7 -3
- package/src/modules/AbstractModule.ts +6 -2
- package/src/replication/AbstractReplicator.ts +212 -78
- package/src/replication/ReplicationModule.ts +2 -3
- package/src/routes/RouterEngine.ts +7 -1
- package/src/routes/configure-fastify.ts +25 -2
- package/src/routes/endpoints/admin.ts +75 -51
- package/src/routes/endpoints/checkpointing.ts +79 -8
- package/src/routes/endpoints/socket-route.ts +3 -2
- package/src/routes/endpoints/sync-rules.ts +26 -14
- package/src/routes/endpoints/sync-stream.ts +1 -1
- package/src/runner/teardown.ts +8 -8
- package/src/storage/BucketStorageBatch.ts +65 -18
- package/src/storage/BucketStorageFactory.ts +73 -46
- package/src/storage/CheckpointChecksumInvalidatedError.ts +17 -0
- package/src/storage/ChecksumCache.ts +27 -14
- package/src/storage/ParsedSyncConfigSet.ts +25 -0
- package/src/storage/PersistedReplicationStream.ts +55 -0
- package/src/storage/PersistedSyncConfigContent.ts +207 -0
- package/src/storage/PersistedSyncConfigStatus.ts +14 -0
- package/src/storage/ReplicationEventPayload.ts +6 -1
- package/src/storage/SourceEntity.ts +12 -2
- package/src/storage/SourceTable.ts +96 -8
- package/src/storage/SourceTableReconciler.ts +188 -0
- package/src/storage/StorageVersionConfig.ts +11 -1
- package/src/storage/SyncRulesBucketStorage.ts +189 -27
- package/src/storage/WriteCheckpointAPI.ts +140 -6
- package/src/storage/implementation/BucketDefinitionMapping.ts +539 -0
- package/src/storage/implementation/IncrementalReprocessingCompatibility.ts +41 -0
- package/src/storage/implementation/IncrementalReprocessingSyncConfigLog.ts +126 -0
- package/src/storage/implementation/ReplicationStreamStorageIds.ts +21 -0
- package/src/storage/storage-index.ts +11 -1
- package/src/storage/storage-metrics.ts +154 -8
- package/src/sync/BucketChecksumState.ts +105 -21
- package/src/sync/RequestTracker.ts +20 -0
- package/src/sync/sync.ts +214 -50
- package/src/sync/util.ts +33 -8
- package/src/system/ServiceContext.ts +7 -0
- package/src/tracing/PerformanceTracer.ts +41 -1
- package/src/util/checkpointing.ts +4 -22
- package/src/util/config/compound-config-collector.ts +31 -1
- package/src/util/config/defaults.ts +2 -0
- package/src/util/config/sync-rules/impl/base64-sync-rules-collector.ts +1 -0
- package/src/util/config/sync-rules/impl/filesystem-sync-rules-collector.ts +1 -0
- package/src/util/config/sync-rules/sync-rules-provider.ts +5 -0
- package/src/util/config/types.ts +4 -1
- package/src/util/lsn.ts +9 -0
- package/src/util/protocol-types.ts +3 -2
- package/src/util/util-index.ts +1 -0
- package/src/util/utils.ts +16 -2
- package/src/util/write-checkpoint-batcher.ts +142 -0
- package/test/src/AbstractReplicator.test.ts +177 -0
- package/test/src/MetricAttributeFilteringExporter.test.ts +96 -0
- package/test/src/PersistedSyncConfigContent.test.ts +152 -0
- package/test/src/checksum_cache.test.ts +22 -0
- package/test/src/config.test.ts +142 -0
- package/test/src/diagnostics.test.ts +87 -34
- package/test/src/module-loader.test.ts +33 -1
- package/test/src/routes/admin.test.ts +188 -18
- package/test/src/routes/checkpointing.test.ts +54 -0
- package/test/src/routes/mocks.ts +6 -2
- package/test/src/routes/stream.test.ts +123 -2
- package/test/src/source-table-reconciler.test.ts +244 -0
- package/test/src/storage/SourceTable.test.ts +92 -0
- package/test/src/storage-metrics.test.ts +148 -0
- package/test/src/sync/BucketChecksumState.test.ts +98 -12
- package/test/src/sync/util.test.ts +28 -1
- package/test/src/util/checkpointing.test.ts +249 -0
- package/test/src/util/utils.test.ts +23 -0
- package/tsconfig.json +1 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/storage/PersistedSyncRulesContent.d.ts +0 -59
- package/dist/storage/PersistedSyncRulesContent.js +0 -100
- package/dist/storage/PersistedSyncRulesContent.js.map +0 -1
- package/src/storage/PersistedSyncRulesContent.ts +0 -174
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_HYDRATION_STATE,
|
|
3
|
+
DEFAULT_TAG,
|
|
4
|
+
nodeSqlite,
|
|
5
|
+
PrecompiledSyncConfig,
|
|
6
|
+
serializeSyncPlan,
|
|
7
|
+
SqlSyncRules
|
|
8
|
+
} from '@powersync/service-sync-rules';
|
|
9
|
+
import * as sqlite from 'node:sqlite';
|
|
10
|
+
import { describe, expect, test } from 'vitest';
|
|
11
|
+
import {
|
|
12
|
+
parsePersistedSyncConfigContent,
|
|
13
|
+
SerializedSyncPlan,
|
|
14
|
+
updateSyncRulesFromConfig
|
|
15
|
+
} from '../../src/storage/storage-index.js';
|
|
16
|
+
|
|
17
|
+
const EVENT_QUERY = 'SELECT user_id, checkpoint FROM checkpoints WHERE active = true';
|
|
18
|
+
const yamlWithoutEvents = `
|
|
19
|
+
config:
|
|
20
|
+
edition: 3
|
|
21
|
+
|
|
22
|
+
streams:
|
|
23
|
+
checkpoints:
|
|
24
|
+
query: SELECT * FROM checkpoints
|
|
25
|
+
`;
|
|
26
|
+
const yamlWithEvents = `${yamlWithoutEvents}
|
|
27
|
+
event_definitions:
|
|
28
|
+
write_checkpoints:
|
|
29
|
+
payloads:
|
|
30
|
+
- ${EVENT_QUERY}
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
describe('persisted compiled replication events', () => {
|
|
34
|
+
test('dual-writes additive compiled events and restores the compiled evaluator', () => {
|
|
35
|
+
const parsed = SqlSyncRules.fromYaml(yamlWithEvents, { defaultSchema: 'test_schema' });
|
|
36
|
+
const update = updateSyncRulesFromConfig(parsed);
|
|
37
|
+
const compiled = update.config.plan!;
|
|
38
|
+
const originalEvent = compiled.plan.events![0];
|
|
39
|
+
|
|
40
|
+
expect(compiled.plan.version).toBeLessThanOrEqual(2);
|
|
41
|
+
expect(compiled.plan.events).toHaveLength(1);
|
|
42
|
+
expect(originalEvent.name).toBe('write_checkpoints');
|
|
43
|
+
expect(compiled.eventDescriptors).toEqual({ write_checkpoints: [EVENT_QUERY] });
|
|
44
|
+
|
|
45
|
+
const restored = restore(compiled);
|
|
46
|
+
expect(restored.config).toBeInstanceOf(PrecompiledSyncConfig);
|
|
47
|
+
expect(restored.config).not.toHaveProperty('eventDescriptors');
|
|
48
|
+
expect(restored.config.eventDefinitions).toHaveLength(1);
|
|
49
|
+
expect(restored.config.eventDefinitions[0].name).toBe('write_checkpoints');
|
|
50
|
+
|
|
51
|
+
const hydrated = restored.config.hydrate({
|
|
52
|
+
hydrationState: DEFAULT_HYDRATION_STATE,
|
|
53
|
+
sqlite: nodeSqlite(sqlite)
|
|
54
|
+
});
|
|
55
|
+
expect(
|
|
56
|
+
hydrated.eventDescriptors[0].evaluateRowWithErrors({
|
|
57
|
+
sourceTable: { connectionTag: DEFAULT_TAG, schema: 'test_schema', name: 'checkpoints' },
|
|
58
|
+
record: { user_id: 'user-1', checkpoint: 4n, active: 1 }
|
|
59
|
+
})
|
|
60
|
+
).toEqual({ results: [{ data: { user_id: 'user-1', checkpoint: 4n } }], errors: [] });
|
|
61
|
+
|
|
62
|
+
// This represents what an older compiler sees after ignoring the additive plan.events field. The raw descriptor
|
|
63
|
+
// mirror is normalized into the compiled representation by the new loading boundary, retaining the legacy behavior
|
|
64
|
+
// of ignoring payload filters until the sync config is redeployed.
|
|
65
|
+
const { events: _ignored, ...planWithoutCompiledEvents } = compiled.plan;
|
|
66
|
+
const legacyView = restore({ ...compiled, plan: planWithoutCompiledEvents });
|
|
67
|
+
expect(legacyView.config).not.toHaveProperty('eventDescriptors');
|
|
68
|
+
expect(legacyView.config.eventDefinitions).toHaveLength(1);
|
|
69
|
+
expect(legacyView.config.eventDefinitions[0].name).toBe('write_checkpoints');
|
|
70
|
+
expect(legacyView.errors).toHaveLength(1);
|
|
71
|
+
expect(legacyView.errors[0]).toMatchObject({ type: 'warning' });
|
|
72
|
+
|
|
73
|
+
const legacyEvent = serializeSyncPlan((legacyView.config as PrecompiledSyncConfig).plan).events![0];
|
|
74
|
+
expect(legacyEvent.sourceQueries[0].variants[0].filters).toEqual([]);
|
|
75
|
+
expect(originalEvent.sourceQueries[0].variants[0].filters).not.toEqual([]);
|
|
76
|
+
|
|
77
|
+
const legacyHydrated = legacyView.config.hydrate({
|
|
78
|
+
hydrationState: DEFAULT_HYDRATION_STATE,
|
|
79
|
+
sqlite: nodeSqlite(sqlite)
|
|
80
|
+
});
|
|
81
|
+
expect(
|
|
82
|
+
legacyHydrated.eventDescriptors[0].evaluateRowWithErrors({
|
|
83
|
+
sourceTable: { connectionTag: DEFAULT_TAG, schema: 'test_schema', name: 'checkpoints' },
|
|
84
|
+
record: { user_id: 'user-1', checkpoint: 4n, active: 0 }
|
|
85
|
+
})
|
|
86
|
+
).toEqual({ results: [{ data: { user_id: 'user-1', checkpoint: 4n } }], errors: [] });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('restores raw event descriptors attached to version 1 and 2 plans', () => {
|
|
90
|
+
const parsed = SqlSyncRules.fromYaml(yamlWithoutEvents, { defaultSchema: 'test_schema' });
|
|
91
|
+
const update = updateSyncRulesFromConfig(parsed);
|
|
92
|
+
const legacy: SerializedSyncPlan = {
|
|
93
|
+
...update.config.plan!,
|
|
94
|
+
eventDescriptors: { write_checkpoints: [EVENT_QUERY] }
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
expect(legacy.plan.version).toBeLessThanOrEqual(2);
|
|
98
|
+
const restored = restore(legacy);
|
|
99
|
+
expect(restored.config).not.toHaveProperty('eventDescriptors');
|
|
100
|
+
expect(restored.config.eventDefinitions).toHaveLength(1);
|
|
101
|
+
expect((restored.config as PrecompiledSyncConfig).plan.events).toMatchObject([
|
|
102
|
+
{ name: 'write_checkpoints', sourceQueries: [{ sql: EVENT_QUERY }] }
|
|
103
|
+
]);
|
|
104
|
+
expect(restored.errors).toHaveLength(1);
|
|
105
|
+
expect(restored.errors[0]).toMatchObject({ type: 'warning' });
|
|
106
|
+
expect(
|
|
107
|
+
serializeSyncPlan((restored.config as PrecompiledSyncConfig).plan).events![0].sourceQueries[0].variants[0].filters
|
|
108
|
+
).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Event SQL accepted by the legacy evaluator must remain loadable after an upgrade, even when its ignored filter uses
|
|
113
|
+
* expressions that the compiled event evaluator intentionally rejects for new deployments.
|
|
114
|
+
*/
|
|
115
|
+
test.each([
|
|
116
|
+
['request parameters', 'SELECT user_id FROM checkpoints WHERE user_id = request.user_id()'],
|
|
117
|
+
['unsupported expressions', 'SELECT user_id FROM checkpoints WHERE active IN (1, 2)']
|
|
118
|
+
])('loads legacy event descriptors without validating ignored %s', (_description, eventQuery) => {
|
|
119
|
+
const parsed = SqlSyncRules.fromYaml(yamlWithoutEvents, { defaultSchema: 'test_schema' });
|
|
120
|
+
const update = updateSyncRulesFromConfig(parsed);
|
|
121
|
+
const legacy: SerializedSyncPlan = {
|
|
122
|
+
...update.config.plan!,
|
|
123
|
+
eventDescriptors: {
|
|
124
|
+
write_checkpoints: [eventQuery]
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const restored = restore(legacy);
|
|
129
|
+
expect(restored.errors).toHaveLength(1);
|
|
130
|
+
expect(restored.errors[0]).toMatchObject({ type: 'warning' });
|
|
131
|
+
|
|
132
|
+
const event = restored.config.hydrate({
|
|
133
|
+
hydrationState: DEFAULT_HYDRATION_STATE,
|
|
134
|
+
sqlite: nodeSqlite(sqlite)
|
|
135
|
+
}).eventDescriptors[0];
|
|
136
|
+
expect(
|
|
137
|
+
event.evaluateRowWithErrors({
|
|
138
|
+
sourceTable: { connectionTag: DEFAULT_TAG, schema: 'test_schema', name: 'checkpoints' },
|
|
139
|
+
record: { user_id: 'user-1' }
|
|
140
|
+
})
|
|
141
|
+
).toEqual({ results: [{ data: { user_id: 'user-1' } }], errors: [] });
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
function restore(compiledPlan: SerializedSyncPlan) {
|
|
146
|
+
return parsePersistedSyncConfigContent({
|
|
147
|
+
content: yamlWithEvents,
|
|
148
|
+
compiledPlan,
|
|
149
|
+
storageVersion: 1,
|
|
150
|
+
parseOptions: { defaultSchema: 'test_schema' }
|
|
151
|
+
});
|
|
152
|
+
}
|
|
@@ -482,4 +482,26 @@ describe('checksum cache', function () {
|
|
|
482
482
|
expect(await cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([TEST_123]);
|
|
483
483
|
expect(await cache.getChecksums(1234n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([TEST_1234]);
|
|
484
484
|
});
|
|
485
|
+
|
|
486
|
+
it('should refresh cached checksums after the configured TTL', async function () {
|
|
487
|
+
let lookups: FetchPartialBucketChecksum[][] = [];
|
|
488
|
+
const cache = new ChecksumCache({
|
|
489
|
+
fetchChecksums: async (batch) => {
|
|
490
|
+
lookups.push(batch);
|
|
491
|
+
return fetchTestChecksums(batch);
|
|
492
|
+
},
|
|
493
|
+
ttlMs: 1
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
expect(await cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([TEST_123]);
|
|
497
|
+
expect(await cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([TEST_123]);
|
|
498
|
+
|
|
499
|
+
await new Promise((resolve) => setTimeout(resolve, 1001)); // Needs to be >1s
|
|
500
|
+
|
|
501
|
+
expect(await cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([TEST_123]);
|
|
502
|
+
expect(lookups.map(removeLookupSources)).toEqual([
|
|
503
|
+
[{ bucket: 'test', end: 123n }],
|
|
504
|
+
[{ bucket: 'test', end: 123n }]
|
|
505
|
+
]);
|
|
506
|
+
});
|
|
485
507
|
});
|
package/test/src/config.test.ts
CHANGED
|
@@ -70,6 +70,146 @@ describe('Config', () => {
|
|
|
70
70
|
expect(config.api_parameters.max_buckets_per_connection).toBe(1);
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
it('should resolve the storage default version', {}, async () => {
|
|
74
|
+
const yamlConfig = /* yaml */ `
|
|
75
|
+
# PowerSync config
|
|
76
|
+
replication:
|
|
77
|
+
connections: []
|
|
78
|
+
storage:
|
|
79
|
+
type: mongodb
|
|
80
|
+
default_storage_version: 4
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
const collector = new CompoundConfigCollector();
|
|
84
|
+
|
|
85
|
+
const config = await collector.collectConfig({
|
|
86
|
+
config_base64: Buffer.from(yamlConfig, 'utf-8').toString('base64')
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(config.storage.default_storage_version).toBe(4);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it.each([1, 1.5, 5])('should reject unsupported storage default version %s', async (defaultStorageVersion) => {
|
|
93
|
+
const yamlConfig = /* yaml */ `
|
|
94
|
+
# PowerSync config
|
|
95
|
+
replication:
|
|
96
|
+
connections: []
|
|
97
|
+
storage:
|
|
98
|
+
type: mongodb
|
|
99
|
+
default_storage_version: ${defaultStorageVersion}
|
|
100
|
+
`;
|
|
101
|
+
|
|
102
|
+
const collector = new CompoundConfigCollector();
|
|
103
|
+
|
|
104
|
+
await expect(
|
|
105
|
+
collector.collectConfig({
|
|
106
|
+
config_base64: Buffer.from(yamlConfig, 'utf-8').toString('base64')
|
|
107
|
+
})
|
|
108
|
+
).rejects.toThrow(`Storage version ${defaultStorageVersion} is not supported`);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should resolve checkpoint request retention config', {}, async () => {
|
|
112
|
+
const yamlConfig = /* yaml */ `
|
|
113
|
+
# PowerSync config
|
|
114
|
+
replication:
|
|
115
|
+
connections: []
|
|
116
|
+
storage:
|
|
117
|
+
type: mongodb
|
|
118
|
+
api:
|
|
119
|
+
parameters:
|
|
120
|
+
checkpoint_request_retention_minutes: 15
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
const collector = new CompoundConfigCollector();
|
|
124
|
+
|
|
125
|
+
const config = await collector.collectConfig({
|
|
126
|
+
config_base64: Buffer.from(yamlConfig, 'utf-8').toString('base64')
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(config.api_parameters.checkpoint_request_retention_minutes).toBe(15);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should default checkpoint request retention to 60 minutes', {}, async () => {
|
|
133
|
+
const yamlConfig = /* yaml */ `
|
|
134
|
+
# PowerSync config
|
|
135
|
+
replication:
|
|
136
|
+
connections: []
|
|
137
|
+
storage:
|
|
138
|
+
type: mongodb
|
|
139
|
+
`;
|
|
140
|
+
|
|
141
|
+
const collector = new CompoundConfigCollector();
|
|
142
|
+
|
|
143
|
+
const config = await collector.collectConfig({
|
|
144
|
+
config_base64: Buffer.from(yamlConfig, 'utf-8').toString('base64')
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(config.api_parameters.checkpoint_request_retention_minutes).toBe(60);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it.each([0, -1, 1.5])('should reject checkpoint request retention of %s minutes', async (retentionMinutes) => {
|
|
151
|
+
const yamlConfig = /* yaml */ `
|
|
152
|
+
# PowerSync config
|
|
153
|
+
replication:
|
|
154
|
+
connections: []
|
|
155
|
+
storage:
|
|
156
|
+
type: mongodb
|
|
157
|
+
api:
|
|
158
|
+
parameters:
|
|
159
|
+
checkpoint_request_retention_minutes: ${retentionMinutes}
|
|
160
|
+
`;
|
|
161
|
+
|
|
162
|
+
const collector = new CompoundConfigCollector();
|
|
163
|
+
|
|
164
|
+
await expect(
|
|
165
|
+
collector.collectConfig({
|
|
166
|
+
config_base64: Buffer.from(yamlConfig, 'utf-8').toString('base64')
|
|
167
|
+
})
|
|
168
|
+
).rejects.toThrow('api.parameters.checkpoint_request_retention_minutes must be a positive integer');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should resolve checksum cache TTL from API parameters', async () => {
|
|
172
|
+
const yamlConfig = /* yaml */ `
|
|
173
|
+
# PowerSync config
|
|
174
|
+
replication:
|
|
175
|
+
connections: []
|
|
176
|
+
storage:
|
|
177
|
+
type: mongodb
|
|
178
|
+
api:
|
|
179
|
+
parameters:
|
|
180
|
+
bucket_count_cache_ttl_minutes: 15
|
|
181
|
+
`;
|
|
182
|
+
|
|
183
|
+
const collector = new CompoundConfigCollector();
|
|
184
|
+
|
|
185
|
+
const config = await collector.collectConfig({
|
|
186
|
+
config_base64: Buffer.from(yamlConfig, 'utf-8').toString('base64')
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
expect(config.api_parameters.bucket_count_cache_ttl_minutes).toBe(15);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should reject fractional checksum cache TTL minutes', async () => {
|
|
193
|
+
const yamlConfig = /* yaml */ `
|
|
194
|
+
# PowerSync config
|
|
195
|
+
replication:
|
|
196
|
+
connections: []
|
|
197
|
+
storage:
|
|
198
|
+
type: mongodb
|
|
199
|
+
api:
|
|
200
|
+
parameters:
|
|
201
|
+
bucket_count_cache_ttl_minutes: 1.5
|
|
202
|
+
`;
|
|
203
|
+
|
|
204
|
+
const collector = new CompoundConfigCollector();
|
|
205
|
+
|
|
206
|
+
await expect(
|
|
207
|
+
collector.collectConfig({
|
|
208
|
+
config_base64: Buffer.from(yamlConfig, 'utf-8').toString('base64')
|
|
209
|
+
})
|
|
210
|
+
).rejects.toThrow('api.parameters.bucket_count_cache_ttl_minutes must be a positive integer');
|
|
211
|
+
});
|
|
212
|
+
|
|
73
213
|
it('should throw YAML validation error for invalid base64 config', {}, async () => {
|
|
74
214
|
const yamlConfig = /* yaml */ `
|
|
75
215
|
# PowerSync config
|
|
@@ -96,6 +236,7 @@ describe('Config', () => {
|
|
|
96
236
|
storage:
|
|
97
237
|
type: mongodb
|
|
98
238
|
sync_config:
|
|
239
|
+
version_label: v6
|
|
99
240
|
content: |
|
|
100
241
|
config:
|
|
101
242
|
edition: 2
|
|
@@ -112,6 +253,7 @@ describe('Config', () => {
|
|
|
112
253
|
|
|
113
254
|
expect(result.sync_rules).toEqual({
|
|
114
255
|
present: true,
|
|
256
|
+
version_label: 'v6',
|
|
115
257
|
exit_on_error: true,
|
|
116
258
|
content: expect.stringContaining('edition: 2')
|
|
117
259
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DiagnosticsOptions, getSyncRulesStatus } from '@/api/diagnostics.js';
|
|
2
2
|
import { RouteAPI, SlotWalBudgetInfo } from '@/api/RouteAPI.js';
|
|
3
|
-
import {
|
|
3
|
+
import { ParsedSyncConfigSet, storage } from '@/index.js';
|
|
4
4
|
import { SqlSyncRules } from '@powersync/service-sync-rules';
|
|
5
5
|
import { describe, expect, test } from 'vitest';
|
|
6
6
|
|
|
@@ -13,53 +13,59 @@ bucket_definitions:
|
|
|
13
13
|
- SELECT id FROM test_table
|
|
14
14
|
`;
|
|
15
15
|
|
|
16
|
-
function makeSyncRulesContent(overrides?: {
|
|
16
|
+
function makeSyncRulesContent(overrides?: {
|
|
17
|
+
slot_name?: string;
|
|
18
|
+
version_label?: string;
|
|
19
|
+
status?: storage.PersistedSyncConfigStatus;
|
|
20
|
+
}): storage.PersistedSyncConfigContent {
|
|
17
21
|
// We don't implement the entire interface correctly here - just enough to test the diagnostics logic.
|
|
18
|
-
|
|
19
|
-
id: 1,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
compiled_plan: null,
|
|
23
|
-
active: true,
|
|
24
|
-
storageVersion: 1,
|
|
22
|
+
const status = overrides?.status ?? {
|
|
23
|
+
id: '1',
|
|
24
|
+
replicationStreamId: 1,
|
|
25
|
+
state: storage.SyncRuleState.ACTIVE,
|
|
25
26
|
last_checkpoint_lsn: 'some_lsn',
|
|
26
27
|
last_fatal_error: null,
|
|
27
28
|
last_fatal_error_ts: null,
|
|
28
29
|
last_keepalive_ts: new Date(),
|
|
29
|
-
last_checkpoint_ts: new Date()
|
|
30
|
+
last_checkpoint_ts: new Date()
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
replicationStreamId: 1,
|
|
35
|
+
syncConfigId: null,
|
|
36
|
+
replicationStreamName: overrides?.slot_name ?? 'test_slot',
|
|
37
|
+
sync_rules_content: MINIMAL_SYNC_RULES,
|
|
38
|
+
compiled_plan: null,
|
|
39
|
+
storageVersion: 1,
|
|
40
|
+
syncConfigState: status.state,
|
|
41
|
+
version_label: overrides?.version_label,
|
|
30
42
|
parsed(options?: any) {
|
|
31
43
|
const syncRules = SqlSyncRules.fromYaml(MINIMAL_SYNC_RULES, {
|
|
32
44
|
...options,
|
|
33
45
|
defaultSchema: 'public'
|
|
34
46
|
});
|
|
35
47
|
return {
|
|
36
|
-
|
|
37
|
-
} as
|
|
48
|
+
syncConfigs: [syncRules]
|
|
49
|
+
} as ParsedSyncConfigSet;
|
|
38
50
|
},
|
|
39
|
-
lock() {
|
|
40
|
-
throw new Error('Not implemented in mock');
|
|
41
|
-
},
|
|
42
|
-
current_lock: null,
|
|
43
51
|
logger: null as any,
|
|
44
52
|
asUpdateOptions: null as any,
|
|
45
|
-
getStorageConfig: null as any
|
|
46
|
-
|
|
53
|
+
getStorageConfig: null as any,
|
|
54
|
+
async getSyncConfigStatus() {
|
|
55
|
+
return status;
|
|
56
|
+
}
|
|
57
|
+
} as storage.PersistedSyncConfigContent;
|
|
47
58
|
}
|
|
48
59
|
|
|
49
|
-
function
|
|
60
|
+
function makeSystemStorage() {
|
|
50
61
|
return {
|
|
51
|
-
|
|
62
|
+
async getStatus() {
|
|
52
63
|
return {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
snapshot_done: true,
|
|
56
|
-
checkpoint_lsn: 'some_lsn',
|
|
57
|
-
active: true
|
|
58
|
-
};
|
|
59
|
-
}
|
|
64
|
+
snapshotDone: true,
|
|
65
|
+
resumeLsn: 'some_lsn'
|
|
60
66
|
};
|
|
61
67
|
}
|
|
62
|
-
} as
|
|
68
|
+
} as storage.SyncRulesBucketStorage;
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
function makeRouteAPI(walBudget?: SlotWalBudgetInfo | undefined): RouteAPI {
|
|
@@ -92,7 +98,8 @@ function makeRouteAPI(walBudget?: SlotWalBudgetInfo | undefined): RouteAPI {
|
|
|
92
98
|
const OPTIONS: DiagnosticsOptions = {
|
|
93
99
|
live_status: true,
|
|
94
100
|
check_connection: true,
|
|
95
|
-
include_content: false
|
|
101
|
+
include_content: false,
|
|
102
|
+
active: true
|
|
96
103
|
};
|
|
97
104
|
|
|
98
105
|
describe('getSyncRulesStatus WAL budget warnings', () => {
|
|
@@ -102,7 +109,7 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
|
|
|
102
109
|
safe_wal_size: 4 * GB,
|
|
103
110
|
max_slot_wal_keep_size: 10 * GB
|
|
104
111
|
});
|
|
105
|
-
const result = await getSyncRulesStatus(
|
|
112
|
+
const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
|
|
106
113
|
const walWarnings = result!.errors.filter((e) => e.message.includes('WAL budget'));
|
|
107
114
|
expect(walWarnings).toHaveLength(1);
|
|
108
115
|
expect(walWarnings[0].level).toBe('warning');
|
|
@@ -115,7 +122,7 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
|
|
|
115
122
|
safe_wal_size: 8 * GB,
|
|
116
123
|
max_slot_wal_keep_size: 10 * GB
|
|
117
124
|
});
|
|
118
|
-
const result = await getSyncRulesStatus(
|
|
125
|
+
const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
|
|
119
126
|
const walWarnings = result!.errors.filter((e) => e.message.includes('WAL budget'));
|
|
120
127
|
expect(walWarnings).toHaveLength(0);
|
|
121
128
|
});
|
|
@@ -126,7 +133,7 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
|
|
|
126
133
|
safe_wal_size: -2.4 * GB,
|
|
127
134
|
max_slot_wal_keep_size: 1 * 1024 * 1024 // 1MB
|
|
128
135
|
});
|
|
129
|
-
const result = await getSyncRulesStatus(
|
|
136
|
+
const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
|
|
130
137
|
const walWarnings = result!.errors.filter((e) => e.message.includes('WAL budget'));
|
|
131
138
|
expect(walWarnings).toHaveLength(1);
|
|
132
139
|
expect(walWarnings[0].message).toContain('0%');
|
|
@@ -137,7 +144,7 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
|
|
|
137
144
|
const api = makeRouteAPI({
|
|
138
145
|
wal_status: 'lost'
|
|
139
146
|
});
|
|
140
|
-
const result = await getSyncRulesStatus(
|
|
147
|
+
const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
|
|
141
148
|
const walErrors = result!.errors.filter(
|
|
142
149
|
(e) => e.message.includes('WAL budget') || e.message.includes('PSYNC_S1146')
|
|
143
150
|
);
|
|
@@ -146,10 +153,56 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
|
|
|
146
153
|
|
|
147
154
|
test('no WAL error when getSlotWalBudget is not defined', async () => {
|
|
148
155
|
const api = makeRouteAPI();
|
|
149
|
-
const result = await getSyncRulesStatus(
|
|
156
|
+
const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
|
|
150
157
|
const walErrors = result!.errors.filter(
|
|
151
158
|
(e) => e.message.includes('WAL budget') || e.message.includes('PSYNC_S1146')
|
|
152
159
|
);
|
|
153
160
|
expect(walErrors).toHaveLength(0);
|
|
154
161
|
});
|
|
162
|
+
|
|
163
|
+
test('uses sync config status for status-derived diagnostics fields', async () => {
|
|
164
|
+
const configStatus: storage.PersistedSyncConfigStatus = {
|
|
165
|
+
id: 'config-a',
|
|
166
|
+
replicationStreamId: 1,
|
|
167
|
+
state: storage.SyncRuleState.ACTIVE,
|
|
168
|
+
last_checkpoint_lsn: 'config_lsn',
|
|
169
|
+
last_fatal_error: 'config failed',
|
|
170
|
+
last_fatal_error_ts: new Date('2026-01-01T00:00:00.000Z'),
|
|
171
|
+
last_keepalive_ts: new Date('2026-01-01T00:01:00.000Z'),
|
|
172
|
+
last_checkpoint_ts: new Date('2026-01-01T00:02:00.000Z')
|
|
173
|
+
};
|
|
174
|
+
const result = await getSyncRulesStatus(
|
|
175
|
+
makeRouteAPI(),
|
|
176
|
+
makeSyncRulesContent({ status: configStatus }),
|
|
177
|
+
OPTIONS,
|
|
178
|
+
makeSystemStorage()
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
expect(result!.connections[0].last_checkpoint_ts).toBe('2026-01-01T00:02:00.000Z');
|
|
182
|
+
expect(result!.connections[0].last_keepalive_ts).toBe('2026-01-01T00:01:00.000Z');
|
|
183
|
+
expect(result!.errors.some((error) => error.message == 'config failed')).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('includes the sync config version label', async () => {
|
|
187
|
+
const result = await getSyncRulesStatus(
|
|
188
|
+
makeRouteAPI(),
|
|
189
|
+
makeSyncRulesContent({ version_label: 'v6' }),
|
|
190
|
+
OPTIONS,
|
|
191
|
+
makeSystemStorage()
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
expect(result!.version_label).toBe('v6');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('includes the sync config version label when parsing fails', async () => {
|
|
198
|
+
const content = makeSyncRulesContent({ version_label: 'v6' });
|
|
199
|
+
content.parsed = () => {
|
|
200
|
+
throw new Error('Invalid sync config');
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const result = await getSyncRulesStatus(makeRouteAPI(), content, OPTIONS, makeSystemStorage());
|
|
204
|
+
|
|
205
|
+
expect(result!.version_label).toBe('v6');
|
|
206
|
+
expect(result!.errors).toEqual([expect.objectContaining({ level: 'fatal', message: 'Invalid sync config' })]);
|
|
207
|
+
});
|
|
155
208
|
});
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
AbstractModule,
|
|
3
|
+
loadModules,
|
|
4
|
+
ModuleManager,
|
|
5
|
+
ServiceContextContainer,
|
|
6
|
+
storage,
|
|
7
|
+
TearDownOptions
|
|
8
|
+
} from '@/index.js';
|
|
2
9
|
import { describe, expect, it } from 'vitest';
|
|
3
10
|
|
|
4
11
|
interface MockConfig {
|
|
@@ -27,6 +34,19 @@ class MockPostgresStorageModule extends AbstractModule {
|
|
|
27
34
|
async initialize(context: ServiceContextContainer): Promise<void> {}
|
|
28
35
|
async teardown(options: TearDownOptions): Promise<void> {}
|
|
29
36
|
}
|
|
37
|
+
class RecordingModule extends AbstractModule {
|
|
38
|
+
teardownOptions: TearDownOptions | undefined;
|
|
39
|
+
|
|
40
|
+
constructor(name: string) {
|
|
41
|
+
super({ name });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async initialize(context: ServiceContextContainer): Promise<void> {}
|
|
45
|
+
|
|
46
|
+
async teardown(options: TearDownOptions): Promise<void> {
|
|
47
|
+
this.teardownOptions = options;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
30
50
|
const mockLoaders = {
|
|
31
51
|
connection: {
|
|
32
52
|
mysql: async () => {
|
|
@@ -99,4 +119,16 @@ describe('module loader', () => {
|
|
|
99
119
|
|
|
100
120
|
await expect(loadModules(config as any, loaders)).rejects.toThrowError('Failed to load MySQL module');
|
|
101
121
|
});
|
|
122
|
+
|
|
123
|
+
it('passes replication streams through teardown options', async () => {
|
|
124
|
+
const module = new RecordingModule('RecordingModule');
|
|
125
|
+
const moduleManager = new ModuleManager();
|
|
126
|
+
const replicationStreams = [{ replicationStreamId: 1 }] as storage.PersistedReplicationStream[];
|
|
127
|
+
|
|
128
|
+
moduleManager.register([module]);
|
|
129
|
+
await moduleManager.tearDown({ replicationStreams, syncRules: replicationStreams });
|
|
130
|
+
|
|
131
|
+
expect(module.teardownOptions?.replicationStreams).toBe(replicationStreams);
|
|
132
|
+
expect(module.teardownOptions?.syncRules).toBe(replicationStreams);
|
|
133
|
+
});
|
|
102
134
|
});
|