@powersync/service-module-mssql 0.0.0-dev-20251128080741

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 (92) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +67 -0
  3. package/README.md +3 -0
  4. package/ci/init-mssql.sql +50 -0
  5. package/dist/api/MSSQLRouteAPIAdapter.d.ts +21 -0
  6. package/dist/api/MSSQLRouteAPIAdapter.js +248 -0
  7. package/dist/api/MSSQLRouteAPIAdapter.js.map +1 -0
  8. package/dist/common/LSN.d.ts +37 -0
  9. package/dist/common/LSN.js +64 -0
  10. package/dist/common/LSN.js.map +1 -0
  11. package/dist/common/MSSQLSourceTable.d.ts +27 -0
  12. package/dist/common/MSSQLSourceTable.js +35 -0
  13. package/dist/common/MSSQLSourceTable.js.map +1 -0
  14. package/dist/common/MSSQLSourceTableCache.d.ts +14 -0
  15. package/dist/common/MSSQLSourceTableCache.js +28 -0
  16. package/dist/common/MSSQLSourceTableCache.js.map +1 -0
  17. package/dist/common/mssqls-to-sqlite.d.ts +18 -0
  18. package/dist/common/mssqls-to-sqlite.js +143 -0
  19. package/dist/common/mssqls-to-sqlite.js.map +1 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +2 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/module/MSSQLModule.d.ts +15 -0
  24. package/dist/module/MSSQLModule.js +68 -0
  25. package/dist/module/MSSQLModule.js.map +1 -0
  26. package/dist/replication/CDCPoller.d.ts +67 -0
  27. package/dist/replication/CDCPoller.js +188 -0
  28. package/dist/replication/CDCPoller.js.map +1 -0
  29. package/dist/replication/CDCReplicationJob.d.ts +17 -0
  30. package/dist/replication/CDCReplicationJob.js +76 -0
  31. package/dist/replication/CDCReplicationJob.js.map +1 -0
  32. package/dist/replication/CDCReplicator.d.ts +18 -0
  33. package/dist/replication/CDCReplicator.js +55 -0
  34. package/dist/replication/CDCReplicator.js.map +1 -0
  35. package/dist/replication/CDCStream.d.ts +106 -0
  36. package/dist/replication/CDCStream.js +539 -0
  37. package/dist/replication/CDCStream.js.map +1 -0
  38. package/dist/replication/MSSQLConnectionManager.d.ts +23 -0
  39. package/dist/replication/MSSQLConnectionManager.js +97 -0
  40. package/dist/replication/MSSQLConnectionManager.js.map +1 -0
  41. package/dist/replication/MSSQLConnectionManagerFactory.d.ts +10 -0
  42. package/dist/replication/MSSQLConnectionManagerFactory.js +28 -0
  43. package/dist/replication/MSSQLConnectionManagerFactory.js.map +1 -0
  44. package/dist/replication/MSSQLErrorRateLimiter.d.ts +10 -0
  45. package/dist/replication/MSSQLErrorRateLimiter.js +34 -0
  46. package/dist/replication/MSSQLErrorRateLimiter.js.map +1 -0
  47. package/dist/replication/MSSQLSnapshotQuery.d.ts +71 -0
  48. package/dist/replication/MSSQLSnapshotQuery.js +190 -0
  49. package/dist/replication/MSSQLSnapshotQuery.js.map +1 -0
  50. package/dist/types/mssql-data-types.d.ts +66 -0
  51. package/dist/types/mssql-data-types.js +62 -0
  52. package/dist/types/mssql-data-types.js.map +1 -0
  53. package/dist/types/types.d.ts +177 -0
  54. package/dist/types/types.js +141 -0
  55. package/dist/types/types.js.map +1 -0
  56. package/dist/utils/mssql.d.ts +80 -0
  57. package/dist/utils/mssql.js +329 -0
  58. package/dist/utils/mssql.js.map +1 -0
  59. package/dist/utils/schema.d.ts +21 -0
  60. package/dist/utils/schema.js +131 -0
  61. package/dist/utils/schema.js.map +1 -0
  62. package/package.json +51 -0
  63. package/src/api/MSSQLRouteAPIAdapter.ts +283 -0
  64. package/src/common/LSN.ts +77 -0
  65. package/src/common/MSSQLSourceTable.ts +54 -0
  66. package/src/common/MSSQLSourceTableCache.ts +36 -0
  67. package/src/common/mssqls-to-sqlite.ts +151 -0
  68. package/src/index.ts +1 -0
  69. package/src/module/MSSQLModule.ts +82 -0
  70. package/src/replication/CDCPoller.ts +247 -0
  71. package/src/replication/CDCReplicationJob.ts +87 -0
  72. package/src/replication/CDCReplicator.ts +70 -0
  73. package/src/replication/CDCStream.ts +691 -0
  74. package/src/replication/MSSQLConnectionManager.ts +113 -0
  75. package/src/replication/MSSQLConnectionManagerFactory.ts +33 -0
  76. package/src/replication/MSSQLErrorRateLimiter.ts +36 -0
  77. package/src/replication/MSSQLSnapshotQuery.ts +230 -0
  78. package/src/types/mssql-data-types.ts +79 -0
  79. package/src/types/types.ts +224 -0
  80. package/src/utils/mssql.ts +420 -0
  81. package/src/utils/schema.ts +172 -0
  82. package/test/src/CDCStream.test.ts +204 -0
  83. package/test/src/CDCStreamTestContext.ts +212 -0
  84. package/test/src/CDCStream_resumable_snapshot.test.ts +159 -0
  85. package/test/src/env.ts +11 -0
  86. package/test/src/mssql-to-sqlite.test.ts +474 -0
  87. package/test/src/setup.ts +12 -0
  88. package/test/src/util.ts +188 -0
  89. package/test/tsconfig.json +28 -0
  90. package/tsconfig.json +26 -0
  91. package/tsconfig.tsbuildinfo +1 -0
  92. package/vitest.config.ts +15 -0
