@powersync/service-core 1.22.0 → 1.23.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 +17 -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 +2 -2
  11. package/dist/replication/AbstractReplicator.js +24 -23
  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 +44 -34
  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 +60 -0
  41. package/dist/storage/PersistedSyncConfigContent.js +123 -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 +27 -26
  80. package/src/replication/ReplicationModule.ts +2 -3
  81. package/src/routes/endpoints/admin.ts +70 -49
  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 +188 -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 +61 -34
  105. package/test/src/module-loader.test.ts +33 -1
  106. package/test/src/routes/admin.test.ts +180 -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,56 @@ 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
21
  return {
19
- id: 1,
20
- slot_name: overrides?.slot_name ?? 'test_slot',
22
+ replicationStreamId: 1,
23
+ syncConfigId: null,
24
+ replicationStreamName: overrides?.slot_name ?? 'test_slot',
21
25
  sync_rules_content: MINIMAL_SYNC_RULES,
22
26
  compiled_plan: null,
23
- active: true,
24
27
  storageVersion: 1,
25
- last_checkpoint_lsn: 'some_lsn',
26
- last_fatal_error: null,
27
- last_fatal_error_ts: null,
28
- last_keepalive_ts: new Date(),
29
- last_checkpoint_ts: new Date(),
30
28
  parsed(options?: any) {
31
29
  const syncRules = SqlSyncRules.fromYaml(MINIMAL_SYNC_RULES, {
32
30
  ...options,
33
31
  defaultSchema: 'public'
34
32
  });
35
33
  return {
36
- syncConfigWithErrors: syncRules
37
- } as PersistedSyncRules;
34
+ syncConfigs: [syncRules]
35
+ } as ParsedSyncConfigSet;
38
36
  },
39
- lock() {
40
- throw new Error('Not implemented in mock');
41
- },
42
- current_lock: null,
43
37
  logger: null as any,
44
38
  asUpdateOptions: null as any,
45
- getStorageConfig: null as any
46
- } as storage.PersistedSyncRulesContent;
39
+ getStorageConfig: null as any,
40
+ async getSyncConfigStatus() {
41
+ return (
42
+ overrides?.status ?? {
43
+ id: '1',
44
+ replicationStreamId: 1,
45
+ state: storage.SyncRuleState.ACTIVE,
46
+ last_checkpoint_lsn: 'some_lsn',
47
+ last_fatal_error: null,
48
+ last_fatal_error_ts: null,
49
+ last_keepalive_ts: new Date(),
50
+ last_checkpoint_ts: new Date()
51
+ }
52
+ );
53
+ }
54
+ } as storage.PersistedSyncConfigContent;
47
55
  }
48
56
 
49
- function makeBucketStorage() {
57
+ function makeSystemStorage() {
50
58
  return {
51
- getInstance() {
59
+ async getStatus() {
52
60
  return {
53
- async getStatus() {
54
- return {
55
- snapshot_done: true,
56
- checkpoint_lsn: 'some_lsn',
57
- active: true
58
- };
59
- }
61
+ snapshotDone: true,
62
+ resumeLsn: 'some_lsn'
60
63
  };
61
64
  }
62
- } as unknown as BucketStorageFactory;
65
+ } as storage.SyncRulesBucketStorage;
63
66
  }
64
67
 
65
68
  function makeRouteAPI(walBudget?: SlotWalBudgetInfo | undefined): RouteAPI {
@@ -92,7 +95,8 @@ function makeRouteAPI(walBudget?: SlotWalBudgetInfo | undefined): RouteAPI {
92
95
  const OPTIONS: DiagnosticsOptions = {
93
96
  live_status: true,
94
97
  check_connection: true,
95
- include_content: false
98
+ include_content: false,
99
+ active: true
96
100
  };
97
101
 
98
102
  describe('getSyncRulesStatus WAL budget warnings', () => {
@@ -102,7 +106,7 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
102
106
  safe_wal_size: 4 * GB,
103
107
  max_slot_wal_keep_size: 10 * GB
104
108
  });
105
- const result = await getSyncRulesStatus(makeBucketStorage(), api, makeSyncRulesContent(), OPTIONS);
109
+ const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
106
110
  const walWarnings = result!.errors.filter((e) => e.message.includes('WAL budget'));
107
111
  expect(walWarnings).toHaveLength(1);
108
112
  expect(walWarnings[0].level).toBe('warning');
@@ -115,7 +119,7 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
115
119
  safe_wal_size: 8 * GB,
116
120
  max_slot_wal_keep_size: 10 * GB
117
121
  });
118
- const result = await getSyncRulesStatus(makeBucketStorage(), api, makeSyncRulesContent(), OPTIONS);
122
+ const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
119
123
  const walWarnings = result!.errors.filter((e) => e.message.includes('WAL budget'));
120
124
  expect(walWarnings).toHaveLength(0);
121
125
  });
