@powersync/service-core 1.22.0 → 1.23.1

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 +23 -0
  2. package/dist/api/RouteAPI.d.ts +2 -2
  3. package/dist/api/diagnostics.d.ts +12 -1
  4. package/dist/api/diagnostics.js +34 -22
  5. package/dist/api/diagnostics.js.map +1 -1
  6. package/dist/entry/commands/compact-action.js +1 -1
  7. package/dist/entry/commands/compact-action.js.map +1 -1
  8. package/dist/modules/AbstractModule.d.ts +6 -2
  9. package/dist/modules/AbstractModule.js.map +1 -1
  10. package/dist/replication/AbstractReplicator.d.ts +14 -3
  11. package/dist/replication/AbstractReplicator.js +122 -62
  12. package/dist/replication/AbstractReplicator.js.map +1 -1
  13. package/dist/replication/ReplicationModule.d.ts +3 -3
  14. package/dist/replication/ReplicationModule.js.map +1 -1
  15. package/dist/routes/configure-fastify.d.ts +12 -12
  16. package/dist/routes/endpoints/admin.d.ts +24 -24
  17. package/dist/routes/endpoints/admin.js +46 -35
  18. package/dist/routes/endpoints/admin.js.map +1 -1
  19. package/dist/routes/endpoints/checkpointing.js +1 -1
  20. package/dist/routes/endpoints/checkpointing.js.map +1 -1
  21. package/dist/routes/endpoints/socket-route.js +1 -1
  22. package/dist/routes/endpoints/socket-route.js.map +1 -1
  23. package/dist/routes/endpoints/sync-rules.js +22 -13
  24. package/dist/routes/endpoints/sync-rules.js.map +1 -1
  25. package/dist/routes/endpoints/sync-stream.js +1 -1
  26. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  27. package/dist/runner/teardown.js +8 -8
  28. package/dist/runner/teardown.js.map +1 -1
  29. package/dist/storage/BucketStorageBatch.d.ts +0 -6
  30. package/dist/storage/BucketStorageBatch.js.map +1 -1
  31. package/dist/storage/BucketStorageFactory.d.ts +22 -27
  32. package/dist/storage/BucketStorageFactory.js +4 -18
  33. package/dist/storage/BucketStorageFactory.js.map +1 -1
  34. package/dist/storage/ParsedSyncConfigSet.d.ts +23 -0
  35. package/dist/storage/ParsedSyncConfigSet.js +2 -0
  36. package/dist/storage/ParsedSyncConfigSet.js.map +1 -0
  37. package/dist/storage/PersistedReplicationStream.d.ts +32 -0
  38. package/dist/storage/PersistedReplicationStream.js +26 -0
  39. package/dist/storage/PersistedReplicationStream.js.map +1 -0
  40. package/dist/storage/PersistedSyncConfigContent.d.ts +63 -0
  41. package/dist/storage/PersistedSyncConfigContent.js +125 -0
  42. package/dist/storage/PersistedSyncConfigContent.js.map +1 -0
  43. package/dist/storage/PersistedSyncConfigStatus.d.ts +13 -0
  44. package/dist/storage/PersistedSyncConfigStatus.js +2 -0
  45. package/dist/storage/PersistedSyncConfigStatus.js.map +1 -0
  46. package/dist/storage/SourceTable.d.ts +9 -3
  47. package/dist/storage/SourceTable.js +13 -3
  48. package/dist/storage/SourceTable.js.map +1 -1
  49. package/dist/storage/SyncRulesBucketStorage.d.ts +59 -16
  50. package/dist/storage/SyncRulesBucketStorage.js.map +1 -1
  51. package/dist/storage/implementation/BucketDefinitionMapping.d.ts +117 -0
  52. package/dist/storage/implementation/BucketDefinitionMapping.js +277 -0
  53. package/dist/storage/implementation/BucketDefinitionMapping.js.map +1 -0
  54. package/dist/storage/implementation/IncrementalReprocessingCompatibility.d.ts +6 -0
  55. package/dist/storage/implementation/IncrementalReprocessingCompatibility.js +31 -0
  56. package/dist/storage/implementation/IncrementalReprocessingCompatibility.js.map +1 -0
  57. package/dist/storage/implementation/IncrementalReprocessingSyncConfigLog.d.ts +17 -0
  58. package/dist/storage/implementation/IncrementalReprocessingSyncConfigLog.js +62 -0
  59. package/dist/storage/implementation/IncrementalReprocessingSyncConfigLog.js.map +1 -0
  60. package/dist/storage/implementation/ReplicationStreamStorageIds.d.ts +15 -0
  61. package/dist/storage/implementation/ReplicationStreamStorageIds.js +20 -0
  62. package/dist/storage/implementation/ReplicationStreamStorageIds.js.map +1 -0
  63. package/dist/storage/storage-index.d.ts +8 -1
  64. package/dist/storage/storage-index.js +8 -1
  65. package/dist/storage/storage-index.js.map +1 -1
  66. package/dist/sync/sync.js +5 -0
  67. package/dist/sync/sync.js.map +1 -1
  68. package/dist/util/checkpointing.js +1 -1
  69. package/dist/util/checkpointing.js.map +1 -1
  70. package/dist/util/config/types.d.ts +1 -1
  71. package/dist/util/lsn.d.ts +4 -0
  72. package/dist/util/lsn.js +10 -0
  73. package/dist/util/lsn.js.map +1 -1
  74. package/package.json +6 -7
  75. package/src/api/RouteAPI.ts +2 -2
  76. package/src/api/diagnostics.ts +43 -26
  77. package/src/entry/commands/compact-action.ts +1 -1
  78. package/src/modules/AbstractModule.ts +6 -2
  79. package/src/replication/AbstractReplicator.ts +145 -65
  80. package/src/replication/ReplicationModule.ts +2 -3
  81. package/src/routes/endpoints/admin.ts +72 -50
  82. package/src/routes/endpoints/checkpointing.ts +1 -1
  83. package/src/routes/endpoints/socket-route.ts +1 -1
  84. package/src/routes/endpoints/sync-rules.ts +23 -13
  85. package/src/routes/endpoints/sync-stream.ts +1 -1
  86. package/src/runner/teardown.ts +8 -8
  87. package/src/storage/BucketStorageBatch.ts +0 -7
  88. package/src/storage/BucketStorageFactory.ts +30 -39
  89. package/src/storage/ParsedSyncConfigSet.ts +25 -0
  90. package/src/storage/PersistedReplicationStream.ts +55 -0
  91. package/src/storage/PersistedSyncConfigContent.ts +192 -0
  92. package/src/storage/PersistedSyncConfigStatus.ts +14 -0
  93. package/src/storage/SourceTable.ts +20 -3
  94. package/src/storage/SyncRulesBucketStorage.ts +64 -16
  95. package/src/storage/implementation/BucketDefinitionMapping.ts +436 -0
  96. package/src/storage/implementation/IncrementalReprocessingCompatibility.ts +41 -0
  97. package/src/storage/implementation/IncrementalReprocessingSyncConfigLog.ts +109 -0
  98. package/src/storage/implementation/ReplicationStreamStorageIds.ts +21 -0
  99. package/src/storage/storage-index.ts +9 -1
  100. package/src/sync/sync.ts +5 -0
  101. package/src/util/checkpointing.ts +1 -1
  102. package/src/util/config/types.ts +1 -1
  103. package/src/util/lsn.ts +9 -0
  104. package/test/src/diagnostics.test.ts +62 -34
  105. package/test/src/module-loader.test.ts +33 -1
  106. package/test/src/routes/admin.test.ts +182 -18
  107. package/test/src/routes/mocks.ts +6 -2
  108. package/tsconfig.json +1 -4
  109. package/tsconfig.tsbuildinfo +1 -1
  110. package/dist/storage/PersistedSyncRulesContent.d.ts +0 -59
  111. package/dist/storage/PersistedSyncRulesContent.js +0 -100
  112. package/dist/storage/PersistedSyncRulesContent.js.map +0 -1
  113. package/src/storage/PersistedSyncRulesContent.ts +0 -174
