@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 @@
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};"]}
@@ -0,0 +1,33 @@
1
+ import { Effect } from "effect";
2
+ const query = `
3
+ SELECT
4
+ change_id AS changeId,
5
+ change_time AS changeTime,
6
+ change_txn_id AS changeTxnId,
7
+ change_type AS changeType,
8
+ table_name AS tableName,
9
+ id,
10
+ before,
11
+ after,
12
+ updates
13
+ FROM turso_cdc
14
+ WHERE change_id > ?
15
+ AND change_type != 2
16
+ ORDER BY change_id ASC
17
+ `;
18
+ const latestChangeIdQuery = `
19
+ SELECT COALESCE(MAX(change_id), 0) AS changeId
20
+ FROM turso_cdc
21
+ `;
22
+ /**
23
+ * Extracts CDC rows from the Turso database after a given change_id exclusively.
24
+ */
25
+ export const extractCDC = (tursoConnection, afterId) => Effect.tryPromise(() => tursoConnection.all(query, afterId)).pipe(Effect.orDie, Effect.tap((r) => Effect.logDebug(`extractCDC: extracted ${r.length} rows after change_id ${afterId}`).pipe(Effect.orDie)));
26
+ export const getLatestChangeId = (tursoConnection) => Effect.tryPromise({
27
+ try: async () => {
28
+ const [row] = (await tursoConnection.all(latestChangeIdQuery));
29
+ return row?.changeId ?? 0;
30
+ },
31
+ catch: (error) => new Error(`Failed to read latest CDC change id: ${String(error)}`),
32
+ });
33
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../../../../src/cdc/extract.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;CAeb,CAAC;AAEF,MAAM,mBAAmB,GAAG;;;CAG3B,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CACzB,eAAyB,EACzB,OAAe,EACoB,EAAE,CACrC,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAsB,CAAC,CAAC,IAAI,CACrF,MAAM,CAAC,KAAK,EACZ,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAChB,MAAM,CAAC,QAAQ,CACd,yBAAyB,CAAC,CAAC,MAAM,yBAAyB,OAAO,EAAE,CACnE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CACpB,CACD,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,eAAyB,EAAgC,EAAE,CAC5F,MAAM,CAAC,UAAU,CAAC;IACjB,GAAG,EAAE,KAAK,IAAI,EAAE;QACf,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,eAAe,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAG1D,CAAC;QACJ,OAAO,GAAG,EAAE,QAAQ,IAAI,CAAC,CAAC;IAC3B,CAAC;IACD,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,wCAAwC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;CACpF,CAAC,CAAC","sourcesContent":["import type { Database } from \"@tursodatabase/database\";\nimport { Effect } from \"effect\";\n\nimport type { CDCRow } from \"./types\";\n\nconst query = `\n\tSELECT\n\t\tchange_id AS changeId,\n\t\tchange_time AS changeTime,\n\t\tchange_txn_id AS changeTxnId,\n\t\tchange_type AS changeType,\n\t\ttable_name AS tableName,\n\t\tid,\n\t\tbefore,\n\t\tafter,\n\t\tupdates\n\tFROM turso_cdc\n\tWHERE change_id > ?\n\t\tAND change_type != 2\n\tORDER BY change_id ASC\n`;\n\nconst latestChangeIdQuery = `\n\tSELECT COALESCE(MAX(change_id), 0) AS changeId\n\tFROM turso_cdc\n`;\n\n/**\n * Extracts CDC rows from the Turso database after a given change_id exclusively.\n */\nexport const extractCDC = (\n\ttursoConnection: Database,\n\tafterId: number,\n): Effect.Effect<readonly CDCRow[]> =>\n\tEffect.tryPromise(() => tursoConnection.all(query, afterId) as Promise<CDCRow[]>).pipe(\n\t\tEffect.orDie,\n\t\tEffect.tap((r) =>\n\t\t\tEffect.logDebug(\n\t\t\t\t`extractCDC: extracted ${r.length} rows after change_id ${afterId}`,\n\t\t\t).pipe(Effect.orDie),\n\t\t),\n\t);\n\nexport const getLatestChangeId = (tursoConnection: Database): Effect.Effect<number, Error> =>\n\tEffect.tryPromise({\n\t\ttry: async () => {\n\t\t\tconst [row] = (await tursoConnection.all(latestChangeIdQuery)) as {\n\t\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- SQLite aggregate MAX returns NULL when the CDC table is empty.\n\t\t\t\tchangeId: number | null;\n\t\t\t}[];\n\t\t\treturn row?.changeId ?? 0;\n\t\t},\n\t\tcatch: (error) => new Error(`Failed to read latest CDC change id: ${String(error)}`),\n\t});"]}
@@ -0,0 +1,127 @@
1
+ import protobuf from "protobufjs";
2
+ const schema = protobuf.parse(`
3
+ syntax = "proto3";
4
+
5
+ message CdcValue {
6
+ oneof kind {
7
+ bool null_value = 1;
8
+ double number_value = 2;
9
+ string string_value = 3;
10
+ bytes bytes_value = 4;
11
+ }
12
+ }
13
+
14
+ message CdcRow {
15
+ sint64 change_id = 1;
16
+ sint64 change_time = 2;
17
+ sint64 change_txn_id = 3;
18
+ sint32 change_type = 4;
19
+ string table_name = 5;
20
+ CdcValue id = 6;
21
+ bytes before = 7;
22
+ bytes after = 8;
23
+ bytes updates = 9;
24
+ }
25
+
26
+ message CdcBatch {
27
+ repeated CdcRow changes = 1;
28
+ }
29
+ `).root;
30
+ const batchType = schema.lookupType("CdcBatch");
31
+ const encodeValue = (value) => {
32
+ // oxlint-disable-next-line local-rules/no-null-undefined-option -- CDC payloads must preserve SQL NULL values.
33
+ if (value === null) {
34
+ return { kind: "nullValue", nullValue: true };
35
+ }
36
+ if (typeof value === "number") {
37
+ return { kind: "numberValue", numberValue: value };
38
+ }
39
+ if (typeof value === "string") {
40
+ return { kind: "stringValue", stringValue: value };
41
+ }
42
+ return { kind: "bytesValue", bytesValue: value };
43
+ };
44
+ // oxlint-disable-next-line local-rules/no-null-undefined-option -- protobufjs omits absent optional fields as undefined on decode.
45
+ const decodeValue = (value) => {
46
+ if (!value) {
47
+ // oxlint-disable-next-line local-rules/no-null-undefined-option -- missing protobuf field maps back to SQL NULL.
48
+ return null;
49
+ }
50
+ switch (value.kind) {
51
+ case "nullValue":
52
+ // oxlint-disable-next-line local-rules/no-null-undefined-option -- protobuf null arm maps to SQL NULL.
53
+ return null;
54
+ case "numberValue":
55
+ return value.numberValue;
56
+ case "stringValue":
57
+ return value.stringValue;
58
+ case "bytesValue":
59
+ return Uint8Array.from(value.bytesValue);
60
+ }
61
+ };
62
+ const encodeRow = (row) => ({
63
+ changeId: row.changeId,
64
+ changeTime: row.changeTime,
65
+ changeTxnId: row.changeTxnId,
66
+ changeType: row.changeType,
67
+ // oxlint-disable-next-line local-rules/no-null-undefined-option -- protobufjs optional fields are omitted with undefined during encoding.
68
+ ...(row.tableName === null ? {} : { tableName: row.tableName }),
69
+ // oxlint-disable-next-line local-rules/no-null-undefined-option -- protobufjs optional fields are omitted with undefined during encoding.
70
+ ...(row.id === null ? {} : { id: encodeValue(row.id) }),
71
+ // oxlint-disable-next-line local-rules/no-null-undefined-option -- protobufjs optional fields are omitted with undefined during encoding.
72
+ ...(row.before === null ? {} : { before: row.before }),
73
+ // oxlint-disable-next-line local-rules/no-null-undefined-option -- protobufjs optional fields are omitted with undefined during encoding.
74
+ ...(row.after === null ? {} : { after: row.after }),
75
+ // oxlint-disable-next-line local-rules/no-null-undefined-option -- protobufjs optional fields are omitted with undefined during encoding.
76
+ ...(row.updates === null ? {} : { updates: row.updates }),
77
+ });
78
+ const decodeRow = (row) => {
79
+ const changeType = row.changeType;
80
+ if (changeType === -1) {
81
+ return {
82
+ changeId: Number(row.changeId),
83
+ changeTime: Number(row.changeTime),
84
+ changeTxnId: Number(row.changeTxnId),
85
+ changeType,
86
+ tableName: row.tableName || "",
87
+ id: decodeValue(row.id),
88
+ before: row.before ? Uint8Array.from(row.before) : new Uint8Array(),
89
+ // oxlint-disable-next-line local-rules/no-null-undefined-option -- Delete CDC rows have no post-delete image.
90
+ after: null,
91
+ // oxlint-disable-next-line local-rules/no-null-undefined-option -- Delete CDC rows have no updates mask.
92
+ updates: null,
93
+ };
94
+ }
95
+ if (changeType === 0) {
96
+ return {
97
+ changeId: Number(row.changeId),
98
+ changeTime: Number(row.changeTime),
99
+ changeTxnId: Number(row.changeTxnId),
100
+ changeType,
101
+ tableName: row.tableName || "",
102
+ id: decodeValue(row.id),
103
+ before: row.before ? Uint8Array.from(row.before) : new Uint8Array(),
104
+ after: row.after ? Uint8Array.from(row.after) : new Uint8Array(),
105
+ updates: row.updates ? Uint8Array.from(row.updates) : new Uint8Array(),
106
+ };
107
+ }
108
+ if (changeType === 1) {
109
+ return {
110
+ changeId: Number(row.changeId),
111
+ changeTime: Number(row.changeTime),
112
+ changeTxnId: Number(row.changeTxnId),
113
+ changeType,
114
+ tableName: row.tableName || "",
115
+ id: decodeValue(row.id),
116
+ // oxlint-disable-next-line local-rules/no-null-undefined-option -- Insert CDC rows have no previous image.
117
+ before: null,
118
+ after: row.after ? Uint8Array.from(row.after) : new Uint8Array(),
119
+ // oxlint-disable-next-line local-rules/no-null-undefined-option -- Insert CDC rows have no updates mask.
120
+ updates: null,
121
+ };
122
+ }
123
+ throw new Error(`Unsupported CDC change type ${String(row.changeType)}`);
124
+ };
125
+ export const serializeCDC = (changes) => batchType.encode({ changes: changes.map(encodeRow) }).finish();
126
+ export const deserializeCDC = (bytes) => batchType.decode(bytes).changes.map(decodeRow);
127
+ //# sourceMappingURL=protobuf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protobuf.js","sourceRoot":"","sources":["../../../../src/cdc/protobuf.ts"],"names":[],"mappings":"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","sourcesContent":["import protobuf from \"protobufjs\";\n\nimport type { CDCRow, CDCValue } from \"./types\";\n\nconst schema = protobuf.parse(`\nsyntax = \"proto3\";\n\nmessage CdcValue {\n\toneof kind {\n\t\tbool null_value = 1;\n\t\tdouble number_value = 2;\n\t\tstring string_value = 3;\n\t\tbytes bytes_value = 4;\n\t}\n}\n\nmessage CdcRow {\n\tsint64 change_id = 1;\n\tsint64 change_time = 2;\n\tsint64 change_txn_id = 3;\n\tsint32 change_type = 4;\n\tstring table_name = 5;\n\tCdcValue id = 6;\n\tbytes before = 7;\n\tbytes after = 8;\n\tbytes updates = 9;\n}\n\nmessage CdcBatch {\n\trepeated CdcRow changes = 1;\n}\n`).root;\n\nconst batchType = schema.lookupType(\"CdcBatch\") as protobuf.Type;\n\ntype ValueMessage =\n\t| { kind: \"nullValue\"; nullValue: boolean }\n\t| { kind: \"numberValue\"; numberValue: number }\n\t| { kind: \"stringValue\"; stringValue: string }\n\t| { kind: \"bytesValue\"; bytesValue: Uint8Array };\n\ntype RowMessage = {\n\tchangeId: number | bigint;\n\tchangeTime: number | bigint;\n\tchangeTxnId: number | bigint;\n\tchangeType: number;\n\ttableName?: string;\n\tid?: ValueMessage;\n\tbefore?: Uint8Array;\n\tafter?: Uint8Array;\n\tupdates?: Uint8Array;\n};\n\ntype BatchMessage = {\n\tchanges: readonly RowMessage[];\n};\n\nconst encodeValue = (value: CDCValue): ValueMessage => {\n\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- CDC payloads must preserve SQL NULL values.\n\tif (value === null) {\n\t\treturn { kind: \"nullValue\", nullValue: true };\n\t}\n\tif (typeof value === \"number\") {\n\t\treturn { kind: \"numberValue\", numberValue: value };\n\t}\n\tif (typeof value === \"string\") {\n\t\treturn { kind: \"stringValue\", stringValue: value };\n\t}\n\treturn { kind: \"bytesValue\", bytesValue: value };\n};\n\n// oxlint-disable-next-line local-rules/no-null-undefined-option -- protobufjs omits absent optional fields as undefined on decode.\nconst decodeValue = (value: ValueMessage | undefined): CDCValue => {\n\tif (!value) {\n\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- missing protobuf field maps back to SQL NULL.\n\t\treturn null;\n\t}\n\tswitch (value.kind) {\n\t\tcase \"nullValue\":\n\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- protobuf null arm maps to SQL NULL.\n\t\t\treturn null;\n\t\tcase \"numberValue\":\n\t\t\treturn value.numberValue;\n\t\tcase \"stringValue\":\n\t\t\treturn value.stringValue;\n\t\tcase \"bytesValue\":\n\t\t\treturn Uint8Array.from(value.bytesValue);\n\t}\n};\n\nconst encodeRow = (row: CDCRow): RowMessage => ({\n\tchangeId: row.changeId,\n\tchangeTime: row.changeTime,\n\tchangeTxnId: row.changeTxnId,\n\tchangeType: row.changeType,\n\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- protobufjs optional fields are omitted with undefined during encoding.\n\t...(row.tableName === null ? {} : { tableName: row.tableName }),\n\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- protobufjs optional fields are omitted with undefined during encoding.\n\t...(row.id === null ? {} : { id: encodeValue(row.id) }),\n\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- protobufjs optional fields are omitted with undefined during encoding.\n\t...(row.before === null ? {} : { before: row.before }),\n\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- protobufjs optional fields are omitted with undefined during encoding.\n\t...(row.after === null ? {} : { after: row.after }),\n\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- protobufjs optional fields are omitted with undefined during encoding.\n\t...(row.updates === null ? {} : { updates: row.updates }),\n});\n\nconst decodeRow = (row: RowMessage): CDCRow => {\n\tconst changeType = row.changeType as CDCRow[\"changeType\"];\n\tif (changeType === -1) {\n\t\treturn {\n\t\t\tchangeId: Number(row.changeId),\n\t\t\tchangeTime: Number(row.changeTime),\n\t\t\tchangeTxnId: Number(row.changeTxnId),\n\t\t\tchangeType,\n\t\t\ttableName: row.tableName || \"\",\n\t\t\tid: decodeValue(row.id),\n\t\t\tbefore: row.before ? Uint8Array.from(row.before) : new Uint8Array(),\n\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- Delete CDC rows have no post-delete image.\n\t\t\tafter: null,\n\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- Delete CDC rows have no updates mask.\n\t\t\tupdates: null,\n\t\t};\n\t}\n\tif (changeType === 0) {\n\t\treturn {\n\t\t\tchangeId: Number(row.changeId),\n\t\t\tchangeTime: Number(row.changeTime),\n\t\t\tchangeTxnId: Number(row.changeTxnId),\n\t\t\tchangeType,\n\t\t\ttableName: row.tableName || \"\",\n\t\t\tid: decodeValue(row.id),\n\t\t\tbefore: row.before ? Uint8Array.from(row.before) : new Uint8Array(),\n\t\t\tafter: row.after ? Uint8Array.from(row.after) : new Uint8Array(),\n\t\t\tupdates: row.updates ? Uint8Array.from(row.updates) : new Uint8Array(),\n\t\t};\n\t}\n\tif (changeType === 1) {\n\t\treturn {\n\t\t\tchangeId: Number(row.changeId),\n\t\t\tchangeTime: Number(row.changeTime),\n\t\t\tchangeTxnId: Number(row.changeTxnId),\n\t\t\tchangeType,\n\t\t\ttableName: row.tableName || \"\",\n\t\t\tid: decodeValue(row.id),\n\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- Insert CDC rows have no previous image.\n\t\t\tbefore: null,\n\t\t\tafter: row.after ? Uint8Array.from(row.after) : new Uint8Array(),\n\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- Insert CDC rows have no updates mask.\n\t\t\tupdates: null,\n\t\t};\n\t}\n\tthrow new Error(`Unsupported CDC change type ${String(row.changeType)}`);\n};\n\nexport const serializeCDC = (changes: readonly CDCRow[]): Uint8Array =>\n\tbatchType.encode({ changes: changes.map(encodeRow) } as BatchMessage).finish();\n\nexport const deserializeCDC = (bytes: Uint8Array): readonly CDCRow[] =>\n\t(batchType.decode(bytes) as unknown as BatchMessage).changes.map(decodeRow);"]}
@@ -0,0 +1,42 @@
1
+ import { Effect } from "effect";
2
+ export const compareSnapshots = (left, right) => JSON.stringify(left) === JSON.stringify(right);
3
+ export const snapshotDatabase = (database) => Effect.gen(function* () {
4
+ const schema = yield* Effect.tryPromise(() => database.all([
5
+ "SELECT type, name, tbl_name AS tblName, sql",
6
+ "FROM sqlite_schema",
7
+ "WHERE name NOT LIKE 'sqlite_%' AND name NOT LIKE 'turso_%'",
8
+ "ORDER BY type, name",
9
+ ].join(" ")));
10
+ const tableSnapshots = yield* Effect.forEach(schema.filter((entry) => entry.type === "table"), (entry) => Effect.gen(function* () {
11
+ const rows = yield* Effect.tryPromise(() => database.all(`SELECT * FROM "${entry.name.replaceAll('"', '""')}"`));
12
+ const normalize = (row) => JSON.stringify(Object.fromEntries(Object.entries(row).map(([key, value]) => [
13
+ key,
14
+ value instanceof Uint8Array
15
+ ? { blob: Array.from(value) }
16
+ : value,
17
+ ])));
18
+ return [
19
+ entry.name,
20
+ [...rows].sort((left, right) => normalize(left).localeCompare(normalize(right))),
21
+ ];
22
+ }), { concurrency: 1 });
23
+ return {
24
+ schema: schema.map((entry) => ({
25
+ ...entry,
26
+ sql: entry.sql
27
+ ? entry.sql.replace(/^(CREATE\s+(?:UNIQUE\s+)?(?:TABLE|INDEX|TRIGGER|VIEW|MATERIALIZED\s+VIEW)\s+)IF\s+NOT\s+EXISTS\s+/i, "$1")
28
+ : // oxlint-disable-next-line local-rules/no-null-undefined-option -- snapshotting must preserve sqlite_schema NULL SQL entries.
29
+ null,
30
+ })),
31
+ tables: Object.fromEntries(tableSnapshots),
32
+ };
33
+ });
34
+ export const compareDatabases = (left, right) => Effect.all([snapshotDatabase(left), snapshotDatabase(right)], { concurrency: 1 }).pipe(Effect.map(([leftSnapshot, rightSnapshot]) => compareSnapshots(leftSnapshot, rightSnapshot)));
35
+ export const applyBatch = (database, batch) => batch.transactional
36
+ ? Effect.gen(function* () {
37
+ yield* Effect.tryPromise(() => database.exec("BEGIN IMMEDIATE")).pipe(Effect.asVoid);
38
+ yield* Effect.forEach(batch.statements, (statement) => Effect.tryPromise(() => database.exec(statement)).pipe(Effect.asVoid), { concurrency: 1, discard: true });
39
+ yield* Effect.tryPromise(() => database.exec("COMMIT")).pipe(Effect.asVoid);
40
+ }).pipe(Effect.catchAll((error) => Effect.tryPromise(() => database.exec("ROLLBACK")).pipe(Effect.asVoid, Effect.catchAll(() => Effect.void), Effect.andThen(Effect.fail(error)))))
41
+ : Effect.forEach(batch.statements, (statement) => Effect.tryPromise(() => database.exec(statement)).pipe(Effect.asVoid), { concurrency: 1, discard: true });
42
+ //# sourceMappingURL=testUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testUtils.js","sourceRoot":"","sources":["../../../../src/cdc/testUtils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Database } from \"@tursodatabase/database\";\nimport { Effect } from \"effect\";\n\nexport type SchemaEntry = {\n\ttype: string;\n\tname: string;\n\ttblName: string;\n\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- sqlite_schema.sql is NULL for implicit indexes and similar internal objects.\n\tsql: string | null;\n};\n\nexport type DatabaseSnapshot = {\n\tschema: readonly SchemaEntry[];\n\ttables: Readonly<Record<string, readonly Record<string, unknown>[]>>;\n};\n\nexport const compareSnapshots = (left: DatabaseSnapshot, right: DatabaseSnapshot): boolean =>\n\tJSON.stringify(left) === JSON.stringify(right);\n\nexport const snapshotDatabase = (database: Database): Effect.Effect<DatabaseSnapshot, Error> =>\n\tEffect.gen(function* () {\n\t\tconst schema = yield* Effect.tryPromise(\n\t\t\t() =>\n\t\t\t\tdatabase.all(\n\t\t\t\t\t[\n\t\t\t\t\t\t\"SELECT type, name, tbl_name AS tblName, sql\",\n\t\t\t\t\t\t\"FROM sqlite_schema\",\n\t\t\t\t\t\t\"WHERE name NOT LIKE 'sqlite_%' AND name NOT LIKE 'turso_%'\",\n\t\t\t\t\t\t\"ORDER BY type, name\",\n\t\t\t\t\t].join(\" \"),\n\t\t\t\t) as Promise<readonly SchemaEntry[]>,\n\t\t);\n\t\tconst tableSnapshots = yield* Effect.forEach(\n\t\t\tschema.filter((entry) => entry.type === \"table\"),\n\t\t\t(entry) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst rows = yield* Effect.tryPromise(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tdatabase.all(\n\t\t\t\t\t\t\t\t`SELECT * FROM \"${entry.name.replaceAll('\"', '\"\"')}\"`,\n\t\t\t\t\t\t\t) as Promise<readonly Record<string, unknown>[]>,\n\t\t\t\t\t);\n\t\t\t\t\tconst normalize = (row: Record<string, unknown>): string =>\n\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\tObject.fromEntries(\n\t\t\t\t\t\t\t\tObject.entries(row).map(([key, value]) => [\n\t\t\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t\t\t\tvalue instanceof Uint8Array\n\t\t\t\t\t\t\t\t\t\t? { blob: Array.from(value) }\n\t\t\t\t\t\t\t\t\t\t: value,\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\treturn [\n\t\t\t\t\t\tentry.name,\n\t\t\t\t\t\t[...rows].sort((left, right) =>\n\t\t\t\t\t\t\tnormalize(left).localeCompare(normalize(right)),\n\t\t\t\t\t\t),\n\t\t\t\t\t] as const;\n\t\t\t\t}),\n\t\t\t{ concurrency: 1 },\n\t\t);\n\t\treturn {\n\t\t\tschema: schema.map((entry) => ({\n\t\t\t\t...entry,\n\t\t\t\tsql: entry.sql\n\t\t\t\t\t? entry.sql.replace(\n\t\t\t\t\t\t\t/^(CREATE\\s+(?:UNIQUE\\s+)?(?:TABLE|INDEX|TRIGGER|VIEW|MATERIALIZED\\s+VIEW)\\s+)IF\\s+NOT\\s+EXISTS\\s+/i,\n\t\t\t\t\t\t\t\"$1\",\n\t\t\t\t\t\t)\n\t\t\t\t\t: // oxlint-disable-next-line local-rules/no-null-undefined-option -- snapshotting must preserve sqlite_schema NULL SQL entries.\n\t\t\t\t\t\tnull,\n\t\t\t})),\n\t\t\ttables: Object.fromEntries(tableSnapshots),\n\t\t};\n\t});\n\nexport const compareDatabases = (left: Database, right: Database): Effect.Effect<boolean, Error> =>\n\tEffect.all([snapshotDatabase(left), snapshotDatabase(right)], { concurrency: 1 }).pipe(\n\t\tEffect.map(([leftSnapshot, rightSnapshot]) =>\n\t\t\tcompareSnapshots(leftSnapshot, rightSnapshot),\n\t\t),\n\t);\n\nexport const applyBatch = (\n\tdatabase: Database,\n\tbatch: {\n\t\treadonly transactional: boolean;\n\t\treadonly statements: readonly string[];\n\t},\n): Effect.Effect<void, Error> =>\n\tbatch.transactional\n\t\t? Effect.gen(function* () {\n\t\t\t\tyield* Effect.tryPromise(() => database.exec(\"BEGIN IMMEDIATE\")).pipe(\n\t\t\t\t\tEffect.asVoid,\n\t\t\t\t);\n\t\t\t\tyield* Effect.forEach(\n\t\t\t\t\tbatch.statements,\n\t\t\t\t\t(statement) =>\n\t\t\t\t\t\tEffect.tryPromise(() => database.exec(statement)).pipe(Effect.asVoid),\n\t\t\t\t\t{ concurrency: 1, discard: true },\n\t\t\t\t);\n\t\t\t\tyield* Effect.tryPromise(() => database.exec(\"COMMIT\")).pipe(Effect.asVoid);\n\t\t\t}).pipe(\n\t\t\t\tEffect.catchAll((error) =>\n\t\t\t\t\tEffect.tryPromise(() => database.exec(\"ROLLBACK\")).pipe(\n\t\t\t\t\t\tEffect.asVoid,\n\t\t\t\t\t\tEffect.catchAll(() => Effect.void),\n\t\t\t\t\t\tEffect.andThen(Effect.fail(error)),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t)\n\t\t: Effect.forEach(\n\t\t\t\tbatch.statements,\n\t\t\t\t(statement) =>\n\t\t\t\t\tEffect.tryPromise(() => database.exec(statement)).pipe(Effect.asVoid),\n\t\t\t\t{ concurrency: 1, discard: true },\n\t\t\t);"]}
@@ -0,0 +1,3 @@
1
+ import { Effect } from "effect";
2
+ export const truncate = (db) => Effect.promise(() => db.run("PRAGMA wal_checkpoint(TRUNCATE);"));
3
+ //# sourceMappingURL=truncate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"truncate.js","sourceRoot":"","sources":["../../../../src/cdc/truncate.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EAAY,EAAE,EAAE,CACxC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC,CAAC","sourcesContent":["import type { Database } from \"@tursodatabase/database\";\nimport { Effect } from \"effect\";\n\nexport const truncate = (db: Database) =>\n\tEffect.promise(() => db.run(\"PRAGMA wal_checkpoint(TRUNCATE);\"));"]}
@@ -0,0 +1,7 @@
1
+ export const CDCChangeType = {
2
+ Delete: -1,
3
+ Update: 0,
4
+ Insert: 1,
5
+ // Commit: 2,
6
+ };
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/cdc/types.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,aAAa,GAAG;IAC5B,MAAM,EAAE,CAAC,CAAC;IACV,MAAM,EAAE,CAAC;IACT,MAAM,EAAE,CAAC;IACT,aAAa;CACJ,CAAC","sourcesContent":["// oxlint-disable-next-line local-rules/no-null-undefined-option -- SQLite CDC rows must preserve SQL NULL values.\nexport type CDCValue = number | string | Uint8Array | null;\n\nexport const CDCChangeType = {\n\tDelete: -1,\n\tUpdate: 0,\n\tInsert: 1,\n\t// Commit: 2,\n} as const;\n\nexport type CDCChangeType = (typeof CDCChangeType)[keyof typeof CDCChangeType];\n\ntype CDCBase = {\n\tchangeId: number;\n\tchangeTime: number;\n\tchangeTxnId: number;\n};\n\nexport type CDCRow =\n\t| (CDCBase & {\n\t\t\tchangeType: typeof CDCChangeType.Insert;\n\t\t\ttableName: string;\n\t\t\tid: CDCValue;\n\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- Insert CDC rows have no previous record image.\n\t\t\tbefore: Uint8Array | null;\n\t\t\tafter: Uint8Array;\n\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- Insert CDC rows have no updates mask.\n\t\t\tupdates: Uint8Array | null;\n\t })\n\t| (CDCBase & {\n\t\t\tchangeType: typeof CDCChangeType.Update;\n\t\t\ttableName: string;\n\t\t\tid: CDCValue;\n\t\t\tbefore: Uint8Array;\n\t\t\tafter: Uint8Array;\n\t\t\tupdates: Uint8Array;\n\t })\n\t| (CDCBase & {\n\t\t\tchangeType: typeof CDCChangeType.Delete;\n\t\t\ttableName: string;\n\t\t\tid: CDCValue;\n\t\t\tbefore: Uint8Array;\n\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- Delete CDC rows have no post-delete record image.\n\t\t\tafter: Uint8Array | null;\n\t\t\t// oxlint-disable-next-line local-rules/no-null-undefined-option -- Delete CDC rows have no updates mask.\n\t\t\tupdates: Uint8Array | null;\n\t });"]}
@@ -0,0 +1,3 @@
1
+ import { Effect } from "effect";
2
+ export const withoutCDC = (database, effect) => Effect.acquireUseRelease(Effect.promise(() => database.exec("PRAGMA capture_data_changes_conn('off')")), () => effect, () => Effect.promise(() => database.exec("PRAGMA capture_data_changes_conn('full')")));
3
+ //# sourceMappingURL=withoutCDC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withoutCDC.js","sourceRoot":"","sources":["../../../../src/cdc/withoutCDC.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,CAAC,MAAM,UAAU,GAAG,CACzB,QAAkB,EAClB,MAA8B,EACL,EAAE,CAC3B,MAAM,CAAC,iBAAiB,CACvB,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC,EAC9E,GAAG,EAAE,CAAC,MAAM,EACZ,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC,CACrF,CAAC","sourcesContent":["import type { Database } from \"@tursodatabase/database\";\nimport { Effect } from \"effect\";\n\nexport const withoutCDC = <A, E, R>(\n\tdatabase: Database,\n\teffect: Effect.Effect<A, E, R>,\n): Effect.Effect<A, E, R> =>\n\tEffect.acquireUseRelease(\n\t\tEffect.promise(() => database.exec(\"PRAGMA capture_data_changes_conn('off')\")),\n\t\t() => effect,\n\t\t() => Effect.promise(() => database.exec(\"PRAGMA capture_data_changes_conn('full')\")),\n\t);"]}
@@ -0,0 +1,123 @@
1
+ import { Context, Effect, Option, Ref } from "effect";
2
+ import { applyBatch } from "./batches.js";
3
+ import { getLatestChangeId } from "./cdc/extract.js";
4
+ import { truncate } from "./cdc/truncate.js";
5
+ import { ConnectionConfig, ConnectionState } from "./contexts.js";
6
+ import { makeFileKV } from "./kv/fileKV.js";
7
+ import { makeS3KV } from "./kv/s3KV.js";
8
+ import { pullFiles } from "./kv/syncFiles.js";
9
+ import { pull } from "./pull.js";
10
+ import { push } from "./push.js";
11
+ import { LocalKV, RemoteKV, baseKey, batchKey, dbKey, encodeJson, getJson, headKey, snapshotKey, } from "./storage.js";
12
+ import { wrapDatabase } from "./wrapDatabase.js";
13
+ export const initializeContexts = (connectionOptions) => Effect.gen(function* () {
14
+ const s3kv = yield* makeS3KV(connectionOptions.bucket);
15
+ const filekv = yield* makeFileKV(connectionOptions.localDirectory ?? "./.s3qlite/");
16
+ return Context.empty().pipe(Context.add(RemoteKV, s3kv), Context.add(LocalKV, filekv));
17
+ });
18
+ export const connect = (dbName, options) => Effect.gen(function* () {
19
+ const remote = yield* RemoteKV;
20
+ const local = yield* LocalKV;
21
+ const { localHead, remoteHead } = yield* Effect.all({
22
+ localHead: getJson(local, headKey(dbName)),
23
+ remoteHead: getJson(remote, headKey(dbName)),
24
+ // localDb: local.get(dbKey(dbName)),
25
+ });
26
+ if (Option.isNone(localHead) && Option.isNone(remoteHead)) {
27
+ const snapshotId = crypto.randomUUID();
28
+ // connection is scoped, will die when the effect is done
29
+ const connection = yield* local.connect(dbKey(dbName));
30
+ yield* truncate(connection);
31
+ yield* Effect.promise(() => connection.close());
32
+ const head = {
33
+ snapshots: [
34
+ {
35
+ id: snapshotId,
36
+ batchIdsApplied: [],
37
+ },
38
+ ],
39
+ batches: [],
40
+ };
41
+ // TODO: this should be atomic, and the head(s) probably updated only after snapshot is written.
42
+ const { remoteResult } = yield* Effect.all({
43
+ remoteResult: remote.set(headKey(dbName), encodeJson(head)),
44
+ snapshot: local.clone(dbKey(dbName), snapshotKey(snapshotId)).pipe(Effect.flatMap(() => local.readStream(snapshotKey(snapshotId))), Effect.flatMap(Option.match({
45
+ onNone: () => Effect.die(new Error("Snapshot not found after clone")),
46
+ onSome: (stream) => remote.writeStream(snapshotKey(snapshotId), stream),
47
+ }))),
48
+ });
49
+ yield* local.set(headKey(dbName), encodeJson({
50
+ head,
51
+ remoteEtag: remoteResult.etag,
52
+ lastSyncedLocalChangeId: 0,
53
+ }));
54
+ }
55
+ if (Option.isSome(remoteHead) && Option.isNone(localHead)) {
56
+ const head = remoteHead.value.value;
57
+ const latestSnapshot = head.snapshots.at(-1);
58
+ if (!latestSnapshot) {
59
+ return yield* Effect.die(new Error("Remote head has no snapshots"));
60
+ }
61
+ const lastAppliedBatchId = latestSnapshot.batchIdsApplied.at(-1);
62
+ const batchStartIndex = lastAppliedBatchId
63
+ ? head.batches.findIndex((b) => b.id === lastAppliedBatchId) + 1
64
+ : 0;
65
+ const batchesToDownload = head.batches.slice(batchStartIndex);
66
+ yield* pullFiles([
67
+ snapshotKey(latestSnapshot.id),
68
+ ...batchesToDownload.map((batch) => batchKey(batch.id)),
69
+ ]);
70
+ yield* local.clone(snapshotKey(latestSnapshot.id), dbKey(dbName));
71
+ const db = yield* local.connect(dbKey(dbName));
72
+ for (const batch of batchesToDownload) {
73
+ yield* applyBatch(db, batch);
74
+ }
75
+ const lastSyncedLocalChangeId = yield* getLatestChangeId(db);
76
+ yield* truncate(db);
77
+ yield* Effect.promise(() => db.close());
78
+ const lastBatchId = batchesToDownload.at(-1)?.id;
79
+ if (lastBatchId) {
80
+ yield* local.clone(dbKey(dbName), baseKey(latestSnapshot.id, lastBatchId));
81
+ }
82
+ yield* local.set(headKey(dbName), encodeJson({
83
+ head,
84
+ remoteEtag: remoteHead.value.etag,
85
+ lastSyncedLocalChangeId,
86
+ }));
87
+ }
88
+ if (Option.isNone(remoteHead) && Option.isSome(localHead)) {
89
+ return yield* Effect.die(new Error("Local head exists but remote head does not. This is an unexpected state."));
90
+ }
91
+ if (Option.isSome(remoteHead) && Option.isSome(localHead)) {
92
+ // do nothing?
93
+ }
94
+ const connectionConfigValue = {
95
+ bucket: options.bucket,
96
+ dbName,
97
+ livePath: dbKey(dbName),
98
+ localDirectory: options.localDirectory ?? "./.s3qlite/",
99
+ localHeadPath: headKey(dbName),
100
+ options,
101
+ };
102
+ const connection = yield* local.connect(dbKey(dbName));
103
+ let currentConnection = connection;
104
+ const connectionRef = yield* Ref.make(connection);
105
+ const connectionStateValue = {
106
+ getConnection: Ref.get(connectionRef),
107
+ setConnection: (conn) => Effect.sync(() => {
108
+ currentConnection = conn;
109
+ }).pipe(Effect.flatMap(() => Ref.set(connectionRef, conn))),
110
+ };
111
+ const ctx = Context.empty().pipe(Context.add(ConnectionConfig, connectionConfigValue), Context.add(ConnectionState, connectionStateValue), Context.add(LocalKV, local), Context.add(RemoteKV, remote));
112
+ const wrapper = wrapDatabase(() => currentConnection, (call) => call());
113
+ wrapper.pull = (() => pull().pipe(Effect.provide(ctx), Effect.asVoid));
114
+ wrapper.push = () => push().pipe(Effect.provide(ctx), Effect.asVoid);
115
+ wrapper.sync = (() => Effect.gen(function* () {
116
+ yield* pull();
117
+ yield* push();
118
+ }).pipe(Effect.provide(ctx)));
119
+ wrapper.fork = () => Effect.die(new Error("Not implemented"));
120
+ wrapper.checkpoint = () => Effect.die(new Error("Not implemented"));
121
+ return wrapper;
122
+ });
123
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../../src/connection.ts"],"names":[],"mappings":"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","sourcesContent":["import { Context, Effect, Option, Ref } from \"effect\";\n\nimport { applyBatch } from \"./batches\";\nimport { getLatestChangeId } from \"./cdc/extract\";\nimport { truncate } from \"./cdc/truncate\";\nimport { ConnectionConfig, ConnectionState } from \"./contexts\";\nimport { makeFileKV } from \"./kv/fileKV\";\nimport { makeS3KV } from \"./kv/s3KV\";\nimport { pullFiles } from \"./kv/syncFiles\";\nimport { pull } from \"./pull\";\nimport { push } from \"./push\";\nimport {\n\tLocalKV,\n\tRemoteKV,\n\tbaseKey,\n\tbatchKey,\n\tdbKey,\n\tencodeJson,\n\tgetJson,\n\theadKey,\n\tsnapshotKey,\n} from \"./storage\";\nimport type { ConnectionOptions, Head, S3qliteDatabase, StoredHead } from \"./types\";\nimport { wrapDatabase } from \"./wrapDatabase\";\n\nexport const initializeContexts = (connectionOptions: ConnectionOptions) =>\n\tEffect.gen(function* () {\n\t\tconst s3kv = yield* makeS3KV(connectionOptions.bucket);\n\t\tconst filekv = yield* makeFileKV(connectionOptions.localDirectory ?? \"./.s3qlite/\");\n\t\treturn Context.empty().pipe(Context.add(RemoteKV, s3kv), Context.add(LocalKV, filekv));\n\t});\n\nexport const connect = (dbName: string, options: ConnectionOptions) =>\n\tEffect.gen(function* () {\n\t\tconst remote = yield* RemoteKV;\n\t\tconst local = yield* LocalKV;\n\n\t\tconst { localHead, remoteHead } = yield* Effect.all({\n\t\t\tlocalHead: getJson<StoredHead>(local, headKey(dbName)),\n\t\t\tremoteHead: getJson<Head>(remote, headKey(dbName)),\n\t\t\t// localDb: local.get(dbKey(dbName)),\n\t\t});\n\n\t\tif (Option.isNone(localHead) && Option.isNone(remoteHead)) {\n\t\t\tconst snapshotId = crypto.randomUUID();\n\n\t\t\t// connection is scoped, will die when the effect is done\n\t\t\tconst connection = yield* local.connect(dbKey(dbName));\n\t\t\tyield* truncate(connection);\n\t\t\tyield* Effect.promise(() => connection.close());\n\n\t\t\tconst head: Head = {\n\t\t\t\tsnapshots: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: snapshotId,\n\t\t\t\t\t\tbatchIdsApplied: [],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tbatches: [],\n\t\t\t};\n\n\t\t\t// TODO: this should be atomic, and the head(s) probably updated only after snapshot is written.\n\t\t\tconst { remoteResult } = yield* Effect.all({\n\t\t\t\tremoteResult: remote.set(headKey(dbName), encodeJson(head)),\n\t\t\t\tsnapshot: local.clone(dbKey(dbName), snapshotKey(snapshotId)).pipe(\n\t\t\t\t\tEffect.flatMap(() => local.readStream(snapshotKey(snapshotId))),\n\t\t\t\t\tEffect.flatMap(\n\t\t\t\t\t\tOption.match({\n\t\t\t\t\t\t\tonNone: () => Effect.die(new Error(\"Snapshot not found after clone\")),\n\t\t\t\t\t\t\tonSome: (stream) => remote.writeStream(snapshotKey(snapshotId), stream),\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t});\n\n\t\t\tyield* local.set(\n\t\t\t\theadKey(dbName),\n\t\t\t\tencodeJson<StoredHead>({\n\t\t\t\t\thead,\n\t\t\t\t\tremoteEtag: remoteResult.etag,\n\t\t\t\t\tlastSyncedLocalChangeId: 0,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tif (Option.isSome(remoteHead) && Option.isNone(localHead)) {\n\t\t\tconst head = remoteHead.value.value;\n\t\t\tconst latestSnapshot = head.snapshots.at(-1);\n\n\t\t\tif (!latestSnapshot) {\n\t\t\t\treturn yield* Effect.die(new Error(\"Remote head has no snapshots\"));\n\t\t\t}\n\n\t\t\tconst lastAppliedBatchId = latestSnapshot.batchIdsApplied.at(-1);\n\t\t\tconst batchStartIndex = lastAppliedBatchId\n\t\t\t\t? head.batches.findIndex((b) => b.id === lastAppliedBatchId) + 1\n\t\t\t\t: 0;\n\t\t\tconst batchesToDownload = head.batches.slice(batchStartIndex);\n\n\t\t\tyield* pullFiles([\n\t\t\t\tsnapshotKey(latestSnapshot.id),\n\t\t\t\t...batchesToDownload.map((batch) => batchKey(batch.id)),\n\t\t\t]);\n\n\t\t\tyield* local.clone(snapshotKey(latestSnapshot.id), dbKey(dbName));\n\n\t\t\tconst db = yield* local.connect(dbKey(dbName));\n\t\t\tfor (const batch of batchesToDownload) {\n\t\t\t\tyield* applyBatch(db, batch);\n\t\t\t}\n\t\t\tconst lastSyncedLocalChangeId = yield* getLatestChangeId(db);\n\t\t\tyield* truncate(db);\n\t\t\tyield* Effect.promise(() => db.close());\n\n\t\t\tconst lastBatchId = batchesToDownload.at(-1)?.id;\n\t\t\tif (lastBatchId) {\n\t\t\t\tyield* local.clone(dbKey(dbName), baseKey(latestSnapshot.id, lastBatchId));\n\t\t\t}\n\n\t\t\tyield* local.set(\n\t\t\t\theadKey(dbName),\n\t\t\t\tencodeJson<StoredHead>({\n\t\t\t\t\thead,\n\t\t\t\t\tremoteEtag: remoteHead.value.etag,\n\t\t\t\t\tlastSyncedLocalChangeId,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tif (Option.isNone(remoteHead) && Option.isSome(localHead)) {\n\t\t\treturn yield* Effect.die(\n\t\t\t\tnew Error(\n\t\t\t\t\t\"Local head exists but remote head does not. This is an unexpected state.\",\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tif (Option.isSome(remoteHead) && Option.isSome(localHead)) {\n\t\t\t// do nothing?\n\t\t}\n\n\t\tconst connectionConfigValue = {\n\t\t\tbucket: options.bucket,\n\t\t\tdbName,\n\t\t\tlivePath: dbKey(dbName),\n\t\t\tlocalDirectory: options.localDirectory ?? \"./.s3qlite/\",\n\t\t\tlocalHeadPath: headKey(dbName),\n\t\t\toptions,\n\t\t};\n\n\t\tconst connection = yield* local.connect(dbKey(dbName));\n\n\t\tlet currentConnection = connection;\n\n\t\tconst connectionRef = yield* Ref.make(connection);\n\n\t\tconst connectionStateValue = {\n\t\t\tgetConnection: Ref.get(connectionRef),\n\t\t\tsetConnection: (conn: typeof connection) =>\n\t\t\t\tEffect.sync(() => {\n\t\t\t\t\tcurrentConnection = conn;\n\t\t\t\t}).pipe(Effect.flatMap(() => Ref.set(connectionRef, conn))),\n\t\t};\n\n\t\tconst ctx = Context.empty().pipe(\n\t\t\tContext.add(ConnectionConfig, connectionConfigValue),\n\t\t\tContext.add(ConnectionState, connectionStateValue),\n\t\t\tContext.add(LocalKV, local),\n\t\t\tContext.add(RemoteKV, remote),\n\t\t);\n\n\t\tconst wrapper = wrapDatabase(\n\t\t\t() => currentConnection,\n\t\t\t(call) => call(),\n\t\t) as unknown as S3qliteDatabase;\n\n\t\twrapper.pull = (() =>\n\t\t\tpull().pipe(Effect.provide(ctx), Effect.asVoid)) as typeof wrapper.pull;\n\t\twrapper.push = () => push().pipe(Effect.provide(ctx), Effect.asVoid);\n\t\twrapper.sync = (() =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tyield* pull();\n\t\t\t\tyield* push();\n\t\t\t}).pipe(Effect.provide(ctx))) as typeof wrapper.sync;\n\t\twrapper.fork = () => Effect.die(new Error(\"Not implemented\"));\n\t\twrapper.checkpoint = () => Effect.die(new Error(\"Not implemented\"));\n\n\t\treturn wrapper;\n\t});"]}
@@ -0,0 +1,6 @@
1
+ import { Context } from "effect";
2
+ export class ConnectionConfig extends Context.Tag("s3qlite/ConnectionConfig")() {
3
+ }
4
+ export class ConnectionState extends Context.Tag("s3qlite/ConnectionState")() {
5
+ }
6
+ //# sourceMappingURL=contexts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contexts.js","sourceRoot":"","sources":["../../../src/contexts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAcjC,MAAM,OAAO,gBAAiB,SAAQ,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,EAG1E;CAAG;AAEN,MAAM,OAAO,eAAgB,SAAQ,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,EAMxE;CAAG","sourcesContent":["import type { Database } from \"@tursodatabase/database\";\nimport { Context } from \"effect\";\nimport type { Effect } from \"effect\";\n\nimport type { ConnectionOptions } from \"./types\";\n\nexport type ConnectionConfigValue = {\n\treadonly bucket: string;\n\treadonly dbName: string;\n\treadonly livePath: string;\n\treadonly localDirectory: string;\n\treadonly localHeadPath: string;\n\treadonly options: ConnectionOptions;\n};\n\nexport class ConnectionConfig extends Context.Tag(\"s3qlite/ConnectionConfig\")<\n\tConnectionConfig,\n\tConnectionConfigValue\n>() {}\n\nexport class ConnectionState extends Context.Tag(\"s3qlite/ConnectionState\")<\n\tConnectionState,\n\t{\n\t\treadonly getConnection: Effect.Effect<Database>;\n\t\treadonly setConnection: (connection: Database) => Effect.Effect<void>;\n\t}\n>() {}"]}
@@ -0,0 +1,12 @@
1
+ import { Effect } from "effect";
2
+ import { connect as connectInternal, initializeContexts } from "./connection.js";
3
+ export * from "./cdc/apply.js";
4
+ export * from "./cdc/extract.js";
5
+ export * from "./cdc/types.js";
6
+ export * from "./kv/fileKV.js";
7
+ export * from "./kv/kv.js";
8
+ export * from "./kv/memoryKV.js";
9
+ export * from "./kv/s3KV.js";
10
+ export { initializeContexts as initContexts };
11
+ export const connect = (dbName, connectionOptions) => initializeContexts(connectionOptions).pipe(Effect.flatMap((ctx) => connectInternal(dbName, connectionOptions).pipe(Effect.provide(ctx))));
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAG9E,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAE1B,OAAO,EAAE,kBAAkB,IAAI,YAAY,EAAE,CAAC;AAE9C,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,MAAc,EAAE,iBAAoC,EAAE,EAAE,CAC/E,kBAAkB,CAAC,iBAAiB,CAAC,CAAC,IAAI,CACzC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CACtB,eAAe,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CACpE,CACD,CAAC","sourcesContent":["import { Effect } from \"effect\";\n\nimport { connect as connectInternal, initializeContexts } from \"./connection\";\nimport type { ConnectionOptions } from \"./types\";\n\nexport * from \"./cdc/apply\";\nexport * from \"./cdc/extract\";\nexport * from \"./cdc/types\";\nexport * from \"./kv/fileKV\";\nexport * from \"./kv/kv\";\nexport * from \"./kv/memoryKV\";\nexport * from \"./kv/s3KV\";\n\nexport { initializeContexts as initContexts };\n\nexport const connect = (dbName: string, connectionOptions: ConnectionOptions) =>\n\tinitializeContexts(connectionOptions).pipe(\n\t\tEffect.flatMap((ctx) =>\n\t\t\tconnectInternal(dbName, connectionOptions).pipe(Effect.provide(ctx)),\n\t\t),\n\t);"]}