@@ -126,7 +130,7 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
126
130
  safe_wal_size: -2.4 * GB,
127
131
  max_slot_wal_keep_size: 1 * 1024 * 1024 // 1MB
128
132
  });
129
- const result = await getSyncRulesStatus(makeBucketStorage(), api, makeSyncRulesContent(), OPTIONS);
133
+ const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
130
134
  const walWarnings = result!.errors.filter((e) => e.message.includes('WAL budget'));
131
135
  expect(walWarnings).toHaveLength(1);
132
136
  expect(walWarnings[0].message).toContain('0%');
@@ -137,7 +141,7 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
137
141
  const api = makeRouteAPI({
138
142
  wal_status: 'lost'
139
143
  });
140
- const result = await getSyncRulesStatus(makeBucketStorage(), api, makeSyncRulesContent(), OPTIONS);
144
+ const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
141
145
  const walErrors = result!.errors.filter(
142
146
  (e) => e.message.includes('WAL budget') || e.message.includes('PSYNC_S1146')
143
147
  );
@@ -146,10 +150,33 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
146
150
 
147
151
  test('no WAL error when getSlotWalBudget is not defined', async () => {
148
152
  const api = makeRouteAPI();
149
- const result = await getSyncRulesStatus(makeBucketStorage(), api, makeSyncRulesContent(), OPTIONS);
153
+ const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
150
154
  const walErrors = result!.errors.filter(
151
155
  (e) => e.message.includes('WAL budget') || e.message.includes('PSYNC_S1146')
152
156
  );
153
157
  expect(walErrors).toHaveLength(0);
154
158
  });
159
+
160
+ test('uses sync config status for status-derived diagnostics fields', async () => {
161
+ const configStatus: storage.PersistedSyncConfigStatus = {
162
+ id: 'config-a',
163
+ replicationStreamId: 1,
164
+ state: storage.SyncRuleState.ACTIVE,
165
+ last_checkpoint_lsn: 'config_lsn',
166
+ last_fatal_error: 'config failed',
167
+ last_fatal_error_ts: new Date('2026-01-01T00:00:00.000Z'),
168
+ last_keepalive_ts: new Date('2026-01-01T00:01:00.000Z'),
169
+ last_checkpoint_ts: new Date('2026-01-01T00:02:00.000Z')
170
+ };
171
+ const result = await getSyncRulesStatus(
172
+ makeRouteAPI(),
173
+ makeSyncRulesContent({ status: configStatus }),
174
+ OPTIONS,
175
+ makeSystemStorage()
176
+ );
177
+
178
+ expect(result!.connections[0].last_checkpoint_ts).toBe('2026-01-01T00:02:00.000Z');
179
+ expect(result!.connections[0].last_keepalive_ts).toBe('2026-01-01T00:01:00.000Z');
180
+ expect(result!.errors.some((error) => error.message == 'config failed')).toBe(true);
181
+ });
155
182
  });
@@ -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,90 @@
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 content = {
45
+ replicationStreamId: id,
46
+ syncConfigId,
47
+ replicationStreamName: `slot_${id}`,
48
+ sync_rules_content:
49
+ options.content ??
50
+ `
51
+ bucket_definitions:
52
+ global:
53
+ data:
54
+ - SELECT id FROM test
55
+ `,
56
+ compiled_plan: null,
57
+ storageVersion: storage.LEGACY_STORAGE_VERSION,
58
+ parsed(options?: any) {
59
+ const syncRules = SqlSyncRules.fromYaml(content.sync_rules_content, {
60
+ ...options,
61
+ defaultSchema: 'public'
62
+ });
63
+ return {
64
+ syncConfigs: [syncRules]
65
+ } as ParsedSyncConfigSet;
66
+ },
67
+ asUpdateOptions: vi.fn(),
68
+ getStorageConfig: vi.fn(),
69
+ async getSyncConfigStatus() {
70
+ return {
71
+ id: syncConfigId,
72
+ replicationStreamId: id,
73
+ state,
74
+ last_checkpoint_lsn: null,
75
+ last_fatal_error: null,
76
+ last_fatal_error_ts: null,
77
+ last_keepalive_ts: lastKeepaliveTs,
78
+ last_checkpoint_ts: lastCheckpointTs
79
+ };
80
+ }
81
+ };
82
+ return content as unknown as storage.PersistedSyncConfigContent;
83
+ }
84
+
8
85
  describe('validate', () => {
9
86
  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
- };
87
+ const context = makeContext();
25
88
 
26
89
  const response = await validate.handler({
27
90
  context,
@@ -45,4 +108,103 @@ bucket_definitions:
45
108
  ]);
46
109
  });
