@powersync/service-module-mysql 0.0.0-dev-20241015210820

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 (96) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +67 -0
  3. package/README.md +3 -0
  4. package/dev/.env.template +2 -0
  5. package/dev/README.md +9 -0
  6. package/dev/config/sync_rules.yaml +12 -0
  7. package/dev/docker/mysql/docker-compose.yaml +17 -0
  8. package/dev/docker/mysql/init-scripts/my.cnf +9 -0
  9. package/dev/docker/mysql/init-scripts/mysql.sql +38 -0
  10. package/dist/api/MySQLRouteAPIAdapter.d.ts +24 -0
  11. package/dist/api/MySQLRouteAPIAdapter.js +311 -0
  12. package/dist/api/MySQLRouteAPIAdapter.js.map +1 -0
  13. package/dist/common/ReplicatedGTID.d.ts +59 -0
  14. package/dist/common/ReplicatedGTID.js +110 -0
  15. package/dist/common/ReplicatedGTID.js.map +1 -0
  16. package/dist/common/check-source-configuration.d.ts +3 -0
  17. package/dist/common/check-source-configuration.js +46 -0
  18. package/dist/common/check-source-configuration.js.map +1 -0
  19. package/dist/common/common-index.d.ts +6 -0
  20. package/dist/common/common-index.js +7 -0
  21. package/dist/common/common-index.js.map +1 -0
  22. package/dist/common/get-replication-columns.d.ts +12 -0
  23. package/dist/common/get-replication-columns.js +103 -0
  24. package/dist/common/get-replication-columns.js.map +1 -0
  25. package/dist/common/get-tables-from-pattern.d.ts +7 -0
  26. package/dist/common/get-tables-from-pattern.js +28 -0
  27. package/dist/common/get-tables-from-pattern.js.map +1 -0
  28. package/dist/common/mysql-to-sqlite.d.ts +4 -0
  29. package/dist/common/mysql-to-sqlite.js +56 -0
  30. package/dist/common/mysql-to-sqlite.js.map +1 -0
  31. package/dist/common/read-executed-gtid.d.ts +6 -0
  32. package/dist/common/read-executed-gtid.js +40 -0
  33. package/dist/common/read-executed-gtid.js.map +1 -0
  34. package/dist/index.d.ts +3 -0
  35. package/dist/index.js +4 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/module/MySQLModule.d.ts +13 -0
  38. package/dist/module/MySQLModule.js +46 -0
  39. package/dist/module/MySQLModule.js.map +1 -0
  40. package/dist/replication/BinLogReplicationJob.d.ts +14 -0
  41. package/dist/replication/BinLogReplicationJob.js +88 -0
  42. package/dist/replication/BinLogReplicationJob.js.map +1 -0
  43. package/dist/replication/BinLogReplicator.d.ts +13 -0
  44. package/dist/replication/BinLogReplicator.js +25 -0
  45. package/dist/replication/BinLogReplicator.js.map +1 -0
  46. package/dist/replication/BinLogStream.d.ts +43 -0
  47. package/dist/replication/BinLogStream.js +421 -0
  48. package/dist/replication/BinLogStream.js.map +1 -0
  49. package/dist/replication/MySQLConnectionManager.d.ts +43 -0
  50. package/dist/replication/MySQLConnectionManager.js +81 -0
  51. package/dist/replication/MySQLConnectionManager.js.map +1 -0
  52. package/dist/replication/MySQLConnectionManagerFactory.d.ts +10 -0
  53. package/dist/replication/MySQLConnectionManagerFactory.js +21 -0
  54. package/dist/replication/MySQLConnectionManagerFactory.js.map +1 -0
  55. package/dist/replication/MySQLErrorRateLimiter.d.ts +10 -0
  56. package/dist/replication/MySQLErrorRateLimiter.js +43 -0
  57. package/dist/replication/MySQLErrorRateLimiter.js.map +1 -0
  58. package/dist/replication/zongji/zongji-utils.d.ts +7 -0
  59. package/dist/replication/zongji/zongji-utils.js +19 -0
  60. package/dist/replication/zongji/zongji-utils.js.map +1 -0
  61. package/dist/types/types.d.ts +50 -0
  62. package/dist/types/types.js +61 -0
  63. package/dist/types/types.js.map +1 -0
  64. package/dist/utils/mysql_utils.d.ts +14 -0
  65. package/dist/utils/mysql_utils.js +38 -0
  66. package/dist/utils/mysql_utils.js.map +1 -0
  67. package/package.json +51 -0
  68. package/src/api/MySQLRouteAPIAdapter.ts +357 -0
  69. package/src/common/ReplicatedGTID.ts +158 -0
  70. package/src/common/check-source-configuration.ts +59 -0
  71. package/src/common/common-index.ts +6 -0
  72. package/src/common/get-replication-columns.ts +124 -0
  73. package/src/common/get-tables-from-pattern.ts +44 -0
  74. package/src/common/mysql-to-sqlite.ts +59 -0
  75. package/src/common/read-executed-gtid.ts +43 -0
  76. package/src/index.ts +5 -0
  77. package/src/module/MySQLModule.ts +53 -0
  78. package/src/replication/BinLogReplicationJob.ts +97 -0
  79. package/src/replication/BinLogReplicator.ts +35 -0
  80. package/src/replication/BinLogStream.ts +547 -0
  81. package/src/replication/MySQLConnectionManager.ts +104 -0
  82. package/src/replication/MySQLConnectionManagerFactory.ts +28 -0
  83. package/src/replication/MySQLErrorRateLimiter.ts +44 -0
  84. package/src/replication/zongji/zongji-utils.ts +32 -0
  85. package/src/replication/zongji/zongji.d.ts +98 -0
  86. package/src/types/types.ts +102 -0
  87. package/src/utils/mysql_utils.ts +47 -0
  88. package/test/src/binlog_stream.test.ts +288 -0
  89. package/test/src/binlog_stream_utils.ts +152 -0
  90. package/test/src/env.ts +7 -0
  91. package/test/src/setup.ts +7 -0
  92. package/test/src/util.ts +62 -0
  93. package/test/tsconfig.json +28 -0
  94. package/tsconfig.json +26 -0
  95. package/tsconfig.tsbuildinfo +1 -0
  96. package/vitest.config.ts +15 -0
