@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.
Files changed (117) hide show
  1. package/cjs/effect.js +18 -0
  2. package/cjs/effect.js.map +1 -0
  3. package/cjs/index.js +40 -0
  4. package/cjs/index.js.map +1 -0
  5. package/cjs/package.json +3 -0
  6. package/cjs/src/batches.js +39 -0
  7. package/cjs/src/batches.js.map +1 -0
  8. package/cjs/src/cdc/apply.js +404 -0
  9. package/cjs/src/cdc/apply.js.map +1 -0
  10. package/cjs/src/cdc/extract.js +38 -0
  11. package/cjs/src/cdc/extract.js.map +1 -0
  12. package/cjs/src/cdc/protobuf.js +135 -0
  13. package/cjs/src/cdc/protobuf.js.map +1 -0
  14. package/cjs/src/cdc/testUtils.js +49 -0
  15. package/cjs/src/cdc/testUtils.js.map +1 -0
  16. package/cjs/src/cdc/truncate.js +7 -0
  17. package/cjs/src/cdc/truncate.js.map +1 -0
  18. package/cjs/src/cdc/types.js +10 -0
  19. package/cjs/src/cdc/types.js.map +1 -0
  20. package/cjs/src/cdc/withoutCDC.js +7 -0
  21. package/cjs/src/cdc/withoutCDC.js.map +1 -0
  22. package/cjs/src/connection.js +128 -0
  23. package/cjs/src/connection.js.map +1 -0
  24. package/cjs/src/contexts.js +11 -0
  25. package/cjs/src/contexts.js.map +1 -0
  26. package/cjs/src/index.js +30 -0
  27. package/cjs/src/index.js.map +1 -0
  28. package/cjs/src/kv/fileKV.js +131 -0
  29. package/cjs/src/kv/fileKV.js.map +1 -0
  30. package/cjs/src/kv/kv.js +8 -0
  31. package/cjs/src/kv/kv.js.map +1 -0
  32. package/cjs/src/kv/memoryKV.js +16 -0
  33. package/cjs/src/kv/memoryKV.js.map +1 -0
  34. package/cjs/src/kv/s3KV.js +283 -0
  35. package/cjs/src/kv/s3KV.js.map +1 -0
  36. package/cjs/src/kv/syncFiles.js +32 -0
  37. package/cjs/src/kv/syncFiles.js.map +1 -0
  38. package/cjs/src/pull.js +101 -0
  39. package/cjs/src/pull.js.map +1 -0
  40. package/cjs/src/push.js +58 -0
  41. package/cjs/src/push.js.map +1 -0
  42. package/cjs/src/storage.js +41 -0
  43. package/cjs/src/storage.js.map +1 -0
  44. package/cjs/src/types.js +3 -0
  45. package/cjs/src/types.js.map +1 -0
  46. package/cjs/src/wrapDatabase.js +80 -0
  47. package/cjs/src/wrapDatabase.js.map +1 -0
  48. package/dts/effect.d.ts +1 -0
  49. package/dts/index.d.ts +16 -0
  50. package/dts/src/batches.d.ts +10 -0
  51. package/dts/src/cdc/apply.d.ts +14 -0
  52. package/dts/src/cdc/extract.d.ts +8 -0
  53. package/dts/src/cdc/protobuf.d.ts +3 -0
  54. package/dts/src/cdc/testUtils.d.ts +19 -0
  55. package/dts/src/cdc/truncate.d.ts +6 -0
  56. package/dts/src/cdc/types.d.ts +35 -0
  57. package/dts/src/cdc/withoutCDC.d.ts +3 -0
  58. package/dts/src/connection.d.ts +5 -0
  59. package/dts/src/contexts.d.ts +22 -0
  60. package/dts/src/index.d.ts +12 -0
  61. package/dts/src/kv/fileKV.d.ts +5 -0
  62. package/dts/src/kv/kv.d.ts +42 -0
  63. package/dts/src/kv/memoryKV.d.ts +5 -0
  64. package/dts/src/kv/s3KV.d.ts +4 -0
  65. package/dts/src/kv/syncFiles.d.ts +4 -0
  66. package/dts/src/pull.d.ts +8 -0
  67. package/dts/src/push.d.ts +4 -0
  68. package/dts/src/storage.d.ts +22 -0
  69. package/dts/src/types.d.ts +38 -0
  70. package/dts/src/wrapDatabase.d.ts +1 -0
  71. package/esm/effect.js +2 -0
  72. package/esm/effect.js.map +1 -0
  73. package/esm/index.js +22 -0
  74. package/esm/index.js.map +1 -0
  75. package/esm/src/batches.js +34 -0
  76. package/esm/src/batches.js.map +1 -0
  77. package/esm/src/cdc/apply.js +398 -0
  78. package/esm/src/cdc/apply.js.map +1 -0
  79. package/esm/src/cdc/extract.js +33 -0
  80. package/esm/src/cdc/extract.js.map +1 -0
  81. package/esm/src/cdc/protobuf.js +127 -0
  82. package/esm/src/cdc/protobuf.js.map +1 -0
  83. package/esm/src/cdc/testUtils.js +42 -0
  84. package/esm/src/cdc/testUtils.js.map +1 -0
  85. package/esm/src/cdc/truncate.js +3 -0
  86. package/esm/src/cdc/truncate.js.map +1 -0
  87. package/esm/src/cdc/types.js +7 -0
  88. package/esm/src/cdc/types.js.map +1 -0
  89. package/esm/src/cdc/withoutCDC.js +3 -0
  90. package/esm/src/cdc/withoutCDC.js.map +1 -0
  91. package/esm/src/connection.js +123 -0
  92. package/esm/src/connection.js.map +1 -0
  93. package/esm/src/contexts.js +6 -0
  94. package/esm/src/contexts.js.map +1 -0
  95. package/esm/src/index.js +12 -0
  96. package/esm/src/index.js.map +1 -0
  97. package/esm/src/kv/fileKV.js +127 -0
  98. package/esm/src/kv/fileKV.js.map +1 -0
  99. package/esm/src/kv/kv.js +4 -0
  100. package/esm/src/kv/kv.js.map +1 -0
  101. package/esm/src/kv/memoryKV.js +12 -0
  102. package/esm/src/kv/memoryKV.js.map +1 -0
  103. package/esm/src/kv/s3KV.js +279 -0
  104. package/esm/src/kv/s3KV.js.map +1 -0
  105. package/esm/src/kv/syncFiles.js +27 -0
  106. package/esm/src/kv/syncFiles.js.map +1 -0
  107. package/esm/src/pull.js +97 -0
  108. package/esm/src/pull.js.map +1 -0
  109. package/esm/src/push.js +54 -0
  110. package/esm/src/push.js.map +1 -0
  111. package/esm/src/storage.js +26 -0
  112. package/esm/src/storage.js.map +1 -0
  113. package/esm/src/types.js +2 -0
  114. package/esm/src/types.js.map +1 -0
  115. package/esm/src/wrapDatabase.js +76 -0
  116. package/esm/src/wrapDatabase.js.map +1 -0
  117. package/package.json +35 -0
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pull = void 0;
4
+ const effect_1 = require("effect");
5
+ const uuid_1 = require("uuid");
6
+ const batches_1 = require("./batches");
7
+ const extract_1 = require("./cdc/extract");
8
+ const truncate_1 = require("./cdc/truncate");
9
+ const contexts_1 = require("./contexts");
10
+ const syncFiles_1 = require("./kv/syncFiles");
11
+ const storage_1 = require("./storage");
12
+ const pull = () => effect_1.Effect.gen(function* () {
13
+ const localKV = yield* storage_1.LocalKV;
14
+ const remoteKV = yield* storage_1.RemoteKV;
15
+ const { dbName } = yield* contexts_1.ConnectionConfig;
16
+ const state = yield* contexts_1.ConnectionState;
17
+ const tursoConnection = { current: yield* state.getConnection };
18
+ yield* effect_1.Effect.logDebug("s3qlite.pull.start").pipe(effect_1.Effect.annotateLogs({ dbName }));
19
+ const localHead = yield* (0, storage_1.getJson)(localKV, (0, storage_1.headKey)(dbName)).pipe(effect_1.Effect.flatMap(effect_1.Option.match({
20
+ onNone: () => effect_1.Effect.die(new Error("Expected local stored head to exist")),
21
+ onSome: effect_1.Effect.succeed,
22
+ })));
23
+ const remoteHeadOption = yield* remoteKV.getIfChanged((0, storage_1.headKey)(dbName), localHead.value.remoteEtag);
24
+ // no changes detected, skip pull
25
+ if (effect_1.Option.isNone(remoteHeadOption)) {
26
+ yield* effect_1.Effect.logDebug("s3qlite.pull.skip").pipe(effect_1.Effect.annotateLogs({ dbName }));
27
+ return { newLocalBatch: effect_1.Option.none() };
28
+ }
29
+ const remoteHead = yield* (0, storage_1.decodeJson)(remoteHeadOption.value.value).pipe(effect_1.Effect.orDieWith((e) => new Error(`Failed to decode remote head: ${String(e)}. Value to decode: ${remoteHeadOption.value.value}`)));
30
+ const remoteBatchesToApply = remoteHead.batches.slice(localHead.value.head.batches.length);
31
+ // Instead of rollbacking the whole database we are keeping a CoW copy of the snapshot+all applied batches up to a certain batch id.
32
+ const lastLocalSnapshotId = localHead.value.head.snapshots.at(-1)?.id;
33
+ const lastLocalBatchId = localHead.value.head.batches.at(-1)?.id;
34
+ if (!lastLocalBatchId || !lastLocalSnapshotId) {
35
+ return yield* effect_1.Effect.die(new Error("Local head is in an invalid state, missing batches or snapshots")).pipe(effect_1.Effect.annotateLogs({ localHead: localHead, remoteHead }));
36
+ }
37
+ const baseSnapshotKey = (0, storage_1.baseKey)(lastLocalSnapshotId, lastLocalBatchId);
38
+ if (!(yield* localKV.exists(baseSnapshotKey))) {
39
+ return yield* effect_1.Effect.die(new Error("Base snapshot is missing!"));
40
+ }
41
+ const { newLocalBatch } = yield* effect_1.Effect.scoped(effect_1.Effect.gen(function* () {
42
+ const workingCopyKey = `${(0, uuid_1.v4)()}.working-copy`;
43
+ yield* effect_1.Effect.addFinalizer(() => localKV.delete(workingCopyKey)).pipe(effect_1.Effect.exit);
44
+ const { newLocalBatch, workingConnection } = yield* effect_1.Effect.all({
45
+ newLocalBatch: (0, batches_1.extractBatch)(tursoConnection.current, localHead.value.lastSyncedLocalChangeId),
46
+ syncFiles: (0, syncFiles_1.pullFiles)(remoteBatchesToApply.map((batch) => (0, storage_1.batchKey)(batch.id))),
47
+ workingConnection: localKV
48
+ .clone(baseSnapshotKey, workingCopyKey)
49
+ .pipe(effect_1.Effect.flatMap(() => localKV.connect(workingCopyKey))),
50
+ }, {
51
+ concurrency: "unbounded",
52
+ });
53
+ // maybe it would be faster to check the sizes of the batches, and if its not bigger than a few megabytes it could just be all loaded into memory in parallel, and then applied at once, as one transaction
54
+ for (const batch of remoteBatchesToApply) {
55
+ yield* (0, batches_1.applyBatch)(workingConnection, batch);
56
+ }
57
+ const nextLastRemoteAppliedChangeId = yield* (0, extract_1.getLatestChangeId)(workingConnection);
58
+ const newHead = structuredClone(remoteHead);
59
+ const lastRemoteSnapshotId = newHead.snapshots.at(-1)?.id;
60
+ const lastRemoteBatchId = newHead.batches.at(-1)?.id;
61
+ if (!lastRemoteBatchId || !lastRemoteSnapshotId) {
62
+ return yield* effect_1.Effect.die(new Error("Local head is in an invalid state, missing batches or snapshots")).pipe(effect_1.Effect.annotateLogs({ newHead }));
63
+ }
64
+ yield* (0, truncate_1.truncate)(workingConnection);
65
+ yield* localKV.delete((0, storage_1.walKey)(workingCopyKey.split(".")[0]));
66
+ yield* localKV.clone(workingCopyKey, (0, storage_1.baseKey)(lastRemoteSnapshotId, lastRemoteBatchId));
67
+ const allPendingBatches = [
68
+ ...(localHead.value.pendingBatches ?? []),
69
+ ...(effect_1.Option.isSome(newLocalBatch)
70
+ ? [
71
+ {
72
+ id: newLocalBatch.value.batch.id,
73
+ lastChangeId: newLocalBatch.value.lastLocalChangeId,
74
+ },
75
+ ]
76
+ : []),
77
+ ];
78
+ for (const pb of allPendingBatches) {
79
+ yield* (0, batches_1.applyBatch)(workingConnection, { id: pb.id });
80
+ }
81
+ yield* localKV.set((0, storage_1.headKey)(dbName), (0, storage_1.encodeJson)({
82
+ head: newHead,
83
+ remoteEtag: remoteHeadOption.value.etag,
84
+ lastSyncedLocalChangeId: nextLastRemoteAppliedChangeId,
85
+ ...(allPendingBatches.length > 0
86
+ ? { pendingBatches: allPendingBatches }
87
+ : {}),
88
+ }));
89
+ const oldConnection = yield* state.getConnection;
90
+ yield* effect_1.Effect.promise(() => oldConnection.close());
91
+ yield* localKV.delete((0, storage_1.walKey)(dbName));
92
+ yield* localKV.delete((0, storage_1.shmKey)(dbName));
93
+ yield* (0, truncate_1.truncate)(workingConnection);
94
+ yield* localKV.clone(workingCopyKey, (0, storage_1.dbKey)(dbName));
95
+ return { newLocalBatch };
96
+ }));
97
+ yield* state.setConnection(yield* localKV.connect((0, storage_1.dbKey)(dbName)));
98
+ return { newLocalBatch };
99
+ });
100
+ exports.pull = pull;
101
+ //# sourceMappingURL=pull.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pull.js","sourceRoot":"","sources":["../../../src/pull.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Scope } from \"effect\";\nimport { Effect, Option } from \"effect\";\nimport { v4 } from \"uuid\";\n\nimport type { ExtractedBatch } from \"./batches\";\nimport { applyBatch, extractBatch } from \"./batches\";\nimport { getLatestChangeId } from \"./cdc/extract\";\nimport { truncate } from \"./cdc/truncate\";\nimport { ConnectionConfig, ConnectionState } from \"./contexts\";\nimport { pullFiles } from \"./kv/syncFiles\";\nimport {\n\tLocalKV,\n\tRemoteKV,\n\tbaseKey,\n\tbatchKey,\n\tdbKey,\n\tdecodeJson,\n\tencodeJson,\n\tgetJson,\n\theadKey,\n\tshmKey,\n\twalKey,\n} from \"./storage\";\nimport type { Head, StoredHead } from \"./types\";\n\nexport const pull = (): Effect.Effect<\n\t{ newLocalBatch: Option.Option<ExtractedBatch> },\n\tError,\n\tConnectionConfig | ConnectionState | LocalKV | RemoteKV | Scope.Scope\n> =>\n\tEffect.gen(function* () {\n\t\tconst localKV = yield* LocalKV;\n\t\tconst remoteKV = yield* RemoteKV;\n\t\tconst { dbName } = yield* ConnectionConfig;\n\t\tconst state = yield* ConnectionState;\n\t\tconst tursoConnection = { current: yield* state.getConnection };\n\n\t\tyield* Effect.logDebug(\"s3qlite.pull.start\").pipe(Effect.annotateLogs({ dbName }));\n\n\t\tconst localHead = yield* getJson<StoredHead>(localKV, headKey(dbName)).pipe(\n\t\t\tEffect.flatMap(\n\t\t\t\tOption.match({\n\t\t\t\t\tonNone: () => Effect.die(new Error(\"Expected local stored head to exist\")),\n\t\t\t\t\tonSome: Effect.succeed,\n\t\t\t\t}),\n\t\t\t),\n\t\t);\n\n\t\tconst remoteHeadOption = yield* remoteKV.getIfChanged(\n\t\t\theadKey(dbName),\n\t\t\tlocalHead.value.remoteEtag,\n\t\t);\n\n\t\t// no changes detected, skip pull\n\t\tif (Option.isNone(remoteHeadOption)) {\n\t\t\tyield* Effect.logDebug(\"s3qlite.pull.skip\").pipe(Effect.annotateLogs({ dbName }));\n\t\t\treturn { newLocalBatch: Option.none() };\n\t\t}\n\n\t\tconst remoteHead = yield* decodeJson<Head>(remoteHeadOption.value.value).pipe(\n\t\t\tEffect.orDieWith(\n\t\t\t\t(e) =>\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t`Failed to decode remote head: ${String(e)}. Value to decode: ${remoteHeadOption.value.value}`,\n\t\t\t\t\t),\n\t\t\t),\n\t\t);\n\n\t\tconst remoteBatchesToApply = remoteHead.batches.slice(localHead.value.head.batches.length);\n\t\t// Instead of rollbacking the whole database we are keeping a CoW copy of the snapshot+all applied batches up to a certain batch id.\n\t\tconst lastLocalSnapshotId = localHead.value.head.snapshots.at(-1)?.id;\n\t\tconst lastLocalBatchId = localHead.value.head.batches.at(-1)?.id;\n\n\t\tif (!lastLocalBatchId || !lastLocalSnapshotId) {\n\t\t\treturn yield* Effect.die(\n\t\t\t\tnew Error(\"Local head is in an invalid state, missing batches or snapshots\"),\n\t\t\t).pipe(Effect.annotateLogs({ localHead: localHead, remoteHead }));\n\t\t}\n\n\t\tconst baseSnapshotKey = baseKey(lastLocalSnapshotId, lastLocalBatchId);\n\t\tif (!(yield* localKV.exists(baseSnapshotKey))) {\n\t\t\treturn yield* Effect.die(new Error(\"Base snapshot is missing!\"));\n\t\t}\n\n\t\tconst { newLocalBatch } = yield* Effect.scoped(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst workingCopyKey = `${v4()}.working-copy`;\n\n\t\t\t\tyield* Effect.addFinalizer(() => localKV.delete(workingCopyKey)).pipe(Effect.exit);\n\n\t\t\t\tconst { newLocalBatch, workingConnection } = yield* Effect.all(\n\t\t\t\t\t{\n\t\t\t\t\t\tnewLocalBatch: extractBatch(\n\t\t\t\t\t\t\ttursoConnection.current,\n\t\t\t\t\t\t\tlocalHead.value.lastSyncedLocalChangeId,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tsyncFiles: pullFiles(\n\t\t\t\t\t\t\tremoteBatchesToApply.map((batch) => batchKey(batch.id)),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tworkingConnection: localKV\n\t\t\t\t\t\t\t.clone(baseSnapshotKey, workingCopyKey)\n\t\t\t\t\t\t\t.pipe(Effect.flatMap(() => localKV.connect(workingCopyKey))),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tconcurrency: \"unbounded\",\n\t\t\t\t\t},\n\t\t\t\t);\n\n\t\t\t\t// maybe it would be faster to check the sizes of the batches, and if its not bigger than a few megabytes it could just be all loaded into memory in parallel, and then applied at once, as one transaction\n\t\t\t\tfor (const batch of remoteBatchesToApply) {\n\t\t\t\t\tyield* applyBatch(workingConnection, batch);\n\t\t\t\t}\n\n\t\t\t\tconst nextLastRemoteAppliedChangeId = yield* getLatestChangeId(workingConnection);\n\n\t\t\t\tconst newHead = structuredClone(remoteHead);\n\n\t\t\t\tconst lastRemoteSnapshotId = newHead.snapshots.at(-1)?.id;\n\t\t\t\tconst lastRemoteBatchId = newHead.batches.at(-1)?.id;\n\n\t\t\t\tif (!lastRemoteBatchId || !lastRemoteSnapshotId) {\n\t\t\t\t\treturn yield* Effect.die(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\"Local head is in an invalid state, missing batches or snapshots\",\n\t\t\t\t\t\t),\n\t\t\t\t\t).pipe(Effect.annotateLogs({ newHead }));\n\t\t\t\t}\n\n\t\t\t\tyield* truncate(workingConnection);\n\t\t\t\tyield* localKV.delete(walKey(workingCopyKey.split(\".\")[0]));\n\n\t\t\t\tyield* localKV.clone(\n\t\t\t\t\tworkingCopyKey,\n\t\t\t\t\tbaseKey(lastRemoteSnapshotId, lastRemoteBatchId),\n\t\t\t\t);\n\n\t\t\t\tconst allPendingBatches = [\n\t\t\t\t\t...(localHead.value.pendingBatches ?? []),\n\t\t\t\t\t...(Option.isSome(newLocalBatch)\n\t\t\t\t\t\t? [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tid: newLocalBatch.value.batch.id,\n\t\t\t\t\t\t\t\t\tlastChangeId: newLocalBatch.value.lastLocalChangeId,\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];\n\n\t\t\t\tfor (const pb of allPendingBatches) {\n\t\t\t\t\tyield* applyBatch(workingConnection, { id: pb.id });\n\t\t\t\t}\n\n\t\t\t\tyield* localKV.set(\n\t\t\t\t\theadKey(dbName),\n\t\t\t\t\tencodeJson<StoredHead>({\n\t\t\t\t\t\thead: newHead,\n\t\t\t\t\t\tremoteEtag: remoteHeadOption.value.etag,\n\t\t\t\t\t\tlastSyncedLocalChangeId: nextLastRemoteAppliedChangeId,\n\t\t\t\t\t\t...(allPendingBatches.length > 0\n\t\t\t\t\t\t\t? { pendingBatches: allPendingBatches }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\tconst oldConnection = yield* state.getConnection;\n\t\t\t\tyield* Effect.promise(() => oldConnection.close());\n\t\t\t\tyield* localKV.delete(walKey(dbName));\n\t\t\t\tyield* localKV.delete(shmKey(dbName));\n\t\t\t\tyield* truncate(workingConnection);\n\t\t\t\tyield* localKV.clone(workingCopyKey, dbKey(dbName));\n\n\t\t\t\treturn { newLocalBatch };\n\t\t\t}),\n\t\t);\n\n\t\tyield* state.setConnection(yield* localKV.connect(dbKey(dbName)));\n\n\t\treturn { newLocalBatch };\n\t});"]}
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.push = void 0;
4
+ const effect_1 = require("effect");
5
+ const batches_1 = require("./batches");
6
+ const extract_1 = require("./cdc/extract");
7
+ const truncate_1 = require("./cdc/truncate");
8
+ const contexts_1 = require("./contexts");
9
+ const syncFiles_1 = require("./kv/syncFiles");
10
+ const storage_1 = require("./storage");
11
+ const push = () => effect_1.Effect.gen(function* () {
12
+ const localKV = yield* storage_1.LocalKV;
13
+ const remoteKV = yield* storage_1.RemoteKV;
14
+ const { dbName } = yield* contexts_1.ConnectionConfig;
15
+ const state = yield* contexts_1.ConnectionState;
16
+ const tursoConnection = { current: yield* state.getConnection };
17
+ const localHead = yield* (0, storage_1.getJson)(localKV, (0, storage_1.headKey)(dbName)).pipe(effect_1.Effect.orDie, effect_1.Effect.flatMap(effect_1.Option.match({
18
+ onNone: () => effect_1.Effect.die(new Error("Expected local stored head to exist")),
19
+ onSome: effect_1.Effect.succeed,
20
+ })));
21
+ const pendingBatches = [];
22
+ if (localHead.value.pendingBatches?.length) {
23
+ pendingBatches.push(...localHead.value.pendingBatches.map((pb) => ({
24
+ batch: { id: pb.id },
25
+ lastChangeId: pb.lastChangeId,
26
+ })));
27
+ }
28
+ const extracted = yield* (0, batches_1.extractBatch)(tursoConnection.current, localHead.value.lastSyncedLocalChangeId);
29
+ if (effect_1.Option.isSome(extracted)) {
30
+ pendingBatches.push({
31
+ batch: extracted.value.batch,
32
+ lastChangeId: extracted.value.lastLocalChangeId,
33
+ });
34
+ }
35
+ if (pendingBatches.length === 0) {
36
+ return;
37
+ }
38
+ yield* (0, syncFiles_1.pushFiles)(pendingBatches.map((pb) => (0, storage_1.batchKey)(pb.batch.id))).pipe(effect_1.Effect.orDie);
39
+ const newHead = {
40
+ snapshots: localHead.value.head.snapshots,
41
+ batches: localHead.value.head.batches.concat(pendingBatches.map((pb) => pb.batch)),
42
+ };
43
+ const result = yield* remoteKV.cas((0, storage_1.headKey)(dbName), (0, storage_1.encodeJson)(newHead), localHead.value.remoteEtag);
44
+ const lastSyncedLocalChangeId = yield* (0, extract_1.getLatestChangeId)(tursoConnection.current).pipe(effect_1.Effect.orDie);
45
+ yield* localKV.set((0, storage_1.headKey)(dbName), (0, storage_1.encodeJson)({
46
+ head: newHead,
47
+ remoteEtag: result.etag,
48
+ lastSyncedLocalChangeId,
49
+ }));
50
+ const lastSnapshotId = newHead.snapshots.at(-1)?.id;
51
+ const lastBatchId = newHead.batches.at(-1)?.id;
52
+ if (lastSnapshotId && lastBatchId) {
53
+ yield* (0, truncate_1.truncate)(tursoConnection.current);
54
+ yield* localKV.clone((0, storage_1.dbKey)(dbName), (0, storage_1.baseKey)(lastSnapshotId, lastBatchId));
55
+ }
56
+ });
57
+ exports.push = push;
58
+ //# sourceMappingURL=push.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.js","sourceRoot":"","sources":["../../../src/push.ts"],"names":[],"mappings":"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","sourcesContent":["import { Effect, Option } from \"effect\";\n\nimport { extractBatch } from \"./batches\";\nimport { getLatestChangeId } from \"./cdc/extract\";\nimport { truncate } from \"./cdc/truncate\";\nimport { ConnectionConfig, ConnectionState } from \"./contexts\";\nimport { pushFiles } from \"./kv/syncFiles\";\nimport {\n\tLocalKV,\n\tRemoteKV,\n\tbaseKey,\n\tbatchKey,\n\tdbKey,\n\tencodeJson,\n\tgetJson,\n\theadKey,\n} from \"./storage\";\nimport type { Batch, Head, StoredHead } from \"./types\";\n\nexport const push = () =>\n\tEffect.gen(function* () {\n\t\tconst localKV = yield* LocalKV;\n\t\tconst remoteKV = yield* RemoteKV;\n\t\tconst { dbName } = yield* ConnectionConfig;\n\t\tconst state = yield* ConnectionState;\n\t\tconst tursoConnection = { current: yield* state.getConnection };\n\n\t\tconst localHead = yield* getJson<StoredHead>(localKV, headKey(dbName)).pipe(\n\t\t\tEffect.orDie,\n\t\t\tEffect.flatMap(\n\t\t\t\tOption.match({\n\t\t\t\t\tonNone: () => Effect.die(new Error(\"Expected local stored head to exist\")),\n\t\t\t\t\tonSome: Effect.succeed,\n\t\t\t\t}),\n\t\t\t),\n\t\t);\n\n\t\tconst pendingBatches: { batch: Batch; lastChangeId: number }[] = [];\n\n\t\tif (localHead.value.pendingBatches?.length) {\n\t\t\tpendingBatches.push(\n\t\t\t\t...localHead.value.pendingBatches.map((pb) => ({\n\t\t\t\t\tbatch: { id: pb.id },\n\t\t\t\t\tlastChangeId: pb.lastChangeId,\n\t\t\t\t})),\n\t\t\t);\n\t\t}\n\n\t\tconst extracted = yield* extractBatch(\n\t\t\ttursoConnection.current,\n\t\t\tlocalHead.value.lastSyncedLocalChangeId,\n\t\t);\n\n\t\tif (Option.isSome(extracted)) {\n\t\t\tpendingBatches.push({\n\t\t\t\tbatch: extracted.value.batch,\n\t\t\t\tlastChangeId: extracted.value.lastLocalChangeId,\n\t\t\t});\n\t\t}\n\n\t\tif (pendingBatches.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tyield* pushFiles(pendingBatches.map((pb) => batchKey(pb.batch.id))).pipe(Effect.orDie);\n\n\t\tconst newHead: Head = {\n\t\t\tsnapshots: localHead.value.head.snapshots,\n\t\t\tbatches: localHead.value.head.batches.concat(pendingBatches.map((pb) => pb.batch)),\n\t\t};\n\n\t\tconst result = yield* remoteKV.cas(\n\t\t\theadKey(dbName),\n\t\t\tencodeJson(newHead),\n\t\t\tlocalHead.value.remoteEtag,\n\t\t);\n\n\t\tconst lastSyncedLocalChangeId = yield* getLatestChangeId(tursoConnection.current).pipe(\n\t\t\tEffect.orDie,\n\t\t);\n\n\t\tyield* localKV.set(\n\t\t\theadKey(dbName),\n\t\t\tencodeJson<StoredHead>({\n\t\t\t\thead: newHead,\n\t\t\t\tremoteEtag: result.etag,\n\t\t\t\tlastSyncedLocalChangeId,\n\t\t\t}),\n\t\t);\n\n\t\tconst lastSnapshotId = newHead.snapshots.at(-1)?.id;\n\t\tconst lastBatchId = newHead.batches.at(-1)?.id;\n\t\tif (lastSnapshotId && lastBatchId) {\n\t\t\tyield* truncate(tursoConnection.current);\n\t\t\tyield* localKV.clone(dbKey(dbName), baseKey(lastSnapshotId, lastBatchId));\n\t\t}\n\t});"]}
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getJson = exports.decodeJson = exports.encodeJson = exports.baseKey = exports.shmKey = exports.walKey = exports.dbKey = exports.snapshotKey = exports.batchKey = exports.headKey = exports.RemoteKV = exports.LocalKV = void 0;
4
+ const effect_1 = require("effect");
5
+ const textEncoder = new TextEncoder();
6
+ const textDecoder = new TextDecoder();
7
+ class LocalKV extends effect_1.Context.Tag("s3qlite/LocalKV")() {
8
+ }
9
+ exports.LocalKV = LocalKV;
10
+ class RemoteKV extends effect_1.Context.Tag("s3qlite/RemoteKV")() {
11
+ }
12
+ exports.RemoteKV = RemoteKV;
13
+ const headKey = (dbName) => `${dbName}.json`;
14
+ exports.headKey = headKey;
15
+ const batchKey = (batchId) => `${batchId}.batch`;
16
+ exports.batchKey = batchKey;
17
+ const snapshotKey = (snapshotId) => `${snapshotId}.snapshot`;
18
+ exports.snapshotKey = snapshotKey;
19
+ const dbKey = (dbName) => `${dbName}.db`;
20
+ exports.dbKey = dbKey;
21
+ const walKey = (dbName) => `${dbName}.db-wal`;
22
+ exports.walKey = walKey;
23
+ const shmKey = (dbName) => `${dbName}.db-shm`;
24
+ exports.shmKey = shmKey;
25
+ const baseKey = (snapshotId, batchId) => `${snapshotId}-${batchId}.base`;
26
+ exports.baseKey = baseKey;
27
+ const encodeJson = (value) => textEncoder.encode(JSON.stringify(value));
28
+ exports.encodeJson = encodeJson;
29
+ const decodeJson = (value) => effect_1.Effect.try({
30
+ try: () => JSON.parse(textDecoder.decode(value)),
31
+ catch: (error) => error instanceof SyntaxError
32
+ ? error
33
+ : new SyntaxError(`Failed to parse "${value}": ${String(error)}`),
34
+ });
35
+ exports.decodeJson = decodeJson;
36
+ const getJson = (kv, key) => kv.get(key).pipe(effect_1.Effect.flatMap(effect_1.Option.match({
37
+ onNone: () => effect_1.Effect.succeed(effect_1.Option.none()),
38
+ onSome: ({ etag, value }) => (0, exports.decodeJson)(value).pipe(effect_1.Effect.map((decoded) => effect_1.Option.some({ etag, value: decoded }))),
39
+ })));
40
+ exports.getJson = getJson;
41
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/storage.ts"],"names":[],"mappings":";;;AAAA,mCAAiD;AAIjD,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC;AACtC,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC;AAEtC,MAAa,OAAQ,SAAQ,gBAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAGxD;CAAG;AAHN,0BAGM;AAEN,MAAa,QAAS,SAAQ,gBAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAAgB;CAAG;AAAhF,4BAAgF;AAEzE,MAAM,OAAO,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,GAAG,MAAM,OAAO,CAAC;AAAvD,QAAA,OAAO,WAAgD;AAE7D,MAAM,QAAQ,GAAG,CAAC,OAAe,EAAU,EAAE,CAAC,GAAG,OAAO,QAAQ,CAAC;AAA3D,QAAA,QAAQ,YAAmD;AAEjE,MAAM,WAAW,GAAG,CAAC,UAAkB,EAAU,EAAE,CAAC,GAAG,UAAU,WAAW,CAAC;AAAvE,QAAA,WAAW,eAA4D;AAE7E,MAAM,KAAK,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,GAAG,MAAM,KAAK,CAAC;AAAnD,QAAA,KAAK,SAA8C;AAEzD,MAAM,MAAM,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,GAAG,MAAM,SAAS,CAAC;AAAxD,QAAA,MAAM,UAAkD;AAE9D,MAAM,MAAM,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,GAAG,MAAM,SAAS,CAAC;AAAxD,QAAA,MAAM,UAAkD;AAE9D,MAAM,OAAO,GAAG,CAAC,UAAkB,EAAE,OAAe,EAAU,EAAE,CACtE,GAAG,UAAU,IAAI,OAAO,OAAO,CAAC;AADpB,QAAA,OAAO,WACa;AAE1B,MAAM,UAAU,GAAG,CAAI,KAAQ,EAAc,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;AAApF,QAAA,UAAU,cAA0E;AAE1F,MAAM,UAAU,GAAG,CAAI,KAAiB,EAAiC,EAAE,CACjF,eAAM,CAAC,GAAG,CAAC;IACV,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAM;IACrD,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAChB,KAAK,YAAY,WAAW;QAC3B,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,IAAI,WAAW,CAAC,oBAAoB,KAAK,MAAM,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;CACnE,CAAC,CAAC;AAPS,QAAA,UAAU,cAOnB;AAEG,MAAM,OAAO,GAAG,CACtB,EAAM,EACN,GAAW,EAC6D,EAAE,CAC1E,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CACf,eAAM,CAAC,OAAO,CACb,eAAM,CAAC,KAAK,CAAC;IACZ,MAAM,EAAE,GAAG,EAAE,CAAC,eAAM,CAAC,OAAO,CAAC,eAAM,CAAC,IAAI,EAAE,CAAC;IAC3C,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAC3B,IAAA,kBAAU,EAAI,KAAK,CAAC,CAAC,IAAI,CACxB,eAAM,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,eAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAC9D;CACF,CAAC,CACF,CACD,CAAC;AAdU,QAAA,OAAO,WAcjB","sourcesContent":["import { Context, Effect, Option } from \"effect\";\n\nimport type { CloneTrait, ConnectDatabaseTrait, KV } from \"./kv/kv\";\n\nconst textEncoder = new TextEncoder();\nconst textDecoder = new TextDecoder();\n\nexport class LocalKV extends Context.Tag(\"s3qlite/LocalKV\")<\n\tLocalKV,\n\tKV & CloneTrait & ConnectDatabaseTrait\n>() {}\n\nexport class RemoteKV extends Context.Tag(\"s3qlite/RemoteKV\")<RemoteKV, KV>() {}\n\nexport const headKey = (dbName: string): string => `${dbName}.json`;\n\nexport const batchKey = (batchId: string): string => `${batchId}.batch`;\n\nexport const snapshotKey = (snapshotId: string): string => `${snapshotId}.snapshot`;\n\nexport const dbKey = (dbName: string): string => `${dbName}.db`;\n\nexport const walKey = (dbName: string): string => `${dbName}.db-wal`;\n\nexport const shmKey = (dbName: string): string => `${dbName}.db-shm`;\n\nexport const baseKey = (snapshotId: string, batchId: string): string =>\n\t`${snapshotId}-${batchId}.base`;\n\nexport const encodeJson = <A>(value: A): Uint8Array => textEncoder.encode(JSON.stringify(value));\n\nexport const decodeJson = <A>(value: Uint8Array): Effect.Effect<A, SyntaxError> =>\n\tEffect.try({\n\t\ttry: () => JSON.parse(textDecoder.decode(value)) as A,\n\t\tcatch: (error) =>\n\t\t\terror instanceof SyntaxError\n\t\t\t\t? error\n\t\t\t\t: new SyntaxError(`Failed to parse \"${value}\": ${String(error)}`),\n\t});\n\nexport const getJson = <A>(\n\tkv: KV,\n\tkey: string,\n): Effect.Effect<Option.Option<{ value: A; etag: string }>, SyntaxError> =>\n\tkv.get(key).pipe(\n\t\tEffect.flatMap(\n\t\t\tOption.match({\n\t\t\t\tonNone: () => Effect.succeed(Option.none()),\n\t\t\t\tonSome: ({ etag, value }) =>\n\t\t\t\t\tdecodeJson<A>(value).pipe(\n\t\t\t\t\t\tEffect.map((decoded) => Option.some({ etag, value: decoded })),\n\t\t\t\t\t),\n\t\t\t}),\n\t\t),\n\t);"]}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Database } from \"@tursodatabase/database\";\nimport type { Effect } from \"effect\";\n\nexport type Snapshot = {\n\tid: string;\n\n\t// thanks to this we can do full history\n\tbaseSnapshotId?: string;\n\tbatchIdsApplied: string[];\n};\n\nexport type Batch = {\n\tid: string;\n};\n\nexport type Head = {\n\tsnapshots: Snapshot[];\n\tbatches: Batch[];\n};\n\nexport type PendingBatch = {\n\tid: string;\n\tlastChangeId: number;\n};\n\nexport type StoredHead = {\n\thead: Head;\n\n\t/**\n\t * The latest etag of the remote head this instance has been synced to.\n\t */\n\tremoteEtag: string;\n\n\tlastSyncedLocalChangeId: number;\n\n\tpendingBatches?: PendingBatch[];\n};\n\n// export type ConflictResolutionMode = \"fail\" | \"preferRemote\" | \"preferLocal\";\n//\n// export type ConflictOperation = \"pull\" | \"push\" | \"sync\";\n//\n// export type ConflictResolutionContext = {\n// \treadonly cause: Error;\n// \treadonly localHead: StoredHead;\n// \treadonly operation: ConflictOperation;\n// \treadonly remoteHead: Head;\n// };\n//\n// export type ConflictResolver = (\n// \tcontext: ConflictResolutionContext,\n// ) => Effect.Effect<ConflictResolutionMode, Error>;\n//\n// type StrongConsistencyOptions = {\n// \tmode: \"strong\";\n// };\n//\n// type EventualConsistencyOptions = {\n// \tmode: \"eventual\";\n// \tsync?: \"manual\" | { everyMs: number };\n// };\n\nexport type ConnectionOptions = {\n\tbucket: string;\n\t// conflictResolution?: ConflictResolutionMode | ConflictResolver;\n\tlocalDirectory?: string;\n};\n// } & (StrongConsistencyOptions | EventualConsistencyOptions);\n\nexport type S3qliteDatabase = Database & {\n\tpull: () => Effect.Effect<void, Error>;\n\tpush: () => Effect.Effect<void, Error>;\n\tsync: () => Effect.Effect<void, Error>;\n\tfork: (dbName: string) => Effect.Effect<void, Error>;\n\tcheckpoint: () => Effect.Effect<void, Error>;\n};"]}
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.wrapDatabase = void 0;
4
+ const asyncDatabaseMethods = new Set([
5
+ "all",
6
+ "close",
7
+ "exec",
8
+ "get",
9
+ "iterate",
10
+ "pragma",
11
+ "run",
12
+ ]);
13
+ const asyncStatementMethods = new Set(["all", "close", "get", "run"]);
14
+ const syncStatementMethods = new Set(["bind", "pluck", "raw", "safeIntegers"]);
15
+ const wrapTransaction = (transaction, wrap) => new Proxy(transaction, {
16
+ apply: (target, thisArg, args) => wrap(() => Reflect.apply(target, thisArg, args), "transaction", args),
17
+ get: (target, property, receiver) => {
18
+ const value = Reflect.get(target, property, receiver);
19
+ if (typeof value !== "function") {
20
+ return value;
21
+ }
22
+ return wrapTransaction(value, wrap);
23
+ },
24
+ });
25
+ const wrapStatement = (statement, wrap) => {
26
+ let proxy;
27
+ proxy = new Proxy(statement, {
28
+ get: (target, property, receiver) => {
29
+ const value = Reflect.get(target, property, receiver);
30
+ if (typeof value !== "function") {
31
+ return value;
32
+ }
33
+ if (syncStatementMethods.has(property)) {
34
+ return (...args) => {
35
+ Reflect.apply(value, target, args);
36
+ return proxy;
37
+ };
38
+ }
39
+ if (property === "iterate") {
40
+ return (...args) => (async function* () {
41
+ const iterable = wrap(() => Reflect.apply(value, target, args), property, args);
42
+ for await (const item of iterable) {
43
+ yield item;
44
+ }
45
+ })();
46
+ }
47
+ if (asyncStatementMethods.has(property)) {
48
+ return (...args) => wrap(() => Reflect.apply(value, target, args), property, args);
49
+ }
50
+ return (...args) => Reflect.apply(value, target, args);
51
+ },
52
+ });
53
+ return proxy;
54
+ };
55
+ const wrapDatabase = (getTarget, wrap) => {
56
+ return new Proxy({}, {
57
+ get: (wrapper, property, receiver) => {
58
+ if (Reflect.has(wrapper, property)) {
59
+ return Reflect.get(wrapper, property, receiver);
60
+ }
61
+ const original = getTarget();
62
+ const value = Reflect.get(original, property, original);
63
+ if (typeof value !== "function") {
64
+ return value;
65
+ }
66
+ if (property === "prepare") {
67
+ return (...args) => wrapStatement(Reflect.apply(value, getTarget(), args), wrap);
68
+ }
69
+ if (property === "transaction") {
70
+ return (...args) => wrapTransaction(Reflect.apply(value, getTarget(), args), wrap);
71
+ }
72
+ if (asyncDatabaseMethods.has(property)) {
73
+ return (...args) => wrap(() => Reflect.apply(value, getTarget(), args), property, args);
74
+ }
75
+ return (...args) => Reflect.apply(value, getTarget(), args);
76
+ },
77
+ });
78
+ };
79
+ exports.wrapDatabase = wrapDatabase;
80
+ //# sourceMappingURL=wrapDatabase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapDatabase.js","sourceRoot":"","sources":["../../../src/wrapDatabase.ts"],"names":[],"mappings":"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","sourcesContent":["const asyncDatabaseMethods = new Set<PropertyKey>([\n\t\"all\",\n\t\"close\",\n\t\"exec\",\n\t\"get\",\n\t\"iterate\",\n\t\"pragma\",\n\t\"run\",\n]);\n\nconst asyncStatementMethods = new Set<PropertyKey>([\"all\", \"close\", \"get\", \"run\"]);\n\nconst syncStatementMethods = new Set<PropertyKey>([\"bind\", \"pluck\", \"raw\", \"safeIntegers\"]);\n\nconst wrapTransaction = <T extends (...args: readonly unknown[]) => unknown>(\n\ttransaction: T,\n\twrap: <R>(call: () => R, method: PropertyKey, args: readonly unknown[]) => R,\n): T =>\n\tnew Proxy(transaction, {\n\t\tapply: (target, thisArg, args) =>\n\t\t\twrap(() => Reflect.apply(target, thisArg, args), \"transaction\", args),\n\t\tget: (target, property, receiver) => {\n\t\t\tconst value = Reflect.get(target, property, receiver);\n\n\t\t\tif (typeof value !== \"function\") {\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t\treturn wrapTransaction(value as (...args: readonly unknown[]) => unknown, wrap);\n\t\t},\n\t}) as T;\n\nconst wrapStatement = <T extends object>(\n\tstatement: T,\n\twrap: <R>(call: () => R, method: PropertyKey, args: readonly unknown[]) => R,\n): T => {\n\tlet proxy: T;\n\n\tproxy = new Proxy(statement, {\n\t\tget: (target, property, receiver) => {\n\t\t\tconst value = Reflect.get(target, property, receiver);\n\n\t\t\tif (typeof value !== \"function\") {\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t\tif (syncStatementMethods.has(property)) {\n\t\t\t\treturn (...args: unknown[]) => {\n\t\t\t\t\tReflect.apply(value, target, args);\n\t\t\t\t\treturn proxy;\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (property === \"iterate\") {\n\t\t\t\treturn (...args: unknown[]) =>\n\t\t\t\t\t(async function* () {\n\t\t\t\t\t\tconst iterable = wrap(\n\t\t\t\t\t\t\t() => Reflect.apply(value, target, args),\n\t\t\t\t\t\t\tproperty,\n\t\t\t\t\t\t\targs,\n\t\t\t\t\t\t) as AsyncIterable<unknown>;\n\n\t\t\t\t\t\tfor await (const item of iterable) {\n\t\t\t\t\t\t\tyield item;\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t}\n\n\t\t\tif (asyncStatementMethods.has(property)) {\n\t\t\t\treturn (...args: unknown[]) =>\n\t\t\t\t\twrap(() => Reflect.apply(value, target, args), property, args);\n\t\t\t}\n\n\t\t\treturn (...args: unknown[]) => Reflect.apply(value, target, args);\n\t\t},\n\t});\n\n\treturn proxy;\n};\n\nexport const wrapDatabase = <T extends object>(\n\tgetTarget: () => T,\n\twrap: <R>(call: () => R, method: PropertyKey, args: readonly unknown[]) => R,\n): T => {\n\treturn new Proxy({} as T, {\n\t\tget: (wrapper, property, receiver) => {\n\t\t\tif (Reflect.has(wrapper, property)) {\n\t\t\t\treturn Reflect.get(wrapper, property, receiver);\n\t\t\t}\n\n\t\t\tconst original = getTarget();\n\t\t\tconst value = Reflect.get(original, property, original);\n\n\t\t\tif (typeof value !== \"function\") {\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t\tif (property === \"prepare\") {\n\t\t\t\treturn (...args: unknown[]) =>\n\t\t\t\t\twrapStatement(Reflect.apply(value, getTarget(), args) as object, wrap);\n\t\t\t}\n\n\t\t\tif (property === \"transaction\") {\n\t\t\t\treturn (...args: unknown[]) =>\n\t\t\t\t\twrapTransaction(\n\t\t\t\t\t\tReflect.apply(value, getTarget(), args) as (\n\t\t\t\t\t\t\t...args: readonly unknown[]\n\t\t\t\t\t\t) => unknown,\n\t\t\t\t\t\twrap,\n\t\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (asyncDatabaseMethods.has(property)) {\n\t\t\t\treturn (...args: unknown[]) =>\n\t\t\t\t\twrap(() => Reflect.apply(value, getTarget(), args), property, args);\n\t\t\t}\n\n\t\t\treturn (...args: unknown[]) => Reflect.apply(value, getTarget(), args);\n\t\t},\n\t});\n};"]}
@@ -0,0 +1 @@
1
+ export * from "./src/index.js";
package/dts/index.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { S3 } from "@effect-aws/client-s3";
2
+ import type { Database } from "@tursodatabase/database";
3
+ import type { ConnectionOptions } from "./src/types.js";
4
+ type PromiseMethods = {
5
+ pull: () => Promise<void>;
6
+ push: () => Promise<void>;
7
+ sync: () => Promise<void>;
8
+ fork: (dbName: string) => Promise<void>;
9
+ checkpoint: () => Promise<void>;
10
+ };
11
+ export type PromiseS3qliteDatabase = Database & PromiseMethods;
12
+ export type PromiseConnectionOptions = ConnectionOptions & {
13
+ s3?: Parameters<typeof S3.layer>[0];
14
+ };
15
+ export declare const connect: (dbName: string, { s3, ...connectionOptions }: PromiseConnectionOptions) => Promise<PromiseS3qliteDatabase>;
16
+ export * from "./src/index.js";
@@ -0,0 +1,10 @@
1
+ import type { Database } from "@tursodatabase/database";
2
+ import { Effect, Option } from "effect";
3
+ import { LocalKV } from "./storage.js";
4
+ import type { Batch } from "./types.js";
5
+ export declare const applyBatch: (database: Database, batch: Batch) => Effect.Effect<void, never, LocalKV>;
6
+ export type ExtractedBatch = {
7
+ batch: Batch;
8
+ lastLocalChangeId: number;
9
+ };
10
+ export declare const extractBatch: (database: Database, lastChangeId: number) => Effect.Effect<Option.Option<ExtractedBatch>, never, LocalKV>;
@@ -0,0 +1,14 @@
1
+ import type { Database } from "@tursodatabase/database";
2
+ import { Effect } from "effect";
3
+ import type { CDCRow } from "./types.js";
4
+ declare const ReplayError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
5
+ readonly _tag: "ReplayError";
6
+ } & Readonly<A>;
7
+ export declare class ReplayError extends ReplayError_base<{
8
+ message: string;
9
+ cause: unknown;
10
+ }> {
11
+ }
12
+ export declare const applyCDC: (database: Database, change: CDCRow) => Effect.Effect<void, Error>;
13
+ export declare const replayCDC: (database: Database, changes: readonly CDCRow[]) => Effect.Effect<void, ReplayError>;
14
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { Database } from "@tursodatabase/database";
2
+ import { Effect } from "effect";
3
+ import type { CDCRow } from "./types.js";
4
+ /**
5
+ * Extracts CDC rows from the Turso database after a given change_id exclusively.
6
+ */
7
+ export declare const extractCDC: (tursoConnection: Database, afterId: number) => Effect.Effect<readonly CDCRow[]>;
8
+ export declare const getLatestChangeId: (tursoConnection: Database) => Effect.Effect<number, Error>;
@@ -0,0 +1,3 @@
1
+ import type { CDCRow } from "./types.js";
2
+ export declare const serializeCDC: (changes: readonly CDCRow[]) => Uint8Array;
3
+ export declare const deserializeCDC: (bytes: Uint8Array) => readonly CDCRow[];
@@ -0,0 +1,19 @@
1
+ import type { Database } from "@tursodatabase/database";
2
+ import { Effect } from "effect";
3
+ export type SchemaEntry = {
4
+ type: string;
5
+ name: string;
6
+ tblName: string;
7
+ sql: string | null;
8
+ };
9
+ export type DatabaseSnapshot = {
10
+ schema: readonly SchemaEntry[];
11
+ tables: Readonly<Record<string, readonly Record<string, unknown>[]>>;
12
+ };
13
+ export declare const compareSnapshots: (left: DatabaseSnapshot, right: DatabaseSnapshot) => boolean;
14
+ export declare const snapshotDatabase: (database: Database) => Effect.Effect<DatabaseSnapshot, Error>;
15
+ export declare const compareDatabases: (left: Database, right: Database) => Effect.Effect<boolean, Error>;
16
+ export declare const applyBatch: (database: Database, batch: {
17
+ readonly transactional: boolean;
18
+ readonly statements: readonly string[];
19
+ }) => Effect.Effect<void, Error>;
@@ -0,0 +1,6 @@
1
+ import type { Database } from "@tursodatabase/database";
2
+ import { Effect } from "effect";
3
+ export declare const truncate: (db: Database) => Effect.Effect<{
4
+ changes: number;
5
+ lastInsertRowid: number;
6
+ }, never, never>;
@@ -0,0 +1,35 @@
1
+ export type CDCValue = number | string | Uint8Array | null;
2
+ export declare const CDCChangeType: {
3
+ readonly Delete: -1;
4
+ readonly Update: 0;
5
+ readonly Insert: 1;
6
+ };
7
+ export type CDCChangeType = (typeof CDCChangeType)[keyof typeof CDCChangeType];
8
+ type CDCBase = {
9
+ changeId: number;
10
+ changeTime: number;
11
+ changeTxnId: number;
12
+ };
13
+ export type CDCRow = (CDCBase & {
14
+ changeType: typeof CDCChangeType.Insert;
15
+ tableName: string;
16
+ id: CDCValue;
17
+ before: Uint8Array | null;
18
+ after: Uint8Array;
19
+ updates: Uint8Array | null;
20
+ }) | (CDCBase & {
21
+ changeType: typeof CDCChangeType.Update;
22
+ tableName: string;
23
+ id: CDCValue;
24
+ before: Uint8Array;
25
+ after: Uint8Array;
26
+ updates: Uint8Array;
27
+ }) | (CDCBase & {
28
+ changeType: typeof CDCChangeType.Delete;
29
+ tableName: string;
30
+ id: CDCValue;
31
+ before: Uint8Array;
32
+ after: Uint8Array | null;
33
+ updates: Uint8Array | null;
34
+ });
35
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { Database } from "@tursodatabase/database";
2
+ import { Effect } from "effect";
3
+ export declare const withoutCDC: <A, E, R>(database: Database, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
@@ -0,0 +1,5 @@
1
+ import { Context, Effect } from "effect";
2
+ import { LocalKV, RemoteKV } from "./storage.js";
3
+ import type { ConnectionOptions, S3qliteDatabase } from "./types.js";
4
+ export declare const initializeContexts: (connectionOptions: ConnectionOptions) => Effect.Effect<Context.Context<LocalKV | RemoteKV>, never, import("effect/Scope").Scope | import("@effect/platform/FileSystem").FileSystem | import("@effect-aws/client-s3").S3Service>;
5
+ export declare const connect: (dbName: string, options: ConnectionOptions) => Effect.Effect<S3qliteDatabase, Error, import("effect/Scope").Scope | LocalKV | RemoteKV>;
@@ -0,0 +1,22 @@
1
+ import type { Database } from "@tursodatabase/database";
2
+ import { Context } from "effect";
3
+ import type { Effect } from "effect";
4
+ import type { ConnectionOptions } from "./types.js";
5
+ export type ConnectionConfigValue = {
6
+ readonly bucket: string;
7
+ readonly dbName: string;
8
+ readonly livePath: string;
9
+ readonly localDirectory: string;
10
+ readonly localHeadPath: string;
11
+ readonly options: ConnectionOptions;
12
+ };
13
+ declare const ConnectionConfig_base: Context.TagClass<ConnectionConfig, "s3qlite/ConnectionConfig", ConnectionConfigValue>;
14
+ export declare class ConnectionConfig extends ConnectionConfig_base {
15
+ }
16
+ declare const ConnectionState_base: Context.TagClass<ConnectionState, "s3qlite/ConnectionState", {
17
+ readonly getConnection: Effect.Effect<Database>;
18
+ readonly setConnection: (connection: Database) => Effect.Effect<void>;
19
+ }>;
20
+ export declare class ConnectionState extends ConnectionState_base {
21
+ }
22
+ export {};
@@ -0,0 +1,12 @@
1
+ import { Effect } from "effect";
2
+ import { initializeContexts } from "./connection.js";
3
+ import type { ConnectionOptions } from "./types.js";
4
+ export * from "./cdc/apply.js";
5
+ export * from "./cdc/extract.js";
6
+ export * from "./cdc/types.js";
7
+ export * from "./kv/fileKV.js";
8
+ export * from "./kv/kv.js";
9
+ export * from "./kv/memoryKV.js";
10
+ export * from "./kv/s3KV.js";
11
+ export { initializeContexts as initContexts };
12
+ export declare const connect: (dbName: string, connectionOptions: ConnectionOptions) => Effect.Effect<import("./types.js").S3qliteDatabase, Error, import("effect/Scope").Scope | import("@effect/platform/FileSystem").FileSystem | import("@effect-aws/client-s3").S3Service>;
@@ -0,0 +1,5 @@
1
+ import { FileSystem } from "@effect/platform/FileSystem";
2
+ import type { Scope } from "effect";
3
+ import { Effect } from "effect";
4
+ import type { CloneTrait, ConnectDatabaseTrait, KV } from "./kv.js";
5
+ export declare const makeFileKV: (rootDirectory: string) => Effect.Effect<KV & CloneTrait & ConnectDatabaseTrait, never, FileSystem | Scope.Scope>;
@@ -0,0 +1,42 @@
1
+ import type { Database } from "@tursodatabase/database";
2
+ import type { Effect, Option, Scope, Stream } from "effect";
3
+ declare const ConflictError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
4
+ readonly _tag: "ConflictError";
5
+ } & Readonly<A>;
6
+ export declare class ConflictError extends ConflictError_base<{
7
+ readonly key: string;
8
+ }> {
9
+ }
10
+ export type KV = {
11
+ get(key: string): Effect.Effect<Option.Option<{
12
+ value: Uint8Array;
13
+ etag: string;
14
+ }>>;
15
+ /** Optimization for S3 calls, so there isn't head+get, but only one request*/
16
+ getIfChanged(key: string, etag: string): Effect.Effect<Option.Option<{
17
+ value: Uint8Array;
18
+ etag: string;
19
+ }>>;
20
+ exists(key: string): Effect.Effect<boolean>;
21
+ set(key: string, value: Uint8Array): Effect.Effect<{
22
+ etag: string;
23
+ }>;
24
+ cas(key: string, value: Uint8Array, etag: string): Effect.Effect<{
25
+ etag: string;
26
+ }, ConflictError>;
27
+ delete(key: string): Effect.Effect<void>;
28
+ readStream(key: string): Effect.Effect<Option.Option<Stream.Stream<Uint8Array>>>;
29
+ writeStream(key: string, content: Stream.Stream<Uint8Array>): Effect.Effect<{
30
+ etag: string;
31
+ }>;
32
+ };
33
+ export type CloneTrait = {
34
+ clone(from: string, to: string): Effect.Effect<void>;
35
+ };
36
+ export type ConnectDatabaseTrait = {
37
+ /**
38
+ * Creates connection with CDC full enabled
39
+ */
40
+ connect(key: string): Effect.Effect<Database, Error, Scope.Scope>;
41
+ };
42
+ export {};
@@ -0,0 +1,5 @@
1
+ import { FileSystem } from "@effect/platform/FileSystem";
2
+ import { Effect } from "effect";
3
+ import type { Scope } from "effect";
4
+ import type { CloneTrait, ConnectDatabaseTrait, KV } from "./kv.js";
5
+ export declare const makeMemoryKV: () => Effect.Effect<KV & CloneTrait & ConnectDatabaseTrait, never, FileSystem | Scope.Scope>;
@@ -0,0 +1,4 @@
1
+ import type { S3Service } from "@effect-aws/client-s3";
2
+ import { Effect } from "effect";
3
+ import type { KV } from "./kv.js";
4
+ export declare const makeS3KV: (bucket: string) => Effect.Effect<KV, never, S3Service>;
@@ -0,0 +1,4 @@
1
+ import { Effect } from "effect";
2
+ import { LocalKV, RemoteKV } from "../storage.js";
3
+ export declare const pullFiles: (keys: string[]) => Effect.Effect<undefined[], Error, LocalKV | RemoteKV>;
4
+ export declare const pushFiles: (keys: string[]) => Effect.Effect<undefined[], Error, LocalKV | RemoteKV>;
@@ -0,0 +1,8 @@
1
+ import type { Scope } from "effect";
2
+ import { Effect, Option } from "effect";
3
+ import type { ExtractedBatch } from "./batches.js";
4
+ import { ConnectionConfig, ConnectionState } from "./contexts.js";
5
+ import { LocalKV, RemoteKV } from "./storage.js";
6
+ export declare const pull: () => Effect.Effect<{
7
+ newLocalBatch: Option.Option<ExtractedBatch>;
8
+ }, Error, ConnectionConfig | ConnectionState | LocalKV | RemoteKV | Scope.Scope>;
@@ -0,0 +1,4 @@
1
+ import { Effect } from "effect";
2
+ import { ConnectionConfig, ConnectionState } from "./contexts.js";
3
+ import { LocalKV, RemoteKV } from "./storage.js";
4
+ export declare const push: () => Effect.Effect<void, import(".").ConflictError, LocalKV | RemoteKV | ConnectionConfig | ConnectionState>;