@lika85456/s3qlite 0.1.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.
- package/cjs/effect.js +18 -0
- package/cjs/effect.js.map +1 -0
- package/cjs/index.js +40 -0
- package/cjs/index.js.map +1 -0
- package/cjs/package.json +3 -0
- package/cjs/src/batches.js +39 -0
- package/cjs/src/batches.js.map +1 -0
- package/cjs/src/cdc/apply.js +404 -0
- package/cjs/src/cdc/apply.js.map +1 -0
- package/cjs/src/cdc/extract.js +38 -0
- package/cjs/src/cdc/extract.js.map +1 -0
- package/cjs/src/cdc/protobuf.js +135 -0
- package/cjs/src/cdc/protobuf.js.map +1 -0
- package/cjs/src/cdc/testUtils.js +49 -0
- package/cjs/src/cdc/testUtils.js.map +1 -0
- package/cjs/src/cdc/truncate.js +7 -0
- package/cjs/src/cdc/truncate.js.map +1 -0
- package/cjs/src/cdc/types.js +10 -0
- package/cjs/src/cdc/types.js.map +1 -0
- package/cjs/src/cdc/withoutCDC.js +7 -0
- package/cjs/src/cdc/withoutCDC.js.map +1 -0
- package/cjs/src/connection.js +128 -0
- package/cjs/src/connection.js.map +1 -0
- package/cjs/src/contexts.js +11 -0
- package/cjs/src/contexts.js.map +1 -0
- package/cjs/src/index.js +30 -0
- package/cjs/src/index.js.map +1 -0
- package/cjs/src/kv/fileKV.js +131 -0
- package/cjs/src/kv/fileKV.js.map +1 -0
- package/cjs/src/kv/kv.js +8 -0
- package/cjs/src/kv/kv.js.map +1 -0
- package/cjs/src/kv/memoryKV.js +16 -0
- package/cjs/src/kv/memoryKV.js.map +1 -0
- package/cjs/src/kv/s3KV.js +283 -0
- package/cjs/src/kv/s3KV.js.map +1 -0
- package/cjs/src/kv/syncFiles.js +32 -0
- package/cjs/src/kv/syncFiles.js.map +1 -0
- package/cjs/src/pull.js +101 -0
- package/cjs/src/pull.js.map +1 -0
- package/cjs/src/push.js +58 -0
- package/cjs/src/push.js.map +1 -0
- package/cjs/src/storage.js +41 -0
- package/cjs/src/storage.js.map +1 -0
- package/cjs/src/types.js +3 -0
- package/cjs/src/types.js.map +1 -0
- package/cjs/src/wrapDatabase.js +80 -0
- package/cjs/src/wrapDatabase.js.map +1 -0
- package/dts/effect.d.ts +1 -0
- package/dts/index.d.ts +16 -0
- package/dts/src/batches.d.ts +10 -0
- package/dts/src/cdc/apply.d.ts +14 -0
- package/dts/src/cdc/extract.d.ts +8 -0
- package/dts/src/cdc/protobuf.d.ts +3 -0
- package/dts/src/cdc/testUtils.d.ts +19 -0
- package/dts/src/cdc/truncate.d.ts +6 -0
- package/dts/src/cdc/types.d.ts +35 -0
- package/dts/src/cdc/withoutCDC.d.ts +3 -0
- package/dts/src/connection.d.ts +5 -0
- package/dts/src/contexts.d.ts +22 -0
- package/dts/src/index.d.ts +12 -0
- package/dts/src/kv/fileKV.d.ts +5 -0
- package/dts/src/kv/kv.d.ts +42 -0
- package/dts/src/kv/memoryKV.d.ts +5 -0
- package/dts/src/kv/s3KV.d.ts +4 -0
- package/dts/src/kv/syncFiles.d.ts +4 -0
- package/dts/src/pull.d.ts +8 -0
- package/dts/src/push.d.ts +4 -0
- package/dts/src/storage.d.ts +22 -0
- package/dts/src/types.d.ts +38 -0
- package/dts/src/wrapDatabase.d.ts +1 -0
- package/esm/effect.js +2 -0
- package/esm/effect.js.map +1 -0
- package/esm/index.js +22 -0
- package/esm/index.js.map +1 -0
- package/esm/src/batches.js +34 -0
- package/esm/src/batches.js.map +1 -0
- package/esm/src/cdc/apply.js +398 -0
- package/esm/src/cdc/apply.js.map +1 -0
- package/esm/src/cdc/extract.js +33 -0
- package/esm/src/cdc/extract.js.map +1 -0
- package/esm/src/cdc/protobuf.js +127 -0
- package/esm/src/cdc/protobuf.js.map +1 -0
- package/esm/src/cdc/testUtils.js +42 -0
- package/esm/src/cdc/testUtils.js.map +1 -0
- package/esm/src/cdc/truncate.js +3 -0
- package/esm/src/cdc/truncate.js.map +1 -0
- package/esm/src/cdc/types.js +7 -0
- package/esm/src/cdc/types.js.map +1 -0
- package/esm/src/cdc/withoutCDC.js +3 -0
- package/esm/src/cdc/withoutCDC.js.map +1 -0
- package/esm/src/connection.js +123 -0
- package/esm/src/connection.js.map +1 -0
- package/esm/src/contexts.js +6 -0
- package/esm/src/contexts.js.map +1 -0
- package/esm/src/index.js +12 -0
- package/esm/src/index.js.map +1 -0
- package/esm/src/kv/fileKV.js +127 -0
- package/esm/src/kv/fileKV.js.map +1 -0
- package/esm/src/kv/kv.js +4 -0
- package/esm/src/kv/kv.js.map +1 -0
- package/esm/src/kv/memoryKV.js +12 -0
- package/esm/src/kv/memoryKV.js.map +1 -0
- package/esm/src/kv/s3KV.js +279 -0
- package/esm/src/kv/s3KV.js.map +1 -0
- package/esm/src/kv/syncFiles.js +27 -0
- package/esm/src/kv/syncFiles.js.map +1 -0
- package/esm/src/pull.js +97 -0
- package/esm/src/pull.js.map +1 -0
- package/esm/src/push.js +54 -0
- package/esm/src/push.js.map +1 -0
- package/esm/src/storage.js +26 -0
- package/esm/src/storage.js.map +1 -0
- package/esm/src/types.js +2 -0
- package/esm/src/types.js.map +1 -0
- package/esm/src/wrapDatabase.js +76 -0
- package/esm/src/wrapDatabase.js.map +1 -0
- package/package.json +35 -0
package/cjs/effect.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./src/index.js"), exports);
|
|
18
|
+
//# sourceMappingURL=effect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effect.js","sourceRoot":"","sources":["../../effect.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA+B","sourcesContent":["export * from \"./src/index.js\";"]}
|
package/cjs/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.connect = void 0;
|
|
18
|
+
const client_s3_1 = require("@effect-aws/client-s3");
|
|
19
|
+
const NodeFileSystem_1 = require("@effect/platform-node/NodeFileSystem");
|
|
20
|
+
const NodePath_1 = require("@effect/platform-node/NodePath");
|
|
21
|
+
const effect_1 = require("effect");
|
|
22
|
+
const index_1 = require("./src/index");
|
|
23
|
+
const toPromiseDatabase = (database) => {
|
|
24
|
+
const pull = database.pull.bind(database);
|
|
25
|
+
const push = database.push.bind(database);
|
|
26
|
+
const sync = database.sync.bind(database);
|
|
27
|
+
const fork = database.fork.bind(database);
|
|
28
|
+
const checkpoint = database.checkpoint.bind(database);
|
|
29
|
+
return Object.assign(database, {
|
|
30
|
+
pull: () => effect_1.Effect.runPromise(pull()),
|
|
31
|
+
push: () => effect_1.Effect.runPromise(push()),
|
|
32
|
+
sync: () => effect_1.Effect.runPromise(sync()),
|
|
33
|
+
fork: (dbName) => effect_1.Effect.runPromise(fork(dbName)),
|
|
34
|
+
checkpoint: () => effect_1.Effect.runPromise(checkpoint()),
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
const connect = async (dbName, { s3, ...connectionOptions }) => effect_1.Effect.runPromise(effect_1.Effect.scoped((0, index_1.connect)(dbName, connectionOptions).pipe(effect_1.Effect.provide(NodeFileSystem_1.layer), effect_1.Effect.provide(NodePath_1.layer), effect_1.Effect.provide(client_s3_1.S3.layer(s3 ?? {})), effect_1.Effect.map(toPromiseDatabase))));
|
|
38
|
+
exports.connect = connect;
|
|
39
|
+
__exportStar(require("./src/index.js"), exports);
|
|
40
|
+
//# sourceMappingURL=index.js.map
|
package/cjs/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,qDAA2C;AAC3C,yEAAgF;AAChF,6DAAoE;AAEpE,mCAAgC;AAEhC,uCAAuD;AAiBvD,MAAM,iBAAiB,GAAG,CAAC,QAAyB,EAA0B,EAAE;IAC/E,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAEtD,OAAO,MAAM,CAAC,MAAM,CAAC,QAAoB,EAAE;QAC1C,IAAI,EAAE,GAAG,EAAE,CAAC,eAAM,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QACrC,IAAI,EAAE,GAAG,EAAE,CAAC,eAAM,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QACrC,IAAI,EAAE,GAAG,EAAE,CAAC,eAAM,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QACrC,IAAI,EAAE,CAAC,MAAc,EAAE,EAAE,CAAC,eAAM,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACzD,UAAU,EAAE,GAAG,EAAE,CAAC,eAAM,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;KACjD,CAA2B,CAAC;AAC9B,CAAC,CAAC;AAEK,MAAM,OAAO,GAAG,KAAK,EAC3B,MAAc,EACd,EAAE,EAAE,EAAE,GAAG,iBAAiB,EAA4B,EACpB,EAAE,CACpC,eAAM,CAAC,UAAU,CAChB,eAAM,CAAC,MAAM,CACZ,IAAA,eAAa,EAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,IAAI,CAC5C,eAAM,CAAC,OAAO,CAAC,sBAAe,CAAC,EAC/B,eAAM,CAAC,OAAO,CAAC,gBAAS,CAAC,EACzB,eAAM,CAAC,OAAO,CAAC,cAAE,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAClC,eAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAC7B,CACD,CACD,CAAC;AAbU,QAAA,OAAO,WAajB;AAEH,iDAA+B","sourcesContent":["import { S3 } from \"@effect-aws/client-s3\";\nimport { layer as fileSystemLayer } from \"@effect/platform-node/NodeFileSystem\";\nimport { layer as pathLayer } from \"@effect/platform-node/NodePath\";\nimport type { Database } from \"@tursodatabase/database\";\nimport { Effect } from \"effect\";\n\nimport { connect as connectEffect } from \"./src/index\";\nimport type { ConnectionOptions, S3qliteDatabase } from \"./src/types\";\n\ntype PromiseMethods = {\n\tpull: () => Promise<void>;\n\tpush: () => Promise<void>;\n\tsync: () => Promise<void>;\n\tfork: (dbName: string) => Promise<void>;\n\tcheckpoint: () => Promise<void>;\n};\n\nexport type PromiseS3qliteDatabase = Database & PromiseMethods;\n\nexport type PromiseConnectionOptions = ConnectionOptions & {\n\ts3?: Parameters<typeof S3.layer>[0];\n};\n\nconst toPromiseDatabase = (database: S3qliteDatabase): PromiseS3qliteDatabase => {\n\tconst pull = database.pull.bind(database);\n\tconst push = database.push.bind(database);\n\tconst sync = database.sync.bind(database);\n\tconst fork = database.fork.bind(database);\n\tconst checkpoint = database.checkpoint.bind(database);\n\n\treturn Object.assign(database as Database, {\n\t\tpull: () => Effect.runPromise(pull()),\n\t\tpush: () => Effect.runPromise(push()),\n\t\tsync: () => Effect.runPromise(sync()),\n\t\tfork: (dbName: string) => Effect.runPromise(fork(dbName)),\n\t\tcheckpoint: () => Effect.runPromise(checkpoint()),\n\t}) as PromiseS3qliteDatabase;\n};\n\nexport const connect = async (\n\tdbName: string,\n\t{ s3, ...connectionOptions }: PromiseConnectionOptions,\n): Promise<PromiseS3qliteDatabase> =>\n\tEffect.runPromise(\n\t\tEffect.scoped(\n\t\t\tconnectEffect(dbName, connectionOptions).pipe(\n\t\t\t\tEffect.provide(fileSystemLayer),\n\t\t\t\tEffect.provide(pathLayer),\n\t\t\t\tEffect.provide(S3.layer(s3 ?? {})),\n\t\t\t\tEffect.map(toPromiseDatabase),\n\t\t\t),\n\t\t),\n\t);\n\nexport * from \"./src/index.js\";"]}
|
package/cjs/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractBatch = exports.applyBatch = void 0;
|
|
4
|
+
const effect_1 = require("effect");
|
|
5
|
+
const uuid_1 = require("uuid");
|
|
6
|
+
const apply_1 = require("./cdc/apply");
|
|
7
|
+
const extract_1 = require("./cdc/extract");
|
|
8
|
+
const protobuf_1 = require("./cdc/protobuf");
|
|
9
|
+
const storage_1 = require("./storage");
|
|
10
|
+
const applyBatch = (database, batch) => effect_1.Effect.gen(function* () {
|
|
11
|
+
const localKv = yield* storage_1.LocalKV;
|
|
12
|
+
const deserialized = (0, protobuf_1.deserializeCDC)(yield* localKv.get((0, storage_1.batchKey)(batch.id)).pipe(effect_1.Effect.flatMap(effect_1.Option.match({
|
|
13
|
+
onNone: () => effect_1.Effect.die(new Error(`Missing local batch ${batch.id}`)),
|
|
14
|
+
onSome: ({ value }) => effect_1.Effect.succeed(value),
|
|
15
|
+
}))));
|
|
16
|
+
yield* (0, apply_1.replayCDC)(database, deserialized).pipe(effect_1.Effect.orDie);
|
|
17
|
+
});
|
|
18
|
+
exports.applyBatch = applyBatch;
|
|
19
|
+
const extractBatch = (database, lastChangeId) => effect_1.Effect.gen(function* () {
|
|
20
|
+
const localKV = yield* storage_1.LocalKV;
|
|
21
|
+
const changes = yield* (0, extract_1.extractCDC)(database, lastChangeId);
|
|
22
|
+
if (changes.length === 0) {
|
|
23
|
+
return effect_1.Option.none();
|
|
24
|
+
}
|
|
25
|
+
const firstChange = changes[0];
|
|
26
|
+
const lastChange = changes[changes.length - 1];
|
|
27
|
+
if (!firstChange || !lastChange) {
|
|
28
|
+
return yield* effect_1.Effect.die(new Error("Unexpected empty changes array"));
|
|
29
|
+
}
|
|
30
|
+
const id = (0, uuid_1.v4)();
|
|
31
|
+
const serialized = (0, protobuf_1.serializeCDC)(changes);
|
|
32
|
+
yield* localKV.set((0, storage_1.batchKey)(id), serialized);
|
|
33
|
+
return effect_1.Option.some({
|
|
34
|
+
batch: { id },
|
|
35
|
+
lastLocalChangeId: lastChange.changeId,
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
exports.extractBatch = extractBatch;
|
|
39
|
+
//# sourceMappingURL=batches.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batches.js","sourceRoot":"","sources":["../../../src/batches.ts"],"names":[],"mappings":";;;AACA,mCAAwC;AACxC,+BAA0B;AAE1B,uCAAwC;AACxC,2CAA2C;AAC3C,6CAA8D;AAC9D,uCAA8C;AAGvC,MAAM,UAAU,GAAG,CAAC,QAAkB,EAAE,KAAY,EAAuC,EAAE,CACnG,eAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,iBAAO,CAAC;IAC/B,MAAM,YAAY,GAAG,IAAA,yBAAc,EAClC,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAA,kBAAQ,EAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAC1C,eAAM,CAAC,OAAO,CACb,eAAM,CAAC,KAAK,CAAC;QACZ,MAAM,EAAE,GAAG,EAAE,CAAC,eAAM,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,uBAAuB,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QACtE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,eAAM,CAAC,OAAO,CAAC,KAAK,CAAC;KAC5C,CAAC,CACF,CACD,CACD,CAAC;IACF,KAAK,CAAC,CAAC,IAAA,iBAAS,EAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,eAAM,CAAC,KAAK,CAAC,CAAC;AAC7D,CAAC,CAAC,CAAC;AAdS,QAAA,UAAU,cAcnB;AAOG,MAAM,YAAY,GAAG,CAC3B,QAAkB,EAClB,YAAoB,EAC2C,EAAE,CACjE,eAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,iBAAO,CAAC;IAC/B,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,IAAA,oBAAU,EAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IAE1D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,eAAM,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC/B,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAE/C,IAAI,CAAC,WAAW,IAAI,CAAC,UAAU,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC,CAAC,eAAM,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,EAAE,GAAG,IAAA,SAAE,GAAE,CAAC;IAChB,MAAM,UAAU,GAAG,IAAA,uBAAY,EAAC,OAAO,CAAC,CAAC;IACzC,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAA,kBAAQ,EAAC,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC;IAE7C,OAAO,eAAM,CAAC,IAAI,CAAiB;QAClC,KAAK,EAAE,EAAE,EAAE,EAAE;QACb,iBAAiB,EAAE,UAAU,CAAC,QAAQ;KACtC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AA3BS,QAAA,YAAY,gBA2BrB","sourcesContent":["import type { Database } from \"@tursodatabase/database\";\nimport { Effect, Option } from \"effect\";\nimport { v4 } from \"uuid\";\n\nimport { replayCDC } from \"./cdc/apply\";\nimport { extractCDC } from \"./cdc/extract\";\nimport { deserializeCDC, serializeCDC } from \"./cdc/protobuf\";\nimport { LocalKV, batchKey } from \"./storage\";\nimport type { Batch } from \"./types\";\n\nexport const applyBatch = (database: Database, batch: Batch): Effect.Effect<void, never, LocalKV> =>\n\tEffect.gen(function* () {\n\t\tconst localKv = yield* LocalKV;\n\t\tconst deserialized = deserializeCDC(\n\t\t\tyield* localKv.get(batchKey(batch.id)).pipe(\n\t\t\t\tEffect.flatMap(\n\t\t\t\t\tOption.match({\n\t\t\t\t\t\tonNone: () => Effect.die(new Error(`Missing local batch ${batch.id}`)),\n\t\t\t\t\t\tonSome: ({ value }) => Effect.succeed(value),\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t\tyield* replayCDC(database, deserialized).pipe(Effect.orDie);\n\t});\n\nexport type ExtractedBatch = {\n\tbatch: Batch;\n\tlastLocalChangeId: number;\n};\n\nexport const extractBatch = (\n\tdatabase: Database,\n\tlastChangeId: number,\n): Effect.Effect<Option.Option<ExtractedBatch>, never, LocalKV> =>\n\tEffect.gen(function* () {\n\t\tconst localKV = yield* LocalKV;\n\t\tconst changes = yield* extractCDC(database, lastChangeId);\n\n\t\tif (changes.length === 0) {\n\t\t\treturn Option.none();\n\t\t}\n\n\t\tconst firstChange = changes[0];\n\t\tconst lastChange = changes[changes.length - 1];\n\n\t\tif (!firstChange || !lastChange) {\n\t\t\treturn yield* Effect.die(new Error(\"Unexpected empty changes array\"));\n\t\t}\n\n\t\tconst id = v4();\n\t\tconst serialized = serializeCDC(changes);\n\t\tyield* localKV.set(batchKey(id), serialized);\n\n\t\treturn Option.some<ExtractedBatch>({\n\t\t\tbatch: { id },\n\t\t\tlastLocalChangeId: lastChange.changeId,\n\t\t});\n\t});"]}
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.replayCDC = exports.applyCDC = exports.ReplayError = void 0;
|
|
4
|
+
const effect_1 = require("effect");
|
|
5
|
+
const types_1 = require("./types");
|
|
6
|
+
class ReplayError extends effect_1.Data.TaggedError("ReplayError") {
|
|
7
|
+
}
|
|
8
|
+
exports.ReplayError = ReplayError;
|
|
9
|
+
const textDecoder = new TextDecoder();
|
|
10
|
+
const readVarint = (bytes, offset) => {
|
|
11
|
+
let value = 0n;
|
|
12
|
+
for (let index = 0; index < 9; index += 1) {
|
|
13
|
+
const cursor = offset + index;
|
|
14
|
+
if (cursor >= bytes.length) {
|
|
15
|
+
throw new Error("Unexpected end of CDC record");
|
|
16
|
+
}
|
|
17
|
+
const byte = bytes[cursor];
|
|
18
|
+
if (index === 8) {
|
|
19
|
+
value = (value << 8n) | BigInt(byte);
|
|
20
|
+
const number = Number(value);
|
|
21
|
+
if (!Number.isSafeInteger(number)) {
|
|
22
|
+
throw new Error("CDC varint is too large");
|
|
23
|
+
}
|
|
24
|
+
return [number, cursor + 1];
|
|
25
|
+
}
|
|
26
|
+
value = (value << 7n) | BigInt(byte & 0x7f);
|
|
27
|
+
if ((byte & 0x80) === 0) {
|
|
28
|
+
const number = Number(value);
|
|
29
|
+
if (!Number.isSafeInteger(number)) {
|
|
30
|
+
throw new Error("CDC varint is too large");
|
|
31
|
+
}
|
|
32
|
+
return [number, cursor + 1];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
throw new Error("Invalid CDC varint");
|
|
36
|
+
};
|
|
37
|
+
const readInteger = (bytes, offset, length) => {
|
|
38
|
+
let value = 0n;
|
|
39
|
+
for (let index = 0; index < length; index += 1) {
|
|
40
|
+
const cursor = offset + index;
|
|
41
|
+
if (cursor >= bytes.length) {
|
|
42
|
+
throw new Error("Unexpected end of CDC integer");
|
|
43
|
+
}
|
|
44
|
+
value = (value << 8n) | BigInt(bytes[cursor]);
|
|
45
|
+
}
|
|
46
|
+
const bits = BigInt(length * 8);
|
|
47
|
+
const signBit = 1n << (bits - 1n);
|
|
48
|
+
const signed = (value & signBit) === 0n ? value : value - (1n << bits);
|
|
49
|
+
const number = Number(signed);
|
|
50
|
+
if (!Number.isSafeInteger(number)) {
|
|
51
|
+
throw new Error("CDC integer is outside JS safe integer range");
|
|
52
|
+
}
|
|
53
|
+
return number;
|
|
54
|
+
};
|
|
55
|
+
const decodeValue = (bytes, offset, serialType) => {
|
|
56
|
+
switch (serialType) {
|
|
57
|
+
case 0:
|
|
58
|
+
// oxlint-disable-next-line local-rules/no-null-undefined-option -- SQLite record serial type 0 represents SQL NULL.
|
|
59
|
+
return [null, offset];
|
|
60
|
+
case 1:
|
|
61
|
+
return [readInteger(bytes, offset, 1), offset + 1];
|
|
62
|
+
case 2:
|
|
63
|
+
return [readInteger(bytes, offset, 2), offset + 2];
|
|
64
|
+
case 3:
|
|
65
|
+
return [readInteger(bytes, offset, 3), offset + 3];
|
|
66
|
+
case 4:
|
|
67
|
+
return [readInteger(bytes, offset, 4), offset + 4];
|
|
68
|
+
case 5:
|
|
69
|
+
return [readInteger(bytes, offset, 6), offset + 6];
|
|
70
|
+
case 6:
|
|
71
|
+
return [readInteger(bytes, offset, 8), offset + 8];
|
|
72
|
+
case 7:
|
|
73
|
+
if (offset + 8 > bytes.length) {
|
|
74
|
+
throw new Error("Unexpected end of CDC float");
|
|
75
|
+
}
|
|
76
|
+
return [
|
|
77
|
+
new DataView(bytes.buffer, bytes.byteOffset + offset, 8).getFloat64(0, false),
|
|
78
|
+
offset + 8,
|
|
79
|
+
];
|
|
80
|
+
case 8:
|
|
81
|
+
return [0, offset];
|
|
82
|
+
case 9:
|
|
83
|
+
return [1, offset];
|
|
84
|
+
case 10:
|
|
85
|
+
case 11:
|
|
86
|
+
throw new Error(`Unsupported CDC serial type ${serialType}`);
|
|
87
|
+
default: {
|
|
88
|
+
const isBlob = serialType % 2 === 0;
|
|
89
|
+
const length = isBlob ? (serialType - 12) / 2 : (serialType - 13) / 2;
|
|
90
|
+
if (length < 0 || offset + length > bytes.length) {
|
|
91
|
+
throw new Error("Unexpected end of CDC payload");
|
|
92
|
+
}
|
|
93
|
+
const value = bytes.slice(offset, offset + length);
|
|
94
|
+
return [isBlob ? value : textDecoder.decode(value), offset + length];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const decodeRecord = (bytes) => effect_1.Effect.try(() => {
|
|
99
|
+
const [headerSize, start] = readVarint(bytes, 0);
|
|
100
|
+
if (headerSize > bytes.length) {
|
|
101
|
+
throw new Error("CDC record header exceeds payload length");
|
|
102
|
+
}
|
|
103
|
+
const serialTypes = [];
|
|
104
|
+
let headerOffset = start;
|
|
105
|
+
while (headerOffset < headerSize) {
|
|
106
|
+
const [serialType, nextOffset] = readVarint(bytes, headerOffset);
|
|
107
|
+
serialTypes.push(serialType);
|
|
108
|
+
headerOffset = nextOffset;
|
|
109
|
+
}
|
|
110
|
+
const values = [];
|
|
111
|
+
let valueOffset = headerSize;
|
|
112
|
+
for (const serialType of serialTypes) {
|
|
113
|
+
const [value, nextOffset] = decodeValue(bytes, valueOffset, serialType);
|
|
114
|
+
values.push(value);
|
|
115
|
+
valueOffset = nextOffset;
|
|
116
|
+
}
|
|
117
|
+
return values;
|
|
118
|
+
});
|
|
119
|
+
const decodeSchemaRow = (bytes) => decodeRecord(bytes).pipe(effect_1.Effect.flatMap((values) => values.length === 5 &&
|
|
120
|
+
typeof values[0] === "string" &&
|
|
121
|
+
typeof values[1] === "string" &&
|
|
122
|
+
typeof values[2] === "string" &&
|
|
123
|
+
typeof values[3] === "number" &&
|
|
124
|
+
// oxlint-disable-next-line local-rules/no-null-undefined-option -- sqlite_schema CDC can carry NULL SQL for implicit objects.
|
|
125
|
+
(typeof values[4] === "string" || values[4] === null)
|
|
126
|
+
? effect_1.Effect.succeed({
|
|
127
|
+
type: values[0],
|
|
128
|
+
name: values[1],
|
|
129
|
+
tblName: values[2],
|
|
130
|
+
rootpage: values[3],
|
|
131
|
+
sql: values[4],
|
|
132
|
+
})
|
|
133
|
+
: effect_1.Effect.fail(new Error("Invalid sqlite_schema CDC record"))));
|
|
134
|
+
const parseUpdatedColumns = (columns, values) => {
|
|
135
|
+
if (values.length % 2 !== 0) {
|
|
136
|
+
return effect_1.Effect.fail(new Error("Invalid CDC updates record"));
|
|
137
|
+
}
|
|
138
|
+
const columnCount = values.length / 2;
|
|
139
|
+
const recordColumns = columns.slice(0, columnCount);
|
|
140
|
+
const changedColumns = [];
|
|
141
|
+
const changedValues = [];
|
|
142
|
+
for (let index = 0; index < columnCount; index += 1) {
|
|
143
|
+
const flag = values[index];
|
|
144
|
+
if (flag !== 0 && flag !== 1) {
|
|
145
|
+
return effect_1.Effect.fail(new Error(`Invalid CDC update flag at column ${index}`));
|
|
146
|
+
}
|
|
147
|
+
if (flag === 0) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (index >= recordColumns.length) {
|
|
151
|
+
return effect_1.Effect.fail(new Error(`CDC updates reference missing column ${index}`));
|
|
152
|
+
}
|
|
153
|
+
changedColumns.push(recordColumns[index].name);
|
|
154
|
+
changedValues.push(values[columnCount + index]);
|
|
155
|
+
}
|
|
156
|
+
return effect_1.Effect.succeed([changedColumns, changedValues]);
|
|
157
|
+
};
|
|
158
|
+
const getTableColumns = (database, tableName) => effect_1.Effect.tryPromise(() => database.all(`PRAGMA table_info("${tableName.replaceAll('"', '""')}")`)).pipe(effect_1.Effect.flatMap((columns) => columns.length > 0
|
|
159
|
+
? effect_1.Effect.succeed(columns)
|
|
160
|
+
: effect_1.Effect.fail(new Error(`Table ${tableName} does not exist`))), effect_1.Effect.tapError((error) => effect_1.Effect.logError("cdc:get-table-columns").pipe(effect_1.Effect.annotateLogs({ table: tableName, error: error.message }))));
|
|
161
|
+
const applySchemaInsert = (database, change) => decodeSchemaRow(change.after).pipe(effect_1.Effect.flatMap((row) => {
|
|
162
|
+
// oxlint-disable-next-line local-rules/no-null-undefined-option -- sqlite_schema rows may have NULL SQL and should be skipped.
|
|
163
|
+
if (row.sql === null) {
|
|
164
|
+
return effect_1.Effect.void;
|
|
165
|
+
}
|
|
166
|
+
const sql = row.sql.replace(/^(CREATE\s+(?:UNIQUE\s+)?(?:TABLE|INDEX|TRIGGER|VIEW|MATERIALIZED\s+VIEW)\s+)(?!IF\s+NOT\s+EXISTS\b)/i, "$1IF NOT EXISTS ");
|
|
167
|
+
return effect_1.Effect.tryPromise({
|
|
168
|
+
try: () => database.run(sql),
|
|
169
|
+
catch: (error) => new Error(`Failed to apply schema insert: ${sql}: ${String(error)}`),
|
|
170
|
+
}).pipe(effect_1.Effect.asVoid);
|
|
171
|
+
}));
|
|
172
|
+
const applySchemaDelete = (database, change) => decodeSchemaRow(change.before).pipe(effect_1.Effect.flatMap((row) => {
|
|
173
|
+
// oxlint-disable-next-line local-rules/no-null-undefined-option -- sqlite_schema rows may have NULL SQL and should be skipped.
|
|
174
|
+
if (row.sql === null || row.name.startsWith("sqlite_")) {
|
|
175
|
+
return effect_1.Effect.void;
|
|
176
|
+
}
|
|
177
|
+
const dropType = row.type === "table"
|
|
178
|
+
? "TABLE"
|
|
179
|
+
: row.type === "index"
|
|
180
|
+
? "INDEX"
|
|
181
|
+
: row.type === "view"
|
|
182
|
+
? "VIEW"
|
|
183
|
+
: row.type === "trigger"
|
|
184
|
+
? "TRIGGER"
|
|
185
|
+
: "";
|
|
186
|
+
if (dropType === "") {
|
|
187
|
+
return effect_1.Effect.fail(new Error(`Unsupported sqlite_schema object type ${row.type}`));
|
|
188
|
+
}
|
|
189
|
+
const sql = `DROP ${dropType} IF EXISTS "${row.name.replaceAll('"', '""')}"`;
|
|
190
|
+
return effect_1.Effect.tryPromise({
|
|
191
|
+
try: () => database.run(sql),
|
|
192
|
+
catch: (error) => new Error(`Failed to apply schema delete: ${sql}: ${String(error)}`),
|
|
193
|
+
}).pipe(effect_1.Effect.asVoid);
|
|
194
|
+
}));
|
|
195
|
+
const applySchemaUpdate = (database, change) => decodeRecord(change.updates).pipe(effect_1.Effect.flatMap((values) => {
|
|
196
|
+
if (values.length !== 10) {
|
|
197
|
+
return effect_1.Effect.fail(new Error("Invalid sqlite_schema CDC update record"));
|
|
198
|
+
}
|
|
199
|
+
const ddl = values[9];
|
|
200
|
+
if (typeof ddl !== "string") {
|
|
201
|
+
return effect_1.Effect.fail(new Error("Invalid sqlite_schema DDL update statement"));
|
|
202
|
+
}
|
|
203
|
+
const match = ddl.match(/^ALTER\s+TABLE\s+("(?:[^"]|"")+"|`[^`]+`|\[[^\]]+\]|\S+)\s+ADD\s+COLUMN\s+("(?:[^"]|"")+"|`[^`]+`|\[[^\]]+\]|\S+)/i);
|
|
204
|
+
if (!match) {
|
|
205
|
+
return effect_1.Effect.tryPromise({
|
|
206
|
+
try: () => database.run(ddl),
|
|
207
|
+
catch: (error) => new Error(`Failed to apply schema update: ${ddl}: ${String(error)}`),
|
|
208
|
+
}).pipe(effect_1.Effect.asVoid);
|
|
209
|
+
}
|
|
210
|
+
const tableName = match[1].startsWith('"') && match[1].endsWith('"')
|
|
211
|
+
? match[1].slice(1, -1).replaceAll('""', '"')
|
|
212
|
+
: match[1].startsWith("`") && match[1].endsWith("`")
|
|
213
|
+
? match[1].slice(1, -1)
|
|
214
|
+
: match[1].startsWith("[") && match[1].endsWith("]")
|
|
215
|
+
? match[1].slice(1, -1)
|
|
216
|
+
: match[1];
|
|
217
|
+
const columnName = match[2].startsWith('"') && match[2].endsWith('"')
|
|
218
|
+
? match[2].slice(1, -1).replaceAll('""', '"')
|
|
219
|
+
: match[2].startsWith("`") && match[2].endsWith("`")
|
|
220
|
+
? match[2].slice(1, -1)
|
|
221
|
+
: match[2].startsWith("[") && match[2].endsWith("]")
|
|
222
|
+
? match[2].slice(1, -1)
|
|
223
|
+
: match[2];
|
|
224
|
+
return getTableColumns(database, tableName).pipe(effect_1.Effect.catchAll(() => effect_1.Effect.succeed([])), effect_1.Effect.flatMap((columns) => columns.some((column) => column.name === columnName)
|
|
225
|
+
? effect_1.Effect.void
|
|
226
|
+
: effect_1.Effect.tryPromise({
|
|
227
|
+
try: () => database.run(ddl),
|
|
228
|
+
catch: (error) => new Error(`Failed to apply schema update: ${ddl}: ${String(error)}`),
|
|
229
|
+
}).pipe(effect_1.Effect.asVoid)));
|
|
230
|
+
}));
|
|
231
|
+
const applyInsert = (database, change) => getTableColumns(database, change.tableName).pipe(effect_1.Effect.flatMap((columns) => decodeRecord(change.after).pipe(effect_1.Effect.flatMap((values) => {
|
|
232
|
+
const recordColumns = columns.slice(0, values.length);
|
|
233
|
+
if (recordColumns.length !== values.length) {
|
|
234
|
+
return effect_1.Effect.fail(new Error(`CDC insert for ${change.tableName} has more columns than target table`));
|
|
235
|
+
}
|
|
236
|
+
const pkColumns = columns
|
|
237
|
+
.filter((column) => column.pk > 0)
|
|
238
|
+
.sort((left, right) => left.pk - right.pk);
|
|
239
|
+
const columnSql = recordColumns
|
|
240
|
+
.map((column) => `"${column.name.replaceAll('"', '""')}"`)
|
|
241
|
+
.join(", ");
|
|
242
|
+
const placeholders = recordColumns.map(() => "?").join(", ");
|
|
243
|
+
const conflictSql = pkColumns.length === 0
|
|
244
|
+
? ""
|
|
245
|
+
: ` ON CONFLICT(${pkColumns.map((column) => `"${column.name.replaceAll('"', '""')}"`).join(", ")}) DO UPDATE SET ${recordColumns.map((column) => `"${column.name.replaceAll('"', '""')}" = excluded."${column.name.replaceAll('"', '""')}"`).join(", ")}`;
|
|
246
|
+
const sql = `INSERT INTO "${change.tableName.replaceAll('"', '""')}" (${columnSql}) VALUES (${placeholders})${conflictSql}`;
|
|
247
|
+
return effect_1.Effect.tryPromise({
|
|
248
|
+
try: () => database.run(sql, ...values),
|
|
249
|
+
catch: (error) => new Error(`Failed to apply insert to ${change.tableName}: ${String(error)}`),
|
|
250
|
+
}).pipe(effect_1.Effect.asVoid);
|
|
251
|
+
}))));
|
|
252
|
+
const applyDelete = (database, change) => getTableColumns(database, change.tableName).pipe(effect_1.Effect.flatMap((columns) => decodeRecord(change.before).pipe(effect_1.Effect.flatMap((values) => {
|
|
253
|
+
const pkColumns = columns
|
|
254
|
+
.filter((column) => column.pk > 0)
|
|
255
|
+
.sort((left, right) => left.pk - right.pk);
|
|
256
|
+
if (pkColumns.length === 0) {
|
|
257
|
+
// oxlint-disable-next-line local-rules/no-null-undefined-option -- rowid-free deletes need a missing CDC id check.
|
|
258
|
+
return change.id === null
|
|
259
|
+
? effect_1.Effect.fail(new Error(`Invalid CDC change ${change.changeId}: table ${change.tableName} has no primary key or rowid`))
|
|
260
|
+
: effect_1.Effect.tryPromise({
|
|
261
|
+
try: () => database.run(`DELETE FROM "${change.tableName.replaceAll('"', '""')}" WHERE rowid = ?`, change.id),
|
|
262
|
+
catch: (error) => new Error(`Failed to apply delete by rowid on ${change.tableName}: ${String(error)}`),
|
|
263
|
+
}).pipe(effect_1.Effect.asVoid);
|
|
264
|
+
}
|
|
265
|
+
const whereValues = pkColumns.map((column) => {
|
|
266
|
+
const index = columns.findIndex((item) => item.name === column.name);
|
|
267
|
+
if (index < 0 || index >= values.length) {
|
|
268
|
+
throw new Error(`Missing primary key column ${column.name} in CDC delete for ${change.tableName}`);
|
|
269
|
+
}
|
|
270
|
+
return values[index];
|
|
271
|
+
});
|
|
272
|
+
const sql = `DELETE FROM "${change.tableName.replaceAll('"', '""')}" WHERE ${pkColumns.map((column) => `"${column.name.replaceAll('"', '""')}" = ?`).join(" AND ")}`;
|
|
273
|
+
return effect_1.Effect.tryPromise({
|
|
274
|
+
try: () => database.run(sql, ...whereValues),
|
|
275
|
+
catch: (error) => new Error(`Failed to apply delete on ${change.tableName}: ${String(error)}`),
|
|
276
|
+
}).pipe(effect_1.Effect.asVoid);
|
|
277
|
+
}))));
|
|
278
|
+
const applyUpdateWithMask = (database, change) => getTableColumns(database, change.tableName).pipe(effect_1.Effect.flatMap((columns) => effect_1.Effect.all({
|
|
279
|
+
after: decodeRecord(change.after),
|
|
280
|
+
updates: decodeRecord(change.updates),
|
|
281
|
+
}).pipe(effect_1.Effect.flatMap(({ after, updates }) => parseUpdatedColumns(columns, updates).pipe(effect_1.Effect.flatMap(([changedColumns, changedValues]) => {
|
|
282
|
+
if (changedColumns.length === 0) {
|
|
283
|
+
return effect_1.Effect.void;
|
|
284
|
+
}
|
|
285
|
+
const pkColumns = columns
|
|
286
|
+
.filter((column) => column.pk > 0)
|
|
287
|
+
.sort((left, right) => left.pk - right.pk);
|
|
288
|
+
if (pkColumns.length === 0) {
|
|
289
|
+
// oxlint-disable-next-line local-rules/no-null-undefined-option -- rowid-free updates need a missing CDC id check.
|
|
290
|
+
return change.id === null
|
|
291
|
+
? effect_1.Effect.fail(new Error(`Invalid CDC change ${change.changeId}: table ${change.tableName} has no primary key or rowid`))
|
|
292
|
+
: effect_1.Effect.tryPromise({
|
|
293
|
+
try: () => database.run(`UPDATE "${change.tableName.replaceAll('"', '""')}" SET ${changedColumns.map((column) => `"${column.replaceAll('"', '""')}" = ?`).join(", ")} WHERE rowid = ?`, ...changedValues, change.id),
|
|
294
|
+
catch: (error) => new Error(`Failed to apply update by rowid on ${change.tableName}: ${String(error)}`),
|
|
295
|
+
}).pipe(effect_1.Effect.asVoid);
|
|
296
|
+
}
|
|
297
|
+
const whereValues = pkColumns.map((column) => {
|
|
298
|
+
const index = columns.findIndex((item) => item.name === column.name);
|
|
299
|
+
if (index < 0 || index >= after.length) {
|
|
300
|
+
throw new Error(`Missing primary key column ${column.name} in CDC update for ${change.tableName}`);
|
|
301
|
+
}
|
|
302
|
+
return after[index];
|
|
303
|
+
});
|
|
304
|
+
const sql = `UPDATE "${change.tableName.replaceAll('"', '""')}" SET ${changedColumns.map((column) => `"${column.replaceAll('"', '""')}" = ?`).join(", ")} WHERE ${pkColumns.map((column) => `"${column.name.replaceAll('"', '""')}" = ?`).join(" AND ")}`;
|
|
305
|
+
return effect_1.Effect.tryPromise({
|
|
306
|
+
try: () => database.run(sql, ...changedValues, ...whereValues),
|
|
307
|
+
catch: (error) => new Error(`Failed to apply update on ${change.tableName}: ${String(error)}`),
|
|
308
|
+
}).pipe(effect_1.Effect.asVoid);
|
|
309
|
+
}))))));
|
|
310
|
+
const applyUpdateWithoutMask = (database, change) => applyDelete(database, {
|
|
311
|
+
...change,
|
|
312
|
+
changeType: types_1.CDCChangeType.Delete,
|
|
313
|
+
// oxlint-disable-next-line local-rules/no-null-undefined-option -- update-as-delete replay must clear fields absent on delete CDC rows.
|
|
314
|
+
after: null,
|
|
315
|
+
// oxlint-disable-next-line local-rules/no-null-undefined-option -- update-as-delete replay must clear fields absent on delete CDC rows.
|
|
316
|
+
updates: null,
|
|
317
|
+
}).pipe(effect_1.Effect.flatMap(() => applyInsert(database, {
|
|
318
|
+
...change,
|
|
319
|
+
changeType: types_1.CDCChangeType.Insert,
|
|
320
|
+
// oxlint-disable-next-line local-rules/no-null-undefined-option -- update-as-insert replay must clear fields absent on insert CDC rows.
|
|
321
|
+
before: null,
|
|
322
|
+
// oxlint-disable-next-line local-rules/no-null-undefined-option -- update-as-insert replay must clear fields absent on insert CDC rows.
|
|
323
|
+
updates: null,
|
|
324
|
+
})));
|
|
325
|
+
const applyCDC = (database, change) => {
|
|
326
|
+
switch (change.changeType) {
|
|
327
|
+
case types_1.CDCChangeType.Insert:
|
|
328
|
+
return change.tableName === "sqlite_schema"
|
|
329
|
+
? applySchemaInsert(database, change)
|
|
330
|
+
: applyInsert(database, change);
|
|
331
|
+
case types_1.CDCChangeType.Delete:
|
|
332
|
+
return change.tableName === "sqlite_schema"
|
|
333
|
+
? applySchemaDelete(database, change)
|
|
334
|
+
: applyDelete(database, change);
|
|
335
|
+
case types_1.CDCChangeType.Update:
|
|
336
|
+
return change.tableName === "sqlite_schema"
|
|
337
|
+
? applySchemaUpdate(database, change)
|
|
338
|
+
: // oxlint-disable-next-line local-rules/no-null-undefined-option -- updates without a mask are represented by NULL in the CDC stream.
|
|
339
|
+
change.updates === null
|
|
340
|
+
? applyUpdateWithoutMask(database, change)
|
|
341
|
+
: applyUpdateWithMask(database, change);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
exports.applyCDC = applyCDC;
|
|
345
|
+
const replayCDC = (database, changes) => {
|
|
346
|
+
if (changes.length === 0) {
|
|
347
|
+
return effect_1.Effect.void;
|
|
348
|
+
}
|
|
349
|
+
const replay = effect_1.Effect.gen(function* () {
|
|
350
|
+
yield* effect_1.Effect.tryPromise({
|
|
351
|
+
try: () => database.exec("BEGIN IMMEDIATE"),
|
|
352
|
+
catch: (error) => new Error(`Failed to begin CDC transaction: ${String(error)}`),
|
|
353
|
+
}).pipe(effect_1.Effect.asVoid);
|
|
354
|
+
const replayResult = yield* effect_1.Effect.either(effect_1.Effect.gen(function* () {
|
|
355
|
+
for (const change of changes) {
|
|
356
|
+
yield* (0, exports.applyCDC)(database, change).pipe(effect_1.Effect.annotateLogs({
|
|
357
|
+
changeId: String(change.changeId),
|
|
358
|
+
changeType: String(change.changeType),
|
|
359
|
+
tableName: change.tableName,
|
|
360
|
+
}), effect_1.Effect.tapError((error) => effect_1.Effect.logError("cdc:error").pipe(effect_1.Effect.annotateLogs({
|
|
361
|
+
changeId: String(change.changeId),
|
|
362
|
+
changeType: String(change.changeType),
|
|
363
|
+
tableName: change.tableName,
|
|
364
|
+
error: error.message,
|
|
365
|
+
}))));
|
|
366
|
+
}
|
|
367
|
+
}).pipe(effect_1.Effect.zipRight(effect_1.Effect.tryPromise({
|
|
368
|
+
try: () => database.exec("COMMIT"),
|
|
369
|
+
catch: (error) => new Error(`Failed to commit CDC transaction: ${String(error)}`),
|
|
370
|
+
}).pipe(effect_1.Effect.asVoid))));
|
|
371
|
+
if (replayResult._tag === "Right") {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const error = replayResult.left;
|
|
375
|
+
const rollbackResult = yield* effect_1.Effect.either(effect_1.Effect.tryPromise({
|
|
376
|
+
try: () => database.exec("ROLLBACK"),
|
|
377
|
+
catch: (rollbackError) => new Error(`Failed to rollback CDC transaction: ${String(rollbackError)}`),
|
|
378
|
+
}).pipe(effect_1.Effect.asVoid));
|
|
379
|
+
if (rollbackResult._tag === "Left") {
|
|
380
|
+
return yield* effect_1.Effect.fail(new ReplayError({
|
|
381
|
+
message: `Failed to replay CDC: ${error instanceof Error ? error.message : String(error)}. Rollback failed: ${rollbackResult.left.message}`,
|
|
382
|
+
cause: error,
|
|
383
|
+
}));
|
|
384
|
+
}
|
|
385
|
+
return yield* effect_1.Effect.fail(new ReplayError({
|
|
386
|
+
message: `Failed to replay CDC: ${error instanceof Error ? error.message : String(error)}`,
|
|
387
|
+
cause: error,
|
|
388
|
+
}));
|
|
389
|
+
});
|
|
390
|
+
return effect_1.Effect.acquireUseRelease(effect_1.Effect.tryPromise({
|
|
391
|
+
try: () => database.exec("PRAGMA capture_data_changes_conn('off')"),
|
|
392
|
+
catch: (error) => new Error(`Failed to disable CDC: ${String(error)}`),
|
|
393
|
+
}).pipe(effect_1.Effect.asVoid), () => replay, () => effect_1.Effect.tryPromise({
|
|
394
|
+
try: () => database.exec("PRAGMA capture_data_changes_conn('full')"),
|
|
395
|
+
catch: (error) => new Error(`Failed to enable CDC: ${String(error)}`),
|
|
396
|
+
}).pipe(effect_1.Effect.asVoid, effect_1.Effect.orDie)).pipe(effect_1.Effect.mapError((error) => error instanceof ReplayError
|
|
397
|
+
? error
|
|
398
|
+
: new ReplayError({
|
|
399
|
+
message: `Failed to replay CDC: ${error instanceof Error ? error.message : String(error)}`,
|
|
400
|
+
cause: error,
|
|
401
|
+
})));
|
|
402
|
+
};
|
|
403
|
+
exports.replayCDC = replayCDC;
|
|
404
|
+
//# sourceMappingURL=apply.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../../../../src/cdc/apply.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Database } from \"@tursodatabase/database\";\nimport { Data, Effect } from \"effect\";\n\nimport { CDCChangeType } from \"./types\";\nimport type { CDCRow, CDCValue } from \"./types\";\n\nexport class ReplayError extends Data.TaggedError(\"ReplayError\")<{\n\tmessage: string;\n\tcause: unknown;\n}> {}\n\ntype TableColumn = {\n\tname: string;\n\tpk: number;\n};\n\ntype SchemaRow = {\n\ttype: string;\n\tname: string;\n\ttblName: string;\n\trootpage: number;\n\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- sqlite_schema CDC can carry NULL SQL for implicit objects.\n\tsql: string | null;\n};\n\nconst textDecoder = new TextDecoder();\n\nconst readVarint = (bytes: Uint8Array, offset: number): readonly [number, number] => {\n\tlet value = 0n;\n\tfor (let index = 0; index < 9; index += 1) {\n\t\tconst cursor = offset + index;\n\t\tif (cursor >= bytes.length) {\n\t\t\tthrow new Error(\"Unexpected end of CDC record\");\n\t\t}\n\t\tconst byte = bytes[cursor];\n\t\tif (index === 8) {\n\t\t\tvalue = (value << 8n) | BigInt(byte);\n\t\t\tconst number = Number(value);\n\t\t\tif (!Number.isSafeInteger(number)) {\n\t\t\t\tthrow new Error(\"CDC varint is too large\");\n\t\t\t}\n\t\t\treturn [number, cursor + 1];\n\t\t}\n\t\tvalue = (value << 7n) | BigInt(byte & 0x7f);\n\t\tif ((byte & 0x80) === 0) {\n\t\t\tconst number = Number(value);\n\t\t\tif (!Number.isSafeInteger(number)) {\n\t\t\t\tthrow new Error(\"CDC varint is too large\");\n\t\t\t}\n\t\t\treturn [number, cursor + 1];\n\t\t}\n\t}\n\tthrow new Error(\"Invalid CDC varint\");\n};\n\nconst readInteger = (bytes: Uint8Array, offset: number, length: number): number => {\n\tlet value = 0n;\n\tfor (let index = 0; index < length; index += 1) {\n\t\tconst cursor = offset + index;\n\t\tif (cursor >= bytes.length) {\n\t\t\tthrow new Error(\"Unexpected end of CDC integer\");\n\t\t}\n\t\tvalue = (value << 8n) | BigInt(bytes[cursor]);\n\t}\n\tconst bits = BigInt(length * 8);\n\tconst signBit = 1n << (bits - 1n);\n\tconst signed = (value & signBit) === 0n ? value : value - (1n << bits);\n\tconst number = Number(signed);\n\tif (!Number.isSafeInteger(number)) {\n\t\tthrow new Error(\"CDC integer is outside JS safe integer range\");\n\t}\n\treturn number;\n};\n\nconst decodeValue = (\n\tbytes: Uint8Array,\n\toffset: number,\n\tserialType: number,\n): readonly [CDCValue, number] => {\n\tswitch (serialType) {\n\t\tcase 0:\n\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- SQLite record serial type 0 represents SQL NULL.\n\t\t\treturn [null, offset];\n\t\tcase 1:\n\t\t\treturn [readInteger(bytes, offset, 1), offset + 1];\n\t\tcase 2:\n\t\t\treturn [readInteger(bytes, offset, 2), offset + 2];\n\t\tcase 3:\n\t\t\treturn [readInteger(bytes, offset, 3), offset + 3];\n\t\tcase 4:\n\t\t\treturn [readInteger(bytes, offset, 4), offset + 4];\n\t\tcase 5:\n\t\t\treturn [readInteger(bytes, offset, 6), offset + 6];\n\t\tcase 6:\n\t\t\treturn [readInteger(bytes, offset, 8), offset + 8];\n\t\tcase 7:\n\t\t\tif (offset + 8 > bytes.length) {\n\t\t\t\tthrow new Error(\"Unexpected end of CDC float\");\n\t\t\t}\n\t\t\treturn [\n\t\t\t\tnew DataView(bytes.buffer, bytes.byteOffset + offset, 8).getFloat64(0, false),\n\t\t\t\toffset + 8,\n\t\t\t];\n\t\tcase 8:\n\t\t\treturn [0, offset];\n\t\tcase 9:\n\t\t\treturn [1, offset];\n\t\tcase 10:\n\t\tcase 11:\n\t\t\tthrow new Error(`Unsupported CDC serial type ${serialType}`);\n\t\tdefault: {\n\t\t\tconst isBlob = serialType % 2 === 0;\n\t\t\tconst length = isBlob ? (serialType - 12) / 2 : (serialType - 13) / 2;\n\t\t\tif (length < 0 || offset + length > bytes.length) {\n\t\t\t\tthrow new Error(\"Unexpected end of CDC payload\");\n\t\t\t}\n\t\t\tconst value = bytes.slice(offset, offset + length);\n\t\t\treturn [isBlob ? value : textDecoder.decode(value), offset + length];\n\t\t}\n\t}\n};\n\nconst decodeRecord = (bytes: Uint8Array): Effect.Effect<readonly CDCValue[], Error> =>\n\tEffect.try(() => {\n\t\tconst [headerSize, start] = readVarint(bytes, 0);\n\t\tif (headerSize > bytes.length) {\n\t\t\tthrow new Error(\"CDC record header exceeds payload length\");\n\t\t}\n\t\tconst serialTypes: number[] = [];\n\t\tlet headerOffset = start;\n\t\twhile (headerOffset < headerSize) {\n\t\t\tconst [serialType, nextOffset] = readVarint(bytes, headerOffset);\n\t\t\tserialTypes.push(serialType);\n\t\t\theaderOffset = nextOffset;\n\t\t}\n\t\tconst values: CDCValue[] = [];\n\t\tlet valueOffset = headerSize;\n\t\tfor (const serialType of serialTypes) {\n\t\t\tconst [value, nextOffset] = decodeValue(bytes, valueOffset, serialType);\n\t\t\tvalues.push(value);\n\t\t\tvalueOffset = nextOffset;\n\t\t}\n\t\treturn values;\n\t});\n\nconst decodeSchemaRow = (bytes: Uint8Array): Effect.Effect<SchemaRow, Error> =>\n\tdecodeRecord(bytes).pipe(\n\t\tEffect.flatMap((values) =>\n\t\t\tvalues.length === 5 &&\n\t\t\ttypeof values[0] === \"string\" &&\n\t\t\ttypeof values[1] === \"string\" &&\n\t\t\ttypeof values[2] === \"string\" &&\n\t\t\ttypeof values[3] === \"number\" &&\n\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- sqlite_schema CDC can carry NULL SQL for implicit objects.\n\t\t\t(typeof values[4] === \"string\" || values[4] === null)\n\t\t\t\t? Effect.succeed({\n\t\t\t\t\t\ttype: values[0],\n\t\t\t\t\t\tname: values[1],\n\t\t\t\t\t\ttblName: values[2],\n\t\t\t\t\t\trootpage: values[3],\n\t\t\t\t\t\tsql: values[4],\n\t\t\t\t\t})\n\t\t\t\t: Effect.fail(new Error(\"Invalid sqlite_schema CDC record\")),\n\t\t),\n\t);\n\nconst parseUpdatedColumns = (\n\tcolumns: readonly TableColumn[],\n\tvalues: readonly CDCValue[],\n): Effect.Effect<readonly [readonly string[], readonly CDCValue[]], Error> => {\n\tif (values.length % 2 !== 0) {\n\t\treturn Effect.fail(new Error(\"Invalid CDC updates record\"));\n\t}\n\tconst columnCount = values.length / 2;\n\tconst recordColumns = columns.slice(0, columnCount);\n\tconst changedColumns: string[] = [];\n\tconst changedValues: CDCValue[] = [];\n\tfor (let index = 0; index < columnCount; index += 1) {\n\t\tconst flag = values[index];\n\t\tif (flag !== 0 && flag !== 1) {\n\t\t\treturn Effect.fail(new Error(`Invalid CDC update flag at column ${index}`));\n\t\t}\n\t\tif (flag === 0) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (index >= recordColumns.length) {\n\t\t\treturn Effect.fail(new Error(`CDC updates reference missing column ${index}`));\n\t\t}\n\t\tchangedColumns.push(recordColumns[index].name);\n\t\tchangedValues.push(values[columnCount + index]);\n\t}\n\treturn Effect.succeed([changedColumns, changedValues]);\n};\n\nconst getTableColumns = (\n\tdatabase: Database,\n\ttableName: string,\n): Effect.Effect<readonly TableColumn[], Error> =>\n\tEffect.tryPromise(\n\t\t() =>\n\t\t\tdatabase.all(`PRAGMA table_info(\"${tableName.replaceAll('\"', '\"\"')}\")`) as Promise<\n\t\t\t\treadonly TableColumn[]\n\t\t\t>,\n\t).pipe(\n\t\tEffect.flatMap((columns) =>\n\t\t\tcolumns.length > 0\n\t\t\t\t? Effect.succeed(columns)\n\t\t\t\t: Effect.fail(new Error(`Table ${tableName} does not exist`)),\n\t\t),\n\t\tEffect.tapError((error) =>\n\t\t\tEffect.logError(\"cdc:get-table-columns\").pipe(\n\t\t\t\tEffect.annotateLogs({ table: tableName, error: error.message }),\n\t\t\t),\n\t\t),\n\t);\n\nconst applySchemaInsert = (\n\tdatabase: Database,\n\tchange: Extract<CDCRow, { changeType: typeof CDCChangeType.Insert }>,\n): Effect.Effect<void, Error> =>\n\tdecodeSchemaRow(change.after).pipe(\n\t\tEffect.flatMap((row) => {\n\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- sqlite_schema rows may have NULL SQL and should be skipped.\n\t\t\tif (row.sql === null) {\n\t\t\t\treturn Effect.void;\n\t\t\t}\n\t\t\tconst sql = row.sql.replace(\n\t\t\t\t/^(CREATE\\s+(?:UNIQUE\\s+)?(?:TABLE|INDEX|TRIGGER|VIEW|MATERIALIZED\\s+VIEW)\\s+)(?!IF\\s+NOT\\s+EXISTS\\b)/i,\n\t\t\t\t\"$1IF NOT EXISTS \",\n\t\t\t);\n\t\t\treturn Effect.tryPromise({\n\t\t\t\ttry: () => database.run(sql),\n\t\t\t\tcatch: (error) =>\n\t\t\t\t\tnew Error(`Failed to apply schema insert: ${sql}: ${String(error)}`),\n\t\t\t}).pipe(Effect.asVoid);\n\t\t}),\n\t);\n\nconst applySchemaDelete = (\n\tdatabase: Database,\n\tchange: Extract<CDCRow, { changeType: typeof CDCChangeType.Delete }>,\n): Effect.Effect<void, Error> =>\n\tdecodeSchemaRow(change.before).pipe(\n\t\tEffect.flatMap((row) => {\n\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- sqlite_schema rows may have NULL SQL and should be skipped.\n\t\t\tif (row.sql === null || row.name.startsWith(\"sqlite_\")) {\n\t\t\t\treturn Effect.void;\n\t\t\t}\n\t\t\tconst dropType =\n\t\t\t\trow.type === \"table\"\n\t\t\t\t\t? \"TABLE\"\n\t\t\t\t\t: row.type === \"index\"\n\t\t\t\t\t\t? \"INDEX\"\n\t\t\t\t\t\t: row.type === \"view\"\n\t\t\t\t\t\t\t? \"VIEW\"\n\t\t\t\t\t\t\t: row.type === \"trigger\"\n\t\t\t\t\t\t\t\t? \"TRIGGER\"\n\t\t\t\t\t\t\t\t: \"\";\n\t\t\tif (dropType === \"\") {\n\t\t\t\treturn Effect.fail(new Error(`Unsupported sqlite_schema object type ${row.type}`));\n\t\t\t}\n\t\t\tconst sql = `DROP ${dropType} IF EXISTS \"${row.name.replaceAll('\"', '\"\"')}\"`;\n\t\t\treturn Effect.tryPromise({\n\t\t\t\ttry: () => database.run(sql),\n\t\t\t\tcatch: (error) =>\n\t\t\t\t\tnew Error(`Failed to apply schema delete: ${sql}: ${String(error)}`),\n\t\t\t}).pipe(Effect.asVoid);\n\t\t}),\n\t);\n\nconst applySchemaUpdate = (\n\tdatabase: Database,\n\tchange: Extract<CDCRow, { changeType: typeof CDCChangeType.Update }>,\n): Effect.Effect<void, Error> =>\n\tdecodeRecord(change.updates).pipe(\n\t\tEffect.flatMap((values) => {\n\t\t\tif (values.length !== 10) {\n\t\t\t\treturn Effect.fail(new Error(\"Invalid sqlite_schema CDC update record\"));\n\t\t\t}\n\t\t\tconst ddl = values[9];\n\t\t\tif (typeof ddl !== \"string\") {\n\t\t\t\treturn Effect.fail(new Error(\"Invalid sqlite_schema DDL update statement\"));\n\t\t\t}\n\t\t\tconst match = ddl.match(\n\t\t\t\t/^ALTER\\s+TABLE\\s+(\"(?:[^\"]|\"\")+\"|`[^`]+`|\\[[^\\]]+\\]|\\S+)\\s+ADD\\s+COLUMN\\s+(\"(?:[^\"]|\"\")+\"|`[^`]+`|\\[[^\\]]+\\]|\\S+)/i,\n\t\t\t);\n\t\t\tif (!match) {\n\t\t\t\treturn Effect.tryPromise({\n\t\t\t\t\ttry: () => database.run(ddl),\n\t\t\t\t\tcatch: (error) =>\n\t\t\t\t\t\tnew Error(`Failed to apply schema update: ${ddl}: ${String(error)}`),\n\t\t\t\t}).pipe(Effect.asVoid);\n\t\t\t}\n\t\t\tconst tableName =\n\t\t\t\tmatch[1].startsWith('\"') && match[1].endsWith('\"')\n\t\t\t\t\t? match[1].slice(1, -1).replaceAll('\"\"', '\"')\n\t\t\t\t\t: match[1].startsWith(\"`\") && match[1].endsWith(\"`\")\n\t\t\t\t\t\t? match[1].slice(1, -1)\n\t\t\t\t\t\t: match[1].startsWith(\"[\") && match[1].endsWith(\"]\")\n\t\t\t\t\t\t\t? match[1].slice(1, -1)\n\t\t\t\t\t\t\t: match[1];\n\t\t\tconst columnName =\n\t\t\t\tmatch[2].startsWith('\"') && match[2].endsWith('\"')\n\t\t\t\t\t? match[2].slice(1, -1).replaceAll('\"\"', '\"')\n\t\t\t\t\t: match[2].startsWith(\"`\") && match[2].endsWith(\"`\")\n\t\t\t\t\t\t? match[2].slice(1, -1)\n\t\t\t\t\t\t: match[2].startsWith(\"[\") && match[2].endsWith(\"]\")\n\t\t\t\t\t\t\t? match[2].slice(1, -1)\n\t\t\t\t\t\t\t: match[2];\n\t\t\treturn getTableColumns(database, tableName).pipe(\n\t\t\t\tEffect.catchAll(() => Effect.succeed([] as const)),\n\t\t\t\tEffect.flatMap((columns) =>\n\t\t\t\t\tcolumns.some((column) => column.name === columnName)\n\t\t\t\t\t\t? Effect.void\n\t\t\t\t\t\t: Effect.tryPromise({\n\t\t\t\t\t\t\t\ttry: () => database.run(ddl),\n\t\t\t\t\t\t\t\tcatch: (error) =>\n\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t`Failed to apply schema update: ${ddl}: ${String(error)}`,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t}).pipe(Effect.asVoid),\n\t\t\t\t),\n\t\t\t);\n\t\t}),\n\t);\n\nconst applyInsert = (\n\tdatabase: Database,\n\tchange: Extract<CDCRow, { changeType: typeof CDCChangeType.Insert }>,\n): Effect.Effect<void, Error> =>\n\tgetTableColumns(database, change.tableName).pipe(\n\t\tEffect.flatMap((columns) =>\n\t\t\tdecodeRecord(change.after).pipe(\n\t\t\t\tEffect.flatMap((values) => {\n\t\t\t\t\tconst recordColumns = columns.slice(0, values.length);\n\t\t\t\t\tif (recordColumns.length !== values.length) {\n\t\t\t\t\t\treturn Effect.fail(\n\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t`CDC insert for ${change.tableName} has more columns than target table`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tconst pkColumns = columns\n\t\t\t\t\t\t.filter((column) => column.pk > 0)\n\t\t\t\t\t\t.sort((left, right) => left.pk - right.pk);\n\t\t\t\t\tconst columnSql = recordColumns\n\t\t\t\t\t\t.map((column) => `\"${column.name.replaceAll('\"', '\"\"')}\"`)\n\t\t\t\t\t\t.join(\", \");\n\t\t\t\t\tconst placeholders = recordColumns.map(() => \"?\").join(\", \");\n\t\t\t\t\tconst conflictSql =\n\t\t\t\t\t\tpkColumns.length === 0\n\t\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t\t: ` ON CONFLICT(${pkColumns.map((column) => `\"${column.name.replaceAll('\"', '\"\"')}\"`).join(\", \")}) DO UPDATE SET ${recordColumns.map((column) => `\"${column.name.replaceAll('\"', '\"\"')}\" = excluded.\"${column.name.replaceAll('\"', '\"\"')}\"`).join(\", \")}`;\n\t\t\t\t\tconst sql = `INSERT INTO \"${change.tableName.replaceAll('\"', '\"\"')}\" (${columnSql}) VALUES (${placeholders})${conflictSql}`;\n\t\t\t\t\treturn Effect.tryPromise({\n\t\t\t\t\t\ttry: () => database.run(sql, ...values),\n\t\t\t\t\t\tcatch: (error) =>\n\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t`Failed to apply insert to ${change.tableName}: ${String(error)}`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t}).pipe(Effect.asVoid);\n\t\t\t\t}),\n\t\t\t),\n\t\t),\n\t);\n\nconst applyDelete = (\n\tdatabase: Database,\n\tchange: Extract<CDCRow, { changeType: typeof CDCChangeType.Delete }>,\n): Effect.Effect<void, Error> =>\n\tgetTableColumns(database, change.tableName).pipe(\n\t\tEffect.flatMap((columns) =>\n\t\t\tdecodeRecord(change.before).pipe(\n\t\t\t\tEffect.flatMap((values) => {\n\t\t\t\t\tconst pkColumns = columns\n\t\t\t\t\t\t.filter((column) => column.pk > 0)\n\t\t\t\t\t\t.sort((left, right) => left.pk - right.pk);\n\t\t\t\t\tif (pkColumns.length === 0) {\n\t\t\t\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- rowid-free deletes need a missing CDC id check.\n\t\t\t\t\t\treturn change.id === null\n\t\t\t\t\t\t\t? Effect.fail(\n\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t`Invalid CDC change ${change.changeId}: table ${change.tableName} has no primary key or rowid`,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t: Effect.tryPromise({\n\t\t\t\t\t\t\t\t\ttry: () =>\n\t\t\t\t\t\t\t\t\t\tdatabase.run(\n\t\t\t\t\t\t\t\t\t\t\t`DELETE FROM \"${change.tableName.replaceAll('\"', '\"\"')}\" WHERE rowid = ?`,\n\t\t\t\t\t\t\t\t\t\t\tchange.id,\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\tcatch: (error) =>\n\t\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t\t`Failed to apply delete by rowid on ${change.tableName}: ${String(error)}`,\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t}).pipe(Effect.asVoid);\n\t\t\t\t\t}\n\t\t\t\t\tconst whereValues = pkColumns.map((column) => {\n\t\t\t\t\t\tconst index = columns.findIndex((item) => item.name === column.name);\n\t\t\t\t\t\tif (index < 0 || index >= values.length) {\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t`Missing primary key column ${column.name} in CDC delete for ${change.tableName}`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn values[index];\n\t\t\t\t\t});\n\t\t\t\t\tconst sql = `DELETE FROM \"${change.tableName.replaceAll('\"', '\"\"')}\" WHERE ${pkColumns.map((column) => `\"${column.name.replaceAll('\"', '\"\"')}\" = ?`).join(\" AND \")}`;\n\t\t\t\t\treturn Effect.tryPromise({\n\t\t\t\t\t\ttry: () => database.run(sql, ...whereValues),\n\t\t\t\t\t\tcatch: (error) =>\n\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t`Failed to apply delete on ${change.tableName}: ${String(error)}`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t}).pipe(Effect.asVoid);\n\t\t\t\t}),\n\t\t\t),\n\t\t),\n\t);\n\nconst applyUpdateWithMask = (\n\tdatabase: Database,\n\tchange: Extract<CDCRow, { changeType: typeof CDCChangeType.Update }>,\n): Effect.Effect<void, Error> =>\n\tgetTableColumns(database, change.tableName).pipe(\n\t\tEffect.flatMap((columns) =>\n\t\t\tEffect.all({\n\t\t\t\tafter: decodeRecord(change.after),\n\t\t\t\tupdates: decodeRecord(change.updates),\n\t\t\t}).pipe(\n\t\t\t\tEffect.flatMap(({ after, updates }) =>\n\t\t\t\t\tparseUpdatedColumns(columns, updates).pipe(\n\t\t\t\t\t\tEffect.flatMap(([changedColumns, changedValues]) => {\n\t\t\t\t\t\t\tif (changedColumns.length === 0) {\n\t\t\t\t\t\t\t\treturn Effect.void;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst pkColumns = columns\n\t\t\t\t\t\t\t\t.filter((column) => column.pk > 0)\n\t\t\t\t\t\t\t\t.sort((left, right) => left.pk - right.pk);\n\t\t\t\t\t\t\tif (pkColumns.length === 0) {\n\t\t\t\t\t\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- rowid-free updates need a missing CDC id check.\n\t\t\t\t\t\t\t\treturn change.id === null\n\t\t\t\t\t\t\t\t\t? Effect.fail(\n\t\t\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t\t\t`Invalid CDC change ${change.changeId}: table ${change.tableName} has no primary key or rowid`,\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t: Effect.tryPromise({\n\t\t\t\t\t\t\t\t\t\t\ttry: () =>\n\t\t\t\t\t\t\t\t\t\t\t\tdatabase.run(\n\t\t\t\t\t\t\t\t\t\t\t\t\t`UPDATE \"${change.tableName.replaceAll('\"', '\"\"')}\" SET ${changedColumns.map((column) => `\"${column.replaceAll('\"', '\"\"')}\" = ?`).join(\", \")} WHERE rowid = ?`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t...changedValues,\n\t\t\t\t\t\t\t\t\t\t\t\t\tchange.id,\n\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\tcatch: (error) =>\n\t\t\t\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t\t\t\t`Failed to apply update by rowid on ${change.tableName}: ${String(error)}`,\n\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t}).pipe(Effect.asVoid);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst whereValues = pkColumns.map((column) => {\n\t\t\t\t\t\t\t\tconst index = columns.findIndex(\n\t\t\t\t\t\t\t\t\t(item) => item.name === column.name,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tif (index < 0 || index >= after.length) {\n\t\t\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t\t\t`Missing primary key column ${column.name} in CDC update for ${change.tableName}`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn after[index];\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tconst sql = `UPDATE \"${change.tableName.replaceAll('\"', '\"\"')}\" SET ${changedColumns.map((column) => `\"${column.replaceAll('\"', '\"\"')}\" = ?`).join(\", \")} WHERE ${pkColumns.map((column) => `\"${column.name.replaceAll('\"', '\"\"')}\" = ?`).join(\" AND \")}`;\n\t\t\t\t\t\t\treturn Effect.tryPromise({\n\t\t\t\t\t\t\t\ttry: () => database.run(sql, ...changedValues, ...whereValues),\n\t\t\t\t\t\t\t\tcatch: (error) =>\n\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t`Failed to apply update on ${change.tableName}: ${String(error)}`,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t}).pipe(Effect.asVoid);\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t),\n\t\t),\n\t);\n\nconst applyUpdateWithoutMask = (\n\tdatabase: Database,\n\tchange: Extract<CDCRow, { changeType: typeof CDCChangeType.Update }>,\n): Effect.Effect<void, Error> =>\n\tapplyDelete(database, {\n\t\t...change,\n\t\tchangeType: CDCChangeType.Delete,\n\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- update-as-delete replay must clear fields absent on delete CDC rows.\n\t\tafter: null,\n\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- update-as-delete replay must clear fields absent on delete CDC rows.\n\t\tupdates: null,\n\t}).pipe(\n\t\tEffect.flatMap(() =>\n\t\t\tapplyInsert(database, {\n\t\t\t\t...change,\n\t\t\t\tchangeType: CDCChangeType.Insert,\n\t\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- update-as-insert replay must clear fields absent on insert CDC rows.\n\t\t\t\tbefore: null,\n\t\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- update-as-insert replay must clear fields absent on insert CDC rows.\n\t\t\t\tupdates: null,\n\t\t\t}),\n\t\t),\n\t);\n\nexport const applyCDC = (database: Database, change: CDCRow): Effect.Effect<void, Error> => {\n\tswitch (change.changeType) {\n\t\tcase CDCChangeType.Insert:\n\t\t\treturn change.tableName === \"sqlite_schema\"\n\t\t\t\t? applySchemaInsert(database, change)\n\t\t\t\t: applyInsert(database, change);\n\t\tcase CDCChangeType.Delete:\n\t\t\treturn change.tableName === \"sqlite_schema\"\n\t\t\t\t? applySchemaDelete(database, change)\n\t\t\t\t: applyDelete(database, change);\n\t\tcase CDCChangeType.Update:\n\t\t\treturn change.tableName === \"sqlite_schema\"\n\t\t\t\t? applySchemaUpdate(database, change)\n\t\t\t\t: // oxlint-disable-next-line local-rules/no-null-undefined-option -- updates without a mask are represented by NULL in the CDC stream.\n\t\t\t\t\tchange.updates === null\n\t\t\t\t\t? applyUpdateWithoutMask(database, change)\n\t\t\t\t\t: applyUpdateWithMask(database, change);\n\t}\n};\n\nexport const replayCDC = (\n\tdatabase: Database,\n\tchanges: readonly CDCRow[],\n): Effect.Effect<void, ReplayError> => {\n\tif (changes.length === 0) {\n\t\treturn Effect.void;\n\t}\n\n\tconst replay = Effect.gen(function* () {\n\t\tyield* Effect.tryPromise({\n\t\t\ttry: () => database.exec(\"BEGIN IMMEDIATE\"),\n\t\t\tcatch: (error) => new Error(`Failed to begin CDC transaction: ${String(error)}`),\n\t\t}).pipe(Effect.asVoid);\n\n\t\tconst replayResult = yield* Effect.either(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tfor (const change of changes) {\n\t\t\t\t\tyield* applyCDC(database, change).pipe(\n\t\t\t\t\t\tEffect.annotateLogs({\n\t\t\t\t\t\t\tchangeId: String(change.changeId),\n\t\t\t\t\t\t\tchangeType: String(change.changeType),\n\t\t\t\t\t\t\ttableName: change.tableName,\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tEffect.tapError((error) =>\n\t\t\t\t\t\t\tEffect.logError(\"cdc:error\").pipe(\n\t\t\t\t\t\t\t\tEffect.annotateLogs({\n\t\t\t\t\t\t\t\t\tchangeId: String(change.changeId),\n\t\t\t\t\t\t\t\t\tchangeType: String(change.changeType),\n\t\t\t\t\t\t\t\t\ttableName: change.tableName,\n\t\t\t\t\t\t\t\t\terror: error.message,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}).pipe(\n\t\t\t\tEffect.zipRight(\n\t\t\t\t\tEffect.tryPromise({\n\t\t\t\t\t\ttry: () => database.exec(\"COMMIT\"),\n\t\t\t\t\t\tcatch: (error) =>\n\t\t\t\t\t\t\tnew Error(`Failed to commit CDC transaction: ${String(error)}`),\n\t\t\t\t\t}).pipe(Effect.asVoid),\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\n\t\tif (replayResult._tag === \"Right\") {\n\t\t\treturn;\n\t\t}\n\n\t\tconst error = replayResult.left;\n\t\tconst rollbackResult = yield* Effect.either(\n\t\t\tEffect.tryPromise({\n\t\t\t\ttry: () => database.exec(\"ROLLBACK\"),\n\t\t\t\tcatch: (rollbackError) =>\n\t\t\t\t\tnew Error(`Failed to rollback CDC transaction: ${String(rollbackError)}`),\n\t\t\t}).pipe(Effect.asVoid),\n\t\t);\n\n\t\tif (rollbackResult._tag === \"Left\") {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew ReplayError({\n\t\t\t\t\tmessage: `Failed to replay CDC: ${error instanceof Error ? error.message : String(error)}. Rollback failed: ${rollbackResult.left.message}`,\n\t\t\t\t\tcause: error,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\treturn yield* Effect.fail(\n\t\t\tnew ReplayError({\n\t\t\t\tmessage: `Failed to replay CDC: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\tcause: error,\n\t\t\t}),\n\t\t);\n\t});\n\n\treturn Effect.acquireUseRelease(\n\t\tEffect.tryPromise({\n\t\t\ttry: () => database.exec(\"PRAGMA capture_data_changes_conn('off')\"),\n\t\t\tcatch: (error) => new Error(`Failed to disable CDC: ${String(error)}`),\n\t\t}).pipe(Effect.asVoid),\n\t\t() => replay,\n\t\t() =>\n\t\t\tEffect.tryPromise({\n\t\t\t\ttry: () => database.exec(\"PRAGMA capture_data_changes_conn('full')\"),\n\t\t\t\tcatch: (error) => new Error(`Failed to enable CDC: ${String(error)}`),\n\t\t\t}).pipe(Effect.asVoid, Effect.orDie),\n\t).pipe(\n\t\tEffect.mapError((error) =>\n\t\t\terror instanceof ReplayError\n\t\t\t\t? error\n\t\t\t\t: new ReplayError({\n\t\t\t\t\t\tmessage: `Failed to replay CDC: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t\t\tcause: error,\n\t\t\t\t\t}),\n\t\t),\n\t);\n};"]}
|