@@ -0,0 +1,43 @@
1
+ import { NormalizedMySQLConnectionConfig } from '../types/types.js';
2
+ import mysqlPromise from 'mysql2/promise';
3
+ import mysql from 'mysql2';
4
+ import ZongJi from '@powersync/mysql-zongji';
5
+ export declare class MySQLConnectionManager {
6
+ options: NormalizedMySQLConnectionConfig;
7
+ poolOptions: mysqlPromise.PoolOptions;
8
+ /**
9
+ * Pool that can create streamable connections
10
+ */
11
+ private readonly pool;
12
+ /**
13
+ * Pool that can create promise-based connections
14
+ */
15
+ private readonly promisePool;
16
+ private binlogListeners;
17
+ constructor(options: NormalizedMySQLConnectionConfig, poolOptions: mysqlPromise.PoolOptions);
18
+ get connectionTag(): string;
19
+ get connectionId(): string;
20
+ get databaseName(): string;
21
+ /**
22
+ * Create a new replication listener
23
+ */
24
+ createBinlogListener(): ZongJi;
25
+ /**
26
+ * Run a query using a connection from the pool
27
+ * A promise with the result is returned
28
+ * @param query
29
+ * @param params
30
+ */
31
+ query(query: string, params?: any[]): Promise<[mysqlPromise.RowDataPacket[], mysqlPromise.FieldPacket[]]>;
32
+ /**
33
+ * Get a streamable connection from this manager's pool
34
+ * The connection should be released when it is no longer needed
35
+ */
36
+ getStreamingConnection(): Promise<mysql.PoolConnection>;
37
+ /**
38
+ * Get a promise connection from this manager's pool
39
+ * The connection should be released when it is no longer needed
40
+ */
41
+ getConnection(): Promise<mysqlPromise.PoolConnection>;
42
+ end(): Promise<void>;
43
+ }
@@ -0,0 +1,81 @@
1
+ import * as mysql_utils from '../utils/mysql_utils.js';
2
+ import ZongJi from '@powersync/mysql-zongji';
3
+ export class MySQLConnectionManager {
4
+ constructor(options, poolOptions) {
5
+ this.options = options;
6
+ this.poolOptions = poolOptions;
7
+ this.binlogListeners = [];
8
+ // The pool is lazy - no connections are opened until a query is performed.
9
+ this.pool = mysql_utils.createPool(options, poolOptions);
10
+ this.promisePool = this.pool.promise();
11
+ }
12
+ get connectionTag() {
13
+ return this.options.tag;
14
+ }
15
+ get connectionId() {
16
+ return this.options.id;
17
+ }
18
+ get databaseName() {
19
+ return this.options.database;
20
+ }
21
+ /**
22
+ * Create a new replication listener
23
+ */
24
+ createBinlogListener() {
25
+ const listener = new ZongJi({
26
+ host: this.options.hostname,
27
+ user: this.options.username,
28
+ password: this.options.password
29
+ });
30
+ this.binlogListeners.push(listener);
31
+ return listener;
32
+ }
33
+ /**
34
+ * Run a query using a connection from the pool
35
+ * A promise with the result is returned
36
+ * @param query
37
+ * @param params
38
+ */
39
+ async query(query, params) {
40
+ return this.promisePool.query(query, params);
41
+ }
42
+ /**
43
+ * Get a streamable connection from this manager's pool
44
+ * The connection should be released when it is no longer needed
45
+ */
46
+ async getStreamingConnection() {
47
+ return new Promise((resolve, reject) => {
48
+ this.pool.getConnection((err, connection) => {
49
+ if (err) {
50
+ reject(err);
51
+ }
52
+ else {
53
+ resolve(connection);
54
+ }
55
+ });
56
+ });
57
+ }
58
+ /**
59
+ * Get a promise connection from this manager's pool
60
+ * The connection should be released when it is no longer needed
61
+ */
62
+ async getConnection() {
63
+ return this.promisePool.getConnection();
64
+ }
65
+ async end() {
66
+ for (const listener of this.binlogListeners) {
67
+ listener.stop();
68
+ }
69
+ await new Promise((resolve, reject) => {
70
+ this.pool.end((err) => {
71
+ if (err) {
72
+ reject(err);
73
+ }
74
+ else {
75
+ resolve();
76
+ }
77
+ });
78
+ });
79
+ }
80
+ }
81
+ //# sourceMappingURL=MySQLConnectionManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MySQLConnectionManager.js","sourceRoot":"","sources":["../../src/replication/MySQLConnectionManager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AACvD,OAAO,MAAM,MAAM,yBAAyB,CAAC;AAE7C,MAAM,OAAO,sBAAsB;IAYjC,YACS,OAAwC,EACxC,WAAqC;QADrC,YAAO,GAAP,OAAO,CAAiC;QACxC,gBAAW,GAAX,WAAW,CAA0B;QAJtC,oBAAe,GAAa,EAAE,CAAC;QAMrC,2EAA2E;QAC3E,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QACzD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;IACzC,CAAC;IAED,IAAW,aAAa;QACtB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;IAC1B,CAAC;IAED,IAAW,YAAY;QACrB,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;IACzB,CAAC;IAED,IAAW,YAAY;QACrB,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,oBAAoB;QAClB,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC;YAC1B,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;YAC3B,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;YAC3B,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;SAChC,CAAC,CAAC;QAEH,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEpC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,KAAK,CAAC,KAAa,EAAE,MAAc;QACvC,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,CAAkB,KAAK,EAAE,MAAM,CAAC,CAAC;IAChE,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,sBAAsB;QAC1B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE;gBAC1C,IAAI,GAAG,EAAE,CAAC;oBACR,MAAM,CAAC,GAAG,CAAC,CAAC;gBACd,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,UAAU,CAAC,CAAC;gBACtB,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,aAAa;QACjB,OAAO,IAAI,CAAC,WAAW,CAAC,aAAa,EAAE,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,GAAG;QACP,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YAC5C,QAAQ,CAAC,IAAI,EAAE,CAAC;QAClB,CAAC;QAED,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;gBACpB,IAAI,GAAG,EAAE,CAAC;oBACR,MAAM,CAAC,GAAG,CAAC,CAAC;gBACd,CAAC;qBAAM,CAAC;oBACN,OAAO,EAAE,CAAC;gBACZ,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,10 @@
1
+ import mysql from 'mysql2/promise';
2
+ import { MySQLConnectionManager } from './MySQLConnectionManager.js';
3
+ import { ResolvedConnectionConfig } from '../types/types.js';
4
+ export declare class MySQLConnectionManagerFactory {
5
+ private readonly connectionManagers;
6
+ private readonly connectionConfig;
7
+ constructor(connectionConfig: ResolvedConnectionConfig);
8
+ create(poolOptions: mysql.PoolOptions): MySQLConnectionManager;
9
+ shutdown(): Promise<void>;
10
+ }
@@ -0,0 +1,21 @@
1
+ import { logger } from '@powersync/lib-services-framework';
2
+ import { MySQLConnectionManager } from './MySQLConnectionManager.js';
3
+ export class MySQLConnectionManagerFactory {
4
+ constructor(connectionConfig) {
5
+ this.connectionConfig = connectionConfig;
6
+ this.connectionManagers = [];
7
+ }
8
+ create(poolOptions) {
9
+ const manager = new MySQLConnectionManager(this.connectionConfig, poolOptions);
10
+ this.connectionManagers.push(manager);
11
+ return manager;
12
+ }
13
+ async shutdown() {
14
+ logger.info('Shutting down MySQL connection Managers...');
15
+ for (const manager of this.connectionManagers) {
16
+ await manager.end();
17
+ }
18
+ logger.info('MySQL connection Managers shutdown completed.');
19
+ }
20
+ }
21
+ //# sourceMappingURL=MySQLConnectionManagerFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MySQLConnectionManagerFactory.js","sourceRoot":"","sources":["../../src/replication/MySQLConnectionManagerFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAE3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAGrE,MAAM,OAAO,6BAA6B;IAIxC,YAAY,gBAA0C;QACpD,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;QACzC,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC;IAC/B,CAAC;IAED,MAAM,CAAC,WAA8B;QACnC,MAAM,OAAO,GAAG,IAAI,sBAAsB,CAAC,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAC;QAC/E,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;QAC1D,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC9C,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC;QACtB,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;IAC/D,CAAC;CACF"}
@@ -0,0 +1,10 @@
1
+ import { ErrorRateLimiter } from '@powersync/service-core';
2
+ export declare class MySQLErrorRateLimiter implements ErrorRateLimiter {
3
+ nextAllowed: number;
4
+ waitUntilAllowed(options?: {
5
+ signal?: AbortSignal | undefined;
6
+ } | undefined): Promise<void>;
7
+ mayPing(): boolean;
8
+ reportError(e: any): void;
9
+ private setDelay;
10
+ }
@@ -0,0 +1,43 @@
1
+ import { setTimeout } from 'timers/promises';
2
+ export class MySQLErrorRateLimiter {
3
+ constructor() {
4
+ this.nextAllowed = Date.now();
5
+ }
6
+ async waitUntilAllowed(options) {
7
+ const delay = Math.max(0, this.nextAllowed - Date.now());
8
+ // Minimum delay between connections, even without errors
9
+ this.setDelay(500);
10
+ await setTimeout(delay, undefined, { signal: options?.signal });
11
+ }
12
+ mayPing() {
13
+ return Date.now() >= this.nextAllowed;
14
+ }
15
+ reportError(e) {
16
+ const message = e.message ?? '';
17
+ if (message.includes('password authentication failed')) {
18
+ // Wait 15 minutes, to avoid triggering Supabase's fail2ban
19
+ this.setDelay(900000);
20
+ }
21
+ else if (message.includes('ENOTFOUND')) {
22
+ // DNS lookup issue - incorrect URI or deleted instance
23
+ this.setDelay(120000);
24
+ }
25
+ else if (message.includes('ECONNREFUSED')) {
26
+ // Could be fail2ban or similar
27
+ this.setDelay(120000);
28
+ }
29
+ else if (message.includes('Unable to do postgres query on ended pool') ||
30
+ message.includes('Postgres unexpectedly closed connection')) {
31
+ // Connection timed out - ignore / immediately retry
32
+ // We don't explicitly set the delay to 0, since there could have been another error that
33
+ // we need to respect.
34
+ }
35
+ else {
36
+ this.setDelay(30000);
37
+ }
38
+ }
39
+ setDelay(delay) {
40
+ this.nextAllowed = Math.max(this.nextAllowed, Date.now() + delay);
41
+ }
42
+ }
43
+ //# sourceMappingURL=MySQLErrorRateLimiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MySQLErrorRateLimiter.js","sourceRoot":"","sources":["../../src/replication/MySQLErrorRateLimiter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,OAAO,qBAAqB;IAAlC;QACE,gBAAW,GAAW,IAAI,CAAC,GAAG,EAAE,CAAC;IAuCnC,CAAC;IArCC,KAAK,CAAC,gBAAgB,CAAC,OAA0D;QAC/E,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACzD,yDAAyD;QACzD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACnB,MAAM,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,WAAW,CAAC;IACxC,CAAC;IAED,WAAW,CAAC,CAAM;QAChB,MAAM,OAAO,GAAI,CAAC,CAAC,OAAkB,IAAI,EAAE,CAAC;QAC5C,IAAI,OAAO,CAAC,QAAQ,CAAC,gCAAgC,CAAC,EAAE,CAAC;YACvD,2DAA2D;YAC3D,IAAI,CAAC,QAAQ,CAAC,MAAO,CAAC,CAAC;QACzB,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YACzC,uDAAuD;YACvD,IAAI,CAAC,QAAQ,CAAC,MAAO,CAAC,CAAC;QACzB,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YAC5C,+BAA+B;YAC/B,IAAI,CAAC,QAAQ,CAAC,MAAO,CAAC,CAAC;QACzB,CAAC;aAAM,IACL,OAAO,CAAC,QAAQ,CAAC,2CAA2C,CAAC;YAC7D,OAAO,CAAC,QAAQ,CAAC,yCAAyC,CAAC,EAC3D,CAAC;YACD,oDAAoD;YACpD,yFAAyF;YACzF,sBAAsB;QACxB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,QAAQ,CAAC,KAAM,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAEO,QAAQ,CAAC,KAAa;QAC5B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;IACpE,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ import { BinLogEvent, BinLogGTIDLogEvent, BinLogMutationEvent, BinLogRotationEvent, BinLogUpdateEvent, BinLogXidEvent } from '@powersync/mysql-zongji';
2
+ export declare function eventIsGTIDLog(event: BinLogEvent): event is BinLogGTIDLogEvent;
3
+ export declare function eventIsXid(event: BinLogEvent): event is BinLogXidEvent;
4
+ export declare function eventIsRotation(event: BinLogEvent): event is BinLogRotationEvent;
5
+ export declare function eventIsWriteMutation(event: BinLogEvent): event is BinLogMutationEvent;
6
+ export declare function eventIsDeleteMutation(event: BinLogEvent): event is BinLogMutationEvent;
7
+ export declare function eventIsUpdateMutation(event: BinLogEvent): event is BinLogUpdateEvent;
@@ -0,0 +1,19 @@
1
+ export function eventIsGTIDLog(event) {
2
+ return event.getEventName() == 'gtidlog';
3
+ }
4
+ export function eventIsXid(event) {
5
+ return event.getEventName() == 'xid';
6
+ }
7
+ export function eventIsRotation(event) {
8
+ return event.getEventName() == 'rotate';
9
+ }
10
+ export function eventIsWriteMutation(event) {
11
+ return event.getEventName() == 'writerows';
12
+ }
13
+ export function eventIsDeleteMutation(event) {
14
+ return event.getEventName() == 'deleterows';
15
+ }
16
+ export function eventIsUpdateMutation(event) {
17
+ return event.getEventName() == 'updaterows';
18
+ }
19
+ //# sourceMappingURL=zongji-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zongji-utils.js","sourceRoot":"","sources":["../../../src/replication/zongji/zongji-utils.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,cAAc,CAAC,KAAkB;IAC/C,OAAO,KAAK,CAAC,YAAY,EAAE,IAAI,SAAS,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAkB;IAC3C,OAAO,KAAK,CAAC,YAAY,EAAE,IAAI,KAAK,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAkB;IAChD,OAAO,KAAK,CAAC,YAAY,EAAE,IAAI,QAAQ,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAkB;IACrD,OAAO,KAAK,CAAC,YAAY,EAAE,IAAI,WAAW,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAkB;IACtD,OAAO,KAAK,CAAC,YAAY,EAAE,IAAI,YAAY,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAkB;IACtD,OAAO,KAAK,CAAC,YAAY,EAAE,IAAI,YAAY,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,50 @@
1
+ import * as t from 'ts-codec';
2
+ export declare const MYSQL_CONNECTION_TYPE: "mysql";
3
+ export interface NormalizedMySQLConnectionConfig {
4
+ id: string;
5
+ tag: string;
6
+ hostname: string;
7
+ port: number;
8
+ database: string;
9
+ username: string;
10
+ password: string;
11
+ cacert?: string;
12
+ client_certificate?: string;
13
+ client_private_key?: string;
14
+ }
15
+ export declare const MySQLConnectionConfig: t.Intersection<t.Codec<{
16
+ type: string;
17
+ id?: string | undefined;
18
+ tag?: string | undefined;
19
+ debug_api?: boolean | undefined;
20
+ }, {
21
+ type: string;
22
+ id?: string | undefined;
23
+ tag?: string | undefined;
24
+ debug_api?: boolean | undefined;
25
+ }, string, t.CodecProps>, t.ObjectCodec<{
26
+ type: t.LiteralCodec<"mysql">;
27
+ uri: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
28
+ hostname: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
29
+ port: t.OptionalCodec<t.Codec<number, string | number, string, t.CodecProps>>;
30
+ username: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
31
+ password: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
32
+ database: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
33
+ cacert: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
34
+ client_certificate: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
35
+ client_private_key: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
36
+ }>>;
37
+ /**
38
+ * Config input specified when starting services
39
+ */
40
+ export type MySQLConnectionConfig = t.Decoded<typeof MySQLConnectionConfig>;
41
+ /**
42
+ * Resolved version of {@link MySQLConnectionConfig}
43
+ */
44
+ export type ResolvedConnectionConfig = MySQLConnectionConfig & NormalizedMySQLConnectionConfig;
45
+ /**
46
+ * Validate and normalize connection options.
47
+ *
48
+ * Returns destructured options.
49
+ */
50
+ export declare function normalizeConnectionConfig(options: MySQLConnectionConfig): NormalizedMySQLConnectionConfig;
@@ -0,0 +1,61 @@
1
+ import * as service_types from '@powersync/service-types';
2
+ import * as t from 'ts-codec';
3
+ import * as urijs from 'uri-js';
4
+ export const MYSQL_CONNECTION_TYPE = 'mysql';
5
+ export const MySQLConnectionConfig = service_types.configFile.DataSourceConfig.and(t.object({
6
+ type: t.literal(MYSQL_CONNECTION_TYPE),
7
+ uri: t.string.optional(),
8
+ hostname: t.string.optional(),
9
+ port: service_types.configFile.portCodec.optional(),
10
+ username: t.string.optional(),
11
+ password: t.string.optional(),
12
+ database: t.string.optional(),
13
+ cacert: t.string.optional(),
14
+ client_certificate: t.string.optional(),
15
+ client_private_key: t.string.optional()
16
+ }));
17
+ /**
18
+ * Validate and normalize connection options.
19
+ *
20
+ * Returns destructured options.
21
+ */
22
+ export function normalizeConnectionConfig(options) {
23
+ let uri;
24
+ if (options.uri) {
25
+ uri = urijs.parse(options.uri);
26
+ if (uri.scheme != 'mysql') {
27
+ throw new Error(`Invalid URI - protocol must be mysql, got ${uri.scheme}`);
28
+ }
29
+ }
30
+ else {
31
+ uri = urijs.parse('mysql:///');
32
+ }
33
+ const hostname = options.hostname ?? uri.host ?? '';
34
+ const port = Number(options.port ?? uri.port ?? 3306);
35
+ const database = options.database ?? uri.path?.substring(1) ?? '';
36
+ const [uri_username, uri_password] = (uri.userinfo ?? '').split(':');
37
+ const username = options.username ?? uri_username ?? '';
38
+ const password = options.password ?? uri_password ?? '';
39
+ if (hostname == '') {
40
+ throw new Error(`hostname required`);
41
+ }
42
+ if (username == '') {
43
+ throw new Error(`username required`);
44
+ }
45
+ if (password == '') {
46
+ throw new Error(`password required`);
47
+ }
48
+ if (database == '') {
49
+ throw new Error(`database required`);
50
+ }
51
+ return {
52
+ id: options.id ?? 'default',
53
+ tag: options.tag ?? 'default',
54
+ hostname,
55
+ port,
56
+ database,
57
+ username,
58
+ password
59
+ };
60
+ }
61
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import mysql from 'mysql2';
2
+ import mysqlPromise from 'mysql2/promise';
3
+ import * as types from '../types/types.js';
4
+ export type RetriedQueryOptions = {
5
+ connection: mysqlPromise.Connection;
6
+ query: string;
7
+ params?: any[];
8
+ retries?: number;
9
+ };
10
+ /**
11
+ * Retry a simple query - up to 2 attempts total.
12
+ */
13
+ export declare function retriedQuery(options: RetriedQueryOptions): Promise<[mysql.RowDataPacket[], mysql.FieldPacket[]]>;
14
+ export declare function createPool(config: types.NormalizedMySQLConnectionConfig, options?: mysql.PoolOptions): mysql.Pool;
@@ -0,0 +1,38 @@
1
+ import { logger } from '@powersync/lib-services-framework';
2
+ import mysql from 'mysql2';
3
+ /**
4
+ * Retry a simple query - up to 2 attempts total.
5
+ */
6
+ export async function retriedQuery(options) {
7
+ const { connection, query, params = [], retries = 2 } = options;
8
+ for (let tries = retries;; tries--) {
9
+ try {
10
+ logger.debug(`Executing query: ${query}`);
11
+ return connection.query(query, params);
12
+ }
13
+ catch (e) {
14
+ if (tries == 1) {
15
+ throw e;
16
+ }
17
+ logger.warn('Query error, retrying', e);
18
+ }
19
+ }
20
+ }
21
+ export function createPool(config, options) {
22
+ const sslOptions = {
23
+ ca: config.cacert,
24
+ key: config.client_private_key,
25
+ cert: config.client_certificate
26
+ };
27
+ const hasSSLOptions = Object.values(sslOptions).some((v) => !!v);
28
+ // TODO confirm if default options are fine for Powersync use case
29
+ return mysql.createPool({
30
+ host: config.hostname,
31
+ user: config.username,
32
+ password: config.password,
33
+ database: config.database,
34
+ ssl: hasSSLOptions ? sslOptions : undefined,
35
+ ...(options || {})
36
+ });
37
+ }
38
+ //# sourceMappingURL=mysql_utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mysql_utils.js","sourceRoot":"","sources":["../../src/utils/mysql_utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAC3D,OAAO,KAAK,MAAM,QAAQ,CAAC;AAW3B;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAA4B;IAC7D,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,EAAE,EAAE,OAAO,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC;IAChE,KAAK,IAAI,KAAK,GAAG,OAAO,GAAI,KAAK,EAAE,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,oBAAoB,KAAK,EAAE,CAAC,CAAC;YAC1C,OAAO,UAAU,CAAC,KAAK,CAA+B,KAAK,EAAE,MAAM,CAAC,CAAC;QACvE,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;gBACf,MAAM,CAAC,CAAC;YACV,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,CAAC,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAA6C,EAAE,OAA2B;IACnG,MAAM,UAAU,GAAG;QACjB,EAAE,EAAE,MAAM,CAAC,MAAM;QACjB,GAAG,EAAE,MAAM,CAAC,kBAAkB;QAC9B,IAAI,EAAE,MAAM,CAAC,kBAAkB;KAChC,CAAC;IACF,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjE,kEAAkE;IAClE,OAAO,KAAK,CAAC,UAAU,CAAC;QACtB,IAAI,EAAE,MAAM,CAAC,QAAQ;QACrB,IAAI,EAAE,MAAM,CAAC,QAAQ;QACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;QAC3C,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;KACnB,CAAC,CAAC;AACL,CAAC"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@powersync/service-module-mysql",
3
+ "repository": "https://github.com/powersync-ja/powersync-service",
4
+ "types": "dist/index.d.ts",
5
+ "version": "0.0.0-dev-20241015210820",
6
+ "license": "FSL-1.1-Apache-2.0",
7
+ "main": "dist/index.js",
8
+ "type": "module",
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.js",
16
+ "default": "./dist/index.js"
17
+ },
18
+ "./types": {
19
+ "import": "./dist/types/types.js",
20
+ "require": "./dist/types/types.js",
21
+ "default": "./dist/types/types.js"
22
+ }
23
+ },
24
+ "dependencies": {
25
+ "@powersync/mysql-zongji": "0.0.0-dev-20241008105633",
26
+ "semver": "^7.5.4",
27
+ "async": "^3.2.4",
28
+ "mysql2": "^3.11.0",
29
+ "ts-codec": "^1.2.2",
30
+ "uri-js": "^4.4.1",
31
+ "uuid": "^9.0.1",
32
+ "@powersync/lib-services-framework": "0.0.0-dev-20241015210820",
33
+ "@powersync/service-core": "0.0.0-dev-20241015210820",
34
+ "@powersync/service-sync-rules": "0.0.0-dev-20241015210820",
35
+ "@powersync/service-types": "0.0.0-dev-20241015210820"
36
+ },
37
+ "devDependencies": {
38
+ "@types/semver": "^7.5.4",
39
+ "@types/async": "^3.2.24",
40
+ "@types/uuid": "^9.0.8",
41
+ "typescript": "^5.5.4",
42
+ "vite-tsconfig-paths": "^4.3.2",
43
+ "vitest": "^0.34.6"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc -b",
47
+ "build:tests": "tsc -b test/tsconfig.json",
48
+ "clean": "rm -rf ./lib && tsc -b --clean",
49
+ "test": "vitest"
50
+ }
51
+ }