47
110
  });
111
+
112
+ describe('diagnostics', () => {
113
+ it('returns deploying config status', async () => {
114
+ const active = makeSyncConfigContent({ id: 1, syncConfigId: 'active-config' });
115
+ const deploying = makeSyncConfigContent({ id: 2, syncConfigId: 'deploying-config', active: false });
116
+ const getInstance = vi.fn(() => ({
117
+ async getStatus() {
118
+ return {
119
+ snapshotDone: false,
120
+ resumeLsn: null
121
+ };
122
+ }
123
+ }));
124
+ const activeBucketStorage = {
125
+ getActiveSyncConfig: vi.fn(async () => ({
126
+ content: active,
127
+ replicationStream: {},
128
+ storage: getInstance()
129
+ })),
130
+ getDeployingSyncConfig: vi.fn(async () => ({
131
+ content: deploying,
132
+ replicationStream: {},
133
+ storage: getInstance()
134
+ }))
135
+ };
136
+
137
+ const response = await diagnostics.handler({
138
+ context: makeContext(activeBucketStorage),
139
+ params: {},
140
+ request
141
+ });
142
+
143
+ expect(response.deploying_sync_rules?.connections[0].slot_name).toBe('slot_2');
144
+ expect(response.active_sync_rules?.connections[0].slot_name).toBe('slot_1');
145
+ });
146
+ });
147
+
148
+ describe('reprocess', () => {
149
+ it('reprocesses the active sync config', async () => {
150
+ const active = makeSyncConfigContent({ id: 7, syncConfigId: 'active-config' });
151
+ const updateSyncRules = vi.fn(async () => ({
152
+ replicationStreamId: 8,
153
+ replicationStreamName: 'new_slot',
154
+ state: storage.SyncRuleState.PROCESSING,
155
+ storageVersion: storage.LEGACY_STORAGE_VERSION,
156
+ replicationJobId: '8',
157
+ current_lock: null
158
+ }));
159
+ const activeBucketStorage = {
160
+ getDeployingSyncConfig: vi.fn(async () => null),
161
+ getActiveSyncConfig: vi.fn(async () => ({
162
+ content: active,
163
+ replicationStream: {},
164
+ storage: {}
165
+ })),
166
+ getSyncConfigContent: vi.fn(),
167
+ updateSyncRules
168
+ };
169
+
170
+ const response = await reprocess.handler({
171
+ context: makeContext(activeBucketStorage),
172
+ params: {},
173
+ request
174
+ });
175
+
176
+ expect(activeBucketStorage.getActiveSyncConfig).toHaveBeenCalledTimes(1);
177
+ expect(activeBucketStorage.getSyncConfigContent).not.toHaveBeenCalled();
178
+ expect(updateSyncRules).toHaveBeenCalledTimes(1);
179
+ expect(response.connections[0].slot_name).toBe('new_slot');
180
+ });
181
+
182
+ it('rejects reprocess while a sync config is deploying', async () => {
183
+ const activeBucketStorage = {
184
+ getDeployingSyncConfig: vi.fn(async () => ({
185
+ content: makeSyncConfigContent({ id: 2, active: false }),
186
+ replicationStream: {},
187
+ storage: {}
188
+ })),
189
+ getActiveSyncConfig: vi.fn(),
190
+ updateSyncRules: vi.fn()
191
+ };
192
+
193
+ await expect(
194
+ reprocess.handler({
195
+ context: makeContext(activeBucketStorage),
196
+ params: {},
197
+ request
198
+ })
199
+ ).rejects.toMatchObject({
200
+ errorData: {
201
+ status: 409,
202
+ code: 'PSYNC_S4106',
203
+ description: 'Busy processing sync config - cannot reprocess'
204
+ }
205
+ });
206
+ expect(activeBucketStorage.getActiveSyncConfig).not.toHaveBeenCalled();
207
+ expect(activeBucketStorage.updateSyncRules).not.toHaveBeenCalled();
208
+ });
209
+ });
48
210
  });
@@ -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": [