@@ -1,6 +1,6 @@
1
1
  import { DiagnosticsOptions, getSyncRulesStatus } from '@/api/diagnostics.js';
2
2
  import { RouteAPI, SlotWalBudgetInfo } from '@/api/RouteAPI.js';
3
- import { BucketStorageFactory, PersistedSyncRules, storage } from '@/index.js';
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,57 @@ bucket_definitions:
13
13
  - SELECT id FROM test_table
14
14
  `;
15
15
 
16
- function makeSyncRulesContent(overrides?: { slot_name?: string }): storage.PersistedSyncRulesContent {
16
+ function makeSyncRulesContent(overrides?: {
17
+ slot_name?: string;
18
+ status?: storage.PersistedSyncConfigStatus;
19
+ }): storage.PersistedSyncConfigContent {
17
20
  // We don't implement the entire interface correctly here - just enough to test the diagnostics logic.
18
- return {
19
- id: 1,
20
- slot_name: overrides?.slot_name ?? 'test_slot',
21
- sync_rules_content: MINIMAL_SYNC_RULES,
22
- compiled_plan: null,
23
- active: true,
24
- storageVersion: 1,
21
+ const status = overrides?.status ?? {
22
+ id: '1',
23
+ replicationStreamId: 1,
24
+ state: storage.SyncRuleState.ACTIVE,
25
25
  last_checkpoint_lsn: 'some_lsn',
26
26
  last_fatal_error: null,
27
27
  last_fatal_error_ts: null,
28
28
  last_keepalive_ts: new Date(),
29
- last_checkpoint_ts: new Date(),
29
+ last_checkpoint_ts: new Date()
30
+ };
31
+
32
+ return {
33
+ replicationStreamId: 1,
34
+ syncConfigId: null,
35
+ replicationStreamName: overrides?.slot_name ?? 'test_slot',
36
+ sync_rules_content: MINIMAL_SYNC_RULES,
37
+ compiled_plan: null,
38
+ storageVersion: 1,
39
+ syncConfigState: status.state,
30
40
  parsed(options?: any) {
31
41
  const syncRules = SqlSyncRules.fromYaml(MINIMAL_SYNC_RULES, {
32
42
  ...options,
33
43
  defaultSchema: 'public'
34
44
  });
35
45
  return {
36
- syncConfigWithErrors: syncRules
37
- } as PersistedSyncRules;
38
- },
39
- lock() {
40
- throw new Error('Not implemented in mock');
46
+ syncConfigs: [syncRules]
47
+ } as ParsedSyncConfigSet;
41
48
  },
42
- current_lock: null,
43
49
  logger: null as any,
44
50
  asUpdateOptions: null as any,
45
- getStorageConfig: null as any
46
- } as storage.PersistedSyncRulesContent;
51
+ getStorageConfig: null as any,
52
+ async getSyncConfigStatus() {
53
+ return status;
54
+ }
55
+ } as storage.PersistedSyncConfigContent;
47
56
  }
48
57
 
49
- function makeBucketStorage() {
58
+ function makeSystemStorage() {
50
59
  return {
51
- getInstance() {
60
+ async getStatus() {
52
61
  return {
53
- async getStatus() {
54
- return {
55
- snapshot_done: true,
56
- checkpoint_lsn: 'some_lsn',
57
- active: true
58
- };
59
- }
62
+ snapshotDone: true,
63
+ resumeLsn: 'some_lsn'
60
64
  };
61
65
  }
62
- } as unknown as BucketStorageFactory;
66
+ } as storage.SyncRulesBucketStorage;
63
67
  }
64
68
 
65
69
  function makeRouteAPI(walBudget?: SlotWalBudgetInfo | undefined): RouteAPI {
@@ -92,7 +96,8 @@ function makeRouteAPI(walBudget?: SlotWalBudgetInfo | undefined): RouteAPI {
92
96
  const OPTIONS: DiagnosticsOptions = {
93
97
  live_status: true,
94
98
  check_connection: true,
95
- include_content: false
99
+ include_content: false,
100
+ active: true
96
101
  };
97
102
 
98
103
  describe('getSyncRulesStatus WAL budget warnings', () => {
@@ -102,7 +107,7 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
102
107
  safe_wal_size: 4 * GB,
103
108
  max_slot_wal_keep_size: 10 * GB
104
109
  });
105
- const result = await getSyncRulesStatus(makeBucketStorage(), api, makeSyncRulesContent(), OPTIONS);
110
+ const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
106
111
  const walWarnings = result!.errors.filter((e) => e.message.includes('WAL budget'));
107
112
  expect(walWarnings).toHaveLength(1);
108
113
  expect(walWarnings[0].level).toBe('warning');
@@ -115,7 +120,7 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
115
120
  safe_wal_size: 8 * GB,
116
121
  max_slot_wal_keep_size: 10 * GB
117
122
  });
118
- const result = await getSyncRulesStatus(makeBucketStorage(), api, makeSyncRulesContent(), OPTIONS);
123
+ const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
119
124
  const walWarnings = result!.errors.filter((e) => e.message.includes('WAL budget'));
120
125
  expect(walWarnings).toHaveLength(0);
121
126
  });
@@ -126,7 +131,7 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
126
131
  safe_wal_size: -2.4 * GB,
127
132
  max_slot_wal_keep_size: 1 * 1024 * 1024 // 1MB
128
133
  });
129
- const result = await getSyncRulesStatus(makeBucketStorage(), api, makeSyncRulesContent(), OPTIONS);
134
+ const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
130
135
  const walWarnings = result!.errors.filter((e) => e.message.includes('WAL budget'));
131
136
  expect(walWarnings).toHaveLength(1);
132
137
  expect(walWarnings[0].message).toContain('0%');
@@ -137,7 +142,7 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
137
142
  const api = makeRouteAPI({
138
143
  wal_status: 'lost'
139
144
  });
140
- const result = await getSyncRulesStatus(makeBucketStorage(), api, makeSyncRulesContent(), OPTIONS);
145
+ const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
141
146
  const walErrors = result!.errors.filter(
142
147
  (e) => e.message.includes('WAL budget') || e.message.includes('PSYNC_S1146')
143
148
  );
@@ -146,10 +151,33 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
146
151
 
147
152
  test('no WAL error when getSlotWalBudget is not defined', async () => {
148
153
  const api = makeRouteAPI();
149
- const result = await getSyncRulesStatus(makeBucketStorage(), api, makeSyncRulesContent(), OPTIONS);
154
+ const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
150
155
  const walErrors = result!.errors.filter(
151
156
  (e) => e.message.includes('WAL budget') || e.message.includes('PSYNC_S1146')
152
157
  );
153
158
  expect(walErrors).toHaveLength(0);
154
159
  });
160
+
161
+ test('uses sync config status for status-derived diagnostics fields', async () => {
162
+ const configStatus: storage.PersistedSyncConfigStatus = {
163
+ id: 'config-a',
164
+ replicationStreamId: 1,
165
+ state: storage.SyncRuleState.ACTIVE,
166
+ last_checkpoint_lsn: 'config_lsn',
167
+ last_fatal_error: 'config failed',
168
+ last_fatal_error_ts: new Date('2026-01-01T00:00:00.000Z'),
169
+ last_keepalive_ts: new Date('2026-01-01T00:01:00.000Z'),
170
+ last_checkpoint_ts: new Date('2026-01-01T00:02:00.000Z')
171
+ };
172
+ const result = await getSyncRulesStatus(
173
+ makeRouteAPI(),
174
+ makeSyncRulesContent({ status: configStatus }),
175
+ OPTIONS,
176
+ makeSystemStorage()
177
+ );
178
+
179
+ expect(result!.connections[0].last_checkpoint_ts).toBe('2026-01-01T00:02:00.000Z');
180
+ expect(result!.connections[0].last_keepalive_ts).toBe('2026-01-01T00:01:00.000Z');
181
+ expect(result!.errors.some((error) => error.message == 'config failed')).toBe(true);
182
+ });
155
183
  });
@@ -1,4 +1,11 @@
1
- import { AbstractModule, loadModules, ServiceContextContainer, TearDownOptions } from '@/index.js';
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
  });
@@ -1,27 +1,92 @@
1
- import { BasicRouterRequest, Context, JwtPayload } from '@/index.js';
1
+ import { BasicRouterRequest, Context, JwtPayload, ParsedSyncConfigSet, storage } from '@/index.js';
2
2
  import { logger } from '@powersync/lib-services-framework';
3
- import { describe, expect, it } from 'vitest';
4
- import { validate } from '../../../src/routes/endpoints/admin.js';
3
+ import { SqlSyncRules } from '@powersync/service-sync-rules';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+ import { diagnostics, reprocess, validate } from '../../../src/routes/endpoints/admin.js';
5
6
  import { mockServiceContext } from './mocks.js';
6
7
 
7
8
  describe('admin routes', () => {
9
+ const request: BasicRouterRequest = {
10
+ headers: {},
11
+ hostname: '',
12
+ protocol: 'http'
13
+ };
14
+
15
+ function makeContext(activeBucketStorage?: any): Context {
16
+ const service_context = mockServiceContext(null);
17
+ if (activeBucketStorage != null) {
18
+ (service_context.storageEngine as any).activeBucketStorage = activeBucketStorage;
19
+ }
20
+
21
+ return {
22
+ logger: logger,
23
+ service_context,
24
+ token_payload: new JwtPayload({
25
+ sub: '',
26
+ exp: 0,
27
+ iat: 0
28
+ })
29
+ };
30
+ }
31
+
32
+ function makeSyncConfigContent(options: {
33
+ id?: number;
34
+ syncConfigId?: string;
35
+ active?: boolean;
36
+ content?: string;
37
+ }): storage.PersistedSyncConfigContent {
38
+ const id = options.id ?? 1;
39
+ const syncConfigId = options.syncConfigId ?? String(id);
40
+ const active = options.active ?? true;
41
+ const state = active ? storage.SyncRuleState.ACTIVE : storage.SyncRuleState.PROCESSING;
42
+ const lastKeepaliveTs = new Date('2026-01-01T00:00:00.000Z');
43
+ const lastCheckpointTs = new Date('2026-01-01T00:00:00.000Z');
44
+ const syncConfigStatus = {
45
+ id: syncConfigId,
46
+ replicationStreamId: id,
47
+ state,
48
+ last_checkpoint_lsn: null,
49
+ last_fatal_error: null,
50
+ last_fatal_error_ts: null,
51
+ last_keepalive_ts: lastKeepaliveTs,
52
+ last_checkpoint_ts: lastCheckpointTs
53
+ };
54
+ const content = {
55
+ replicationStreamId: id,
56
+ syncConfigId,
57
+ replicationStreamName: `slot_${id}`,
58
+ sync_rules_content:
59
+ options.content ??
60
+ `
61
+ bucket_definitions:
62
+ global:
63
+ data:
64
+ - SELECT id FROM test
65
+ `,
66
+ compiled_plan: null,
67
+ storageVersion: storage.LEGACY_STORAGE_VERSION,
68
+ syncConfigState: syncConfigStatus.state,
69
+ parsed(options?: any) {
70
+ const syncRules = SqlSyncRules.fromYaml(content.sync_rules_content, {
71
+ ...options,
72
+ defaultSchema: 'public'
73
+ });
74
+ return {
75
+ syncConfigs: [syncRules]
76
+ } as ParsedSyncConfigSet;
77
+ },
78
+ asUpdateOptions: vi.fn(),
79
+ getStorageConfig: vi.fn(),
80
+ async getSyncConfigStatus() {
81
+ return syncConfigStatus;
82
+ }
83
+ };
84
+ return content as unknown as storage.PersistedSyncConfigContent;
85
+ }
86
+
8
87
  describe('validate', () => {
9
88
  it('reports errors with source location', async () => {
10
- const context: Context = {
11
- logger: logger,
12
- service_context: mockServiceContext(null),
13
- token_payload: new JwtPayload({
14
- sub: '',
15
- exp: 0,
16
- iat: 0
17
- })
18
- };
19
-
20
- const request: BasicRouterRequest = {
21
- headers: {},
22
- hostname: '',
23
- protocol: 'http'
24
- };
89
+ const context = makeContext();
25
90
 
26
91
  const response = await validate.handler({
27
92
  context,
@@ -45,4 +110,103 @@ bucket_definitions:
45
110
  ]);
46
111
  });
47
112
  });
113
+
114
+ describe('diagnostics', () => {
115
+ it('returns deploying config status', async () => {
116
+ const active = makeSyncConfigContent({ id: 1, syncConfigId: 'active-config' });
117
+ const deploying = makeSyncConfigContent({ id: 2, syncConfigId: 'deploying-config', active: false });
118
+ const getInstance = vi.fn(() => ({
119
+ async getStatus() {
120
+ return {
121
+ snapshotDone: false,
122
+ resumeLsn: null
123
+ };
124
+ }
125
+ }));
126
+ const activeBucketStorage = {
127
+ getActiveSyncConfig: vi.fn(async () => ({
128
+ content: active,
129
+ replicationStream: {},
130
+ storage: getInstance()
131
+ })),
132
+ getDeployingSyncConfig: vi.fn(async () => ({
133
+ content: deploying,
134
+ replicationStream: {},
135
+ storage: getInstance()
136
+ }))
137
+ };
138
+
139
+ const response = await diagnostics.handler({
140
+ context: makeContext(activeBucketStorage),
141
+ params: {},
142
+ request
143
+ });
144
+
145
+ expect(response.deploying_sync_rules?.connections[0].slot_name).toBe('slot_2');
146
+ expect(response.active_sync_rules?.connections[0].slot_name).toBe('slot_1');
147
+ });
148
+ });
149
+
150
+ describe('reprocess', () => {
151
+ it('reprocesses the active sync config', async () => {
152
+ const active = makeSyncConfigContent({ id: 7, syncConfigId: 'active-config' });
153
+ const updateSyncRules = vi.fn(async () => ({
154
+ replicationStreamId: 8,
155
+ replicationStreamName: 'new_slot',
156
+ state: storage.SyncRuleState.PROCESSING,
157
+ storageVersion: storage.LEGACY_STORAGE_VERSION,
158
+ replicationJobId: '8',
159
+ current_lock: null
160
+ }));
161
+ const activeBucketStorage = {
162
+ getDeployingSyncConfig: vi.fn(async () => null),
163
+ getActiveSyncConfig: vi.fn(async () => ({
164
+ content: active,
165
+ replicationStream: {},
166
+ storage: {}
167
+ })),
168
+ getSyncConfigContent: vi.fn(),
169
+ updateSyncRules
170
+ };
171
+
172
+ const response = await reprocess.handler({
173
+ context: makeContext(activeBucketStorage),
174
+ params: {},
175
+ request
176
+ });
177
+
178
+ expect(activeBucketStorage.getActiveSyncConfig).toHaveBeenCalledTimes(1);
179
+ expect(activeBucketStorage.getSyncConfigContent).not.toHaveBeenCalled();
180
+ expect(updateSyncRules).toHaveBeenCalledTimes(1);
181
+ expect(response.connections[0].slot_name).toBe('new_slot');
182
+ });
183
+
184
+ it('rejects reprocess while a sync config is deploying', async () => {
185
+ const activeBucketStorage = {
186
+ getDeployingSyncConfig: vi.fn(async () => ({
187
+ content: makeSyncConfigContent({ id: 2, active: false }),
188
+ replicationStream: {},
189
+ storage: {}
190
+ })),
191
+ getActiveSyncConfig: vi.fn(),
192
+ updateSyncRules: vi.fn()
193
+ };
194
+
195
+ await expect(
196
+ reprocess.handler({
197
+ context: makeContext(activeBucketStorage),
198
+ params: {},
199
+ request
200
+ })
201
+ ).rejects.toMatchObject({
202
+ errorData: {
203
+ status: 409,
204
+ code: 'PSYNC_S4106',
205
+ description: 'Busy processing sync config - cannot reprocess'
206
+ }
207
+ });
208
+ expect(activeBucketStorage.getActiveSyncConfig).not.toHaveBeenCalled();
209
+ expect(activeBucketStorage.updateSyncRules).not.toHaveBeenCalled();
210
+ });
211
+ });
48
212
  });
@@ -18,8 +18,12 @@ export function mockServiceContext(storage: Partial<SyncRulesBucketStorage> | nu
18
18
 
19
19
  const storageEngine: StorageEngine = {
20
20
  activeBucketStorage: {
21
- async getActiveStorage() {
22
- return storage;
21
+ async getActiveSyncConfig() {
22
+ return storage == null
23
+ ? null
24
+ : {
25
+ storage
26
+ };
23
27
  }
24
28
  } as Partial<BucketStorageFactory>
25
29
  } as any;
package/tsconfig.json CHANGED
@@ -2,10 +2,7 @@
2
2
  "extends": "../../tsconfig.base.json",
3
3
  "compilerOptions": {
4
4
  "rootDir": "src",
5
- "outDir": "dist",
6
- "esModuleInterop": true,
7
- "skipLibCheck": true,
8
- "sourceMap": true
5
+ "outDir": "dist"
9
6
  },
10
7
  "include": ["src"],
11
8
  "references": [