@@ -0,0 +1,188 @@
1
+ import * as types from '@module/types/types.js';
2
+ import { logger } from '@powersync/lib-services-framework';
3
+ import { BucketStorageFactory, InternalOpId, ReplicationCheckpoint, TestStorageFactory } from '@powersync/service-core';
4
+
5
+ import * as mongo_storage from '@powersync/service-module-mongodb-storage';
6
+ import * as postgres_storage from '@powersync/service-module-postgres-storage';
7
+
8
+ import { describe, TestOptions } from 'vitest';
9
+ import { env } from './env.js';
10
+ import { MSSQLConnectionManager } from '@module/replication/MSSQLConnectionManager.js';
11
+ import { createCheckpoint, enableCDCForTable, getLatestLSN } from '@module/utils/mssql.js';
12
+ import sql from 'mssql';
13
+ import { v4 as uuid } from 'uuid';
14
+ import { LSN } from '@module/common/LSN.js';
15
+
16
+ export const TEST_URI = env.MSSQL_TEST_URI;
17
+
18
+ export const INITIALIZED_MONGO_STORAGE_FACTORY = mongo_storage.test_utils.mongoTestStorageFactoryGenerator({
19
+ url: env.MONGO_TEST_URL,
20
+ isCI: env.CI
21
+ });
22
+
23
+ export const INITIALIZED_POSTGRES_STORAGE_FACTORY = postgres_storage.test_utils.postgresTestStorageFactoryGenerator({
24
+ url: env.PG_STORAGE_TEST_URL
25
+ });
26
+
27
+ export function describeWithStorage(options: TestOptions, fn: (factory: TestStorageFactory) => void) {
28
+ describe.skipIf(!env.TEST_MONGO_STORAGE)(`mongodb storage`, options, function () {
29
+ fn(INITIALIZED_MONGO_STORAGE_FACTORY);
30
+ });
31
+
32
+ describe.skipIf(!env.TEST_POSTGRES_STORAGE)(`postgres storage`, options, function () {
33
+ fn(INITIALIZED_POSTGRES_STORAGE_FACTORY);
34
+ });
35
+ }
36
+
37
+ export const TEST_CONNECTION_OPTIONS = types.normalizeConnectionConfig({
38
+ type: 'mssql',
39
+ uri: TEST_URI,
40
+ trustServerCertificate: true
41
+ });
42
+
43
+ /**
44
+ * Clears all test tables (those prefixed with 'test_') from the database. Also removes CDC instances for those tables.
45
+ * @param connectionManager
46
+ */
47
+ export async function clearTestDb(connectionManager: MSSQLConnectionManager) {
48
+ const { recordset: tables } = await connectionManager.query(`
49
+ SELECT TABLE_SCHEMA, TABLE_NAME
50
+ FROM INFORMATION_SCHEMA.TABLES
51
+ WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME LIKE 'test_%'
52
+ `);
53
+ for (const row of tables) {
54
+ // Disable CDC for the table if enabled
55
+ await connectionManager.execute('sys.sp_cdc_disable_table', [
56
+ { name: 'source_schema', value: row.TABLE_SCHEMA },
57
+ { name: 'source_name', value: row.TABLE_NAME },
58
+ { name: 'capture_instance', value: 'all' }
59
+ ]);
60
+ // Drop Tables
61
+ await connectionManager.query(`DROP TABLE [${row.TABLE_NAME}]`);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Create a new database for testing and enables CDC on it.
67
+ * @param connectionManager
68
+ * @param dbName
69
+ */
70
+ export async function createTestDb(connectionManager: MSSQLConnectionManager, dbName: string) {
71
+ await connectionManager.query(`DROP DATABASE IF EXISTS ${dbName}`);
72
+ await connectionManager.query(`CREATE DATABASE ${dbName}`);
73
+ await connectionManager.execute(`
74
+ USE ${dbName};
75
+ GO
76
+
77
+ EXEC sys.sp_cdc_enable_db;
78
+ GO`);
79
+ }
80
+
81
+ export async function createTestTable(connectionManager: MSSQLConnectionManager, tableName: string): Promise<void> {
82
+ await connectionManager.query(`
83
+ CREATE TABLE ${connectionManager.schema}.${tableName} (
84
+ id UNIQUEIDENTIFIER PRIMARY KEY,
85
+ description VARCHAR(MAX)
86
+ )
87
+ `);
88
+ await enableCDCForTable({ connectionManager, table: tableName });
89
+ }
90
+
91
+ export async function createTestTableWithBasicId(
92
+ connectionManager: MSSQLConnectionManager,
93
+ tableName: string
94
+ ): Promise<void> {
95
+ await connectionManager.query(`
96
+ CREATE TABLE ${connectionManager.schema}.${tableName} (
97
+ id INT IDENTITY(1,1) PRIMARY KEY,
98
+ description VARCHAR(MAX)
99
+ )
100
+ `);
101
+ await enableCDCForTable({ connectionManager, table: tableName });
102
+ }
103
+
104
+ export interface TestData {
105
+ id: string;
106
+ description: string;
107
+ }
108
+ export async function insertTestData(connectionManager: MSSQLConnectionManager, tableName: string): Promise<TestData> {
109
+ const id = createUpperCaseUUID();
110
+ const description = `description_${id}`;
111
+ await connectionManager.query(
112
+ `
113
+ INSERT INTO ${connectionManager.schema}.${tableName} (id, description) VALUES (@id, @description)
114
+ `,
115
+ [
116
+ { name: 'id', type: sql.UniqueIdentifier, value: id },
117
+ { name: 'description', type: sql.NVarChar(sql.MAX), value: description }
118
+ ]
119
+ );
120
+
121
+ return { id, description };
122
+ }
123
+
124
+ export async function waitForPendingCDCChanges(
125
+ beforeLSN: LSN,
126
+ connectionManager: MSSQLConnectionManager
127
+ ): Promise<void> {
128
+ while (true) {
129
+ const { recordset: result } = await connectionManager.query(
130
+ `
131
+ SELECT TOP 1 start_lsn
132
+ FROM cdc.lsn_time_mapping
133
+ WHERE start_lsn > @before_lsn
134
+ ORDER BY start_lsn DESC
135
+ `,
136
+ [{ name: 'before_lsn', type: sql.VarBinary, value: beforeLSN.toBinary() }]
137
+ );
138
+
139
+ if (result.length === 0) {
140
+ logger.info(`CDC changes pending. Waiting for 500ms...`);
141
+ await new Promise((resolve) => setTimeout(resolve, 500));
142
+ } else {
143
+ logger.info(`Found LSN: ${LSN.fromBinary(result[0].start_lsn).toString()}`);
144
+ return;
145
+ }
146
+ }
147
+ }
148
+
149
+ export async function getClientCheckpoint(
150
+ connectionManager: MSSQLConnectionManager,
151
+ storageFactory: BucketStorageFactory,
152
+ options?: { timeout?: number }
153
+ ): Promise<InternalOpId> {
154
+ const start = Date.now();
155
+
156
+ const lsn = await getLatestLSN(connectionManager);
157
+ await createCheckpoint(connectionManager);
158
+
159
+ // This old API needs a persisted checkpoint id.
160
+ // Since we don't use LSNs anymore, the only way to get that is to wait.
161
+
162
+ const timeout = options?.timeout ?? 50_000;
163
+ let lastCp: ReplicationCheckpoint | null = null;
164
+
165
+ logger.info(`Waiting for LSN checkpoint: ${lsn}`);
166
+ while (Date.now() - start < timeout) {
167
+ const storage = await storageFactory.getActiveStorage();
168
+ const cp = await storage?.getCheckpoint();
169
+ if (cp != null) {
170
+ lastCp = cp;
171
+ if (cp.lsn != null && cp.lsn >= lsn.toString()) {
172
+ logger.info(`Got write checkpoint: ${lsn} : ${cp.checkpoint}`);
173
+ return cp.checkpoint;
174
+ }
175
+ }
176
+
177
+ await new Promise((resolve) => setTimeout(resolve, 30));
178
+ }
179
+
180
+ throw new Error(`Timeout while waiting for checkpoint ${lsn}. Last checkpoint: ${lastCp?.lsn}`);
181
+ }
182
+
183
+ /**
184
+ * Generates a new UUID string in uppercase for testing purposes to match the SQL Server UNIQUEIDENTIFIER format.
185
+ */
186
+ export function createUpperCaseUUID(): string {
187
+ return uuid().toUpperCase();
188
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "baseUrl": "./",
6
+ "noEmit": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "sourceMap": true,
10
+ "paths": {
11
+ "@/*": ["../../../packages/service-core/src/*"],
12
+ "@module/*": ["../src/*"],
13
+ "@core-tests/*": ["../../../packages/service-core/test/src/*"]
14
+ }
15
+ },
16
+ "include": ["src"],
17
+ "references": [
18
+ {
19
+ "path": "../"
20
+ },
21
+ {
22
+ "path": "../../../packages/service-core/test"
23
+ },
24
+ {
25
+ "path": "../../../packages/service-core/"
26
+ }
27
+ ]
28
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist",
6
+ "esModuleInterop": true,
7
+ "skipLibCheck": true,
8
+ "sourceMap": true,
9
+ "typeRoots": ["./node_modules/@types"]
10
+ },
11
+ "include": ["src"],
12
+ "references": [
13
+ {
14
+ "path": "../../packages/types"
15
+ },
16
+ {
17
+ "path": "../../packages/sync-rules"
18
+ },
19
+ {
20
+ "path": "../../packages/service-core"
21
+ },
22
+ {
23
+ "path": "../../libs/lib-services"
24
+ }
25
+ ]
26
+ }