@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,127 @@
1
+ import { FileSystem } from "@effect/platform/FileSystem";
2
+ import { connect as connectTurso } from "@tursodatabase/database";
3
+ import { Effect, Option, Stream } from "effect";
4
+ import { constants } from "node:fs";
5
+ import { copyFile as copyFilePromise } from "node:fs/promises";
6
+ import { ConflictError } from "./kv.js";
7
+ // TODO: in the future the etag should be probably cached
8
+ const getEtag = (value) => Effect.promise(async () => {
9
+ const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(value).buffer);
10
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
11
+ }).pipe(Effect.orDie);
12
+ export const makeFileKV = (rootDirectory) => Effect.gen(function* () {
13
+ const fs = yield* FileSystem;
14
+ const writeBytes = (key, value) => Effect.gen(function* () {
15
+ const path = `${rootDirectory}/${key}`;
16
+ const directory = path.split("/").slice(0, -1).join("/");
17
+ yield* fs.makeDirectory(directory, { recursive: true }).pipe(Effect.orDie);
18
+ const tempPath = `${path}.${crypto.randomUUID()}.tmp`;
19
+ yield* fs.writeFile(tempPath, value).pipe(Effect.orDie);
20
+ yield* fs.rename(tempPath, path).pipe(Effect.orDie);
21
+ return { etag: yield* getEtag(value) };
22
+ });
23
+ return {
24
+ get: (key) => Effect.gen(function* () {
25
+ yield* Effect.logDebug(`fileKV.get(${key})`);
26
+ const path = `${rootDirectory}/${key}`;
27
+ if (!(yield* fs.exists(path).pipe(Effect.orDie))) {
28
+ yield* Effect.logDebug(`fileKV.get(${key}) => None`);
29
+ return Option.none();
30
+ }
31
+ const value = yield* fs.readFile(path).pipe(Effect.orDie);
32
+ const etag = yield* getEtag(value);
33
+ yield* Effect.logDebug(`fileKV.get(${key}) => Some(${etag})`);
34
+ return Option.some({ etag, value });
35
+ }),
36
+ getIfChanged: (key, etag) => Effect.gen(function* () {
37
+ yield* Effect.logDebug(`fileKV.getIfChanged(${key}, ${etag})`);
38
+ const path = `${rootDirectory}/${key}`;
39
+ if (!(yield* fs.exists(path).pipe(Effect.orDie))) {
40
+ yield* Effect.logDebug(`fileKV.getIfChanged(${key}) => None`);
41
+ return Option.none();
42
+ }
43
+ const value = yield* fs.readFile(path).pipe(Effect.orDie);
44
+ const currentEtag = yield* getEtag(value);
45
+ if (currentEtag === etag) {
46
+ yield* Effect.logDebug(`fileKV.getIfChanged(${key}) => None (not changed)`);
47
+ return Option.none();
48
+ }
49
+ yield* Effect.logDebug(`fileKV.getIfChanged(${key}) => Some(${currentEtag})`);
50
+ return Option.some({ etag: currentEtag, value });
51
+ }),
52
+ exists: (key) => Effect.logDebug(`fileKV.exists(${key})`).pipe(Effect.flatMap(() => fs.exists(`${rootDirectory}/${key}`).pipe(Effect.orDie)), Effect.tap((result) => Effect.logDebug(`fileKV.exists(${key}) => ${result}`))),
53
+ set: (key, value) => Effect.logDebug(`fileKV.set(${key}, ${value.length} bytes)`).pipe(Effect.flatMap(() => writeBytes(key, value)), Effect.tap((result) => Effect.logDebug(`fileKV.set(${key}) => ${result.etag}`))),
54
+ cas: (key, value, etag) => Effect.gen(function* () {
55
+ yield* Effect.logDebug(`fileKV.cas(${key}, ${value.length} bytes, ${etag})`);
56
+ const path = `${rootDirectory}/${key}`;
57
+ if (!(yield* fs.exists(path).pipe(Effect.orDie))) {
58
+ yield* Effect.logDebug(`fileKV.cas(${key}) => ConflictError (missing)`);
59
+ return yield* Effect.fail(new ConflictError({ key }));
60
+ }
61
+ const existingValue = yield* fs.readFile(path).pipe(Effect.orDie);
62
+ if ((yield* getEtag(existingValue)) !== etag) {
63
+ yield* Effect.logDebug(`fileKV.cas(${key}) => ConflictError (etag mismatch)`);
64
+ return yield* Effect.fail(new ConflictError({ key }));
65
+ }
66
+ const result = yield* writeBytes(key, value);
67
+ yield* Effect.logDebug(`fileKV.cas(${key}) => ${result.etag}`);
68
+ return result;
69
+ }),
70
+ delete: (key) => Effect.logDebug(`fileKV.delete(${key})`).pipe(Effect.flatMap(() => fs.remove(`${rootDirectory}/${key}`, { force: true }).pipe(Effect.orDie)), Effect.tap(() => Effect.logDebug(`fileKV.delete(${key}) => void`))),
71
+ readStream: (key) => Effect.gen(function* () {
72
+ yield* Effect.logDebug(`fileKV.readStream(${key})`);
73
+ const path = `${rootDirectory}/${key}`;
74
+ if (!(yield* fs.exists(path).pipe(Effect.orDie))) {
75
+ yield* Effect.logDebug(`fileKV.readStream(${key}) => None`);
76
+ return Option.none();
77
+ }
78
+ yield* Effect.logDebug(`fileKV.readStream(${key}) => Some(stream)`);
79
+ return Option.some(fs.stream(path).pipe(Stream.orDie));
80
+ }),
81
+ writeStream: (key, content) => Effect.gen(function* () {
82
+ yield* Effect.logDebug(`fileKV.writeStream(${key}, stream)`);
83
+ const path = `${rootDirectory}/${key}`;
84
+ const directory = path.split("/").slice(0, -1).join("/");
85
+ yield* fs.makeDirectory(directory, { recursive: true }).pipe(Effect.orDie);
86
+ const tempPath = `${path}.${crypto.randomUUID()}.tmp`;
87
+ yield* Stream.run(content, fs.sink(tempPath)).pipe(Effect.orDie);
88
+ yield* fs.rename(tempPath, path).pipe(Effect.orDie);
89
+ const value = yield* fs.readFile(path).pipe(Effect.orDie);
90
+ const etag = yield* getEtag(value);
91
+ yield* Effect.logDebug(`fileKV.writeStream(${key}) => ${etag}`);
92
+ return { etag };
93
+ }),
94
+ clone: (from, to) => Effect.gen(function* () {
95
+ yield* Effect.logDebug(`fileKV.clone(${from}, ${to})`);
96
+ const fromPath = `${rootDirectory}/${from}`;
97
+ const toPath = `${rootDirectory}/${to}`;
98
+ const directory = toPath.split("/").slice(0, -1).join("/");
99
+ yield* fs.makeDirectory(directory, { recursive: true }).pipe(Effect.orDie);
100
+ const cloneAttempt = yield* Effect.either(Effect.promise(() => copyFilePromise(fromPath, toPath, constants.COPYFILE_FICLONE)));
101
+ if (cloneAttempt._tag === "Right") {
102
+ yield* Effect.logDebug(`fileKV.clone(${from}, ${to}) => void (copied)`);
103
+ return;
104
+ }
105
+ const errorOption = typeof cloneAttempt.left === "object"
106
+ ? Option.fromNullable(cloneAttempt.left)
107
+ : Option.none();
108
+ const currentError = Option.map(errorOption, (error) => error);
109
+ if (Option.isNone(currentError) ||
110
+ !("code" in currentError.value) ||
111
+ !["ENOTSUP", "EINVAL", "ENOSYS", "EXDEV", "EOPNOTSUPP"].includes(String(currentError.value.code))) {
112
+ return yield* Effect.die(cloneAttempt.left);
113
+ }
114
+ yield* fs.copyFile(fromPath, toPath).pipe(Effect.orDie);
115
+ yield* Effect.logDebug(`fileKV.clone(${from}, ${to}) => void (copied)`);
116
+ }),
117
+ connect: (key) => Effect.logDebug(`fileKV.connect(${key})`).pipe(Effect.flatMap(() => Effect.acquireRelease(Effect.tryPromise({
118
+ try: async () => {
119
+ const connection = await connectTurso(`${rootDirectory}/${key}`);
120
+ await connection.exec("PRAGMA capture_data_changes_conn('full')");
121
+ return connection;
122
+ },
123
+ catch: (e) => new Error(`Failed to connect to database at key ${key}: ${String(e)}`),
124
+ }), (db) => Effect.promise(() => db.close()))), Effect.tap(() => Effect.logDebug(`fileKV.connect(${key}) => database`))),
125
+ };
126
+ });
127
+ //# sourceMappingURL=fileKV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileKV.js","sourceRoot":"","sources":["../../../../src/kv/fileKV.ts"],"names":[],"mappings":"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","sourcesContent":["import { FileSystem } from \"@effect/platform/FileSystem\";\nimport { connect as connectTurso } from \"@tursodatabase/database\";\nimport type { Scope } from \"effect\";\nimport { Effect, Option, Stream } from \"effect\";\nimport { constants } from \"node:fs\";\nimport { copyFile as copyFilePromise } from \"node:fs/promises\";\n\nimport { ConflictError } from \"./kv\";\nimport type { CloneTrait, ConnectDatabaseTrait, KV } from \"./kv\";\n\n// TODO: in the future the etag should be probably cached\nconst getEtag = (value: Uint8Array): Effect.Effect<string> =>\n\tEffect.promise(async () => {\n\t\tconst digest = await crypto.subtle.digest(\"SHA-256\", Uint8Array.from(value).buffer);\n\t\treturn Array.from(new Uint8Array(digest), (byte) =>\n\t\t\tbyte.toString(16).padStart(2, \"0\"),\n\t\t).join(\"\");\n\t}).pipe(Effect.orDie);\n\nexport const makeFileKV = (\n\trootDirectory: string,\n): Effect.Effect<KV & CloneTrait & ConnectDatabaseTrait, never, FileSystem | Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem;\n\n\t\tconst writeBytes = (key: string, value: Uint8Array): Effect.Effect<{ etag: string }> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst path = `${rootDirectory}/${key}`;\n\t\t\t\tconst directory = path.split(\"/\").slice(0, -1).join(\"/\");\n\t\t\t\tyield* fs.makeDirectory(directory, { recursive: true }).pipe(Effect.orDie);\n\t\t\t\tconst tempPath = `${path}.${crypto.randomUUID()}.tmp`;\n\t\t\t\tyield* fs.writeFile(tempPath, value).pipe(Effect.orDie);\n\t\t\t\tyield* fs.rename(tempPath, path).pipe(Effect.orDie);\n\t\t\t\treturn { etag: yield* getEtag(value) };\n\t\t\t});\n\n\t\treturn {\n\t\t\tget: (key) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* Effect.logDebug(`fileKV.get(${key})`);\n\n\t\t\t\t\tconst path = `${rootDirectory}/${key}`;\n\t\t\t\t\tif (!(yield* fs.exists(path).pipe(Effect.orDie))) {\n\t\t\t\t\t\tyield* Effect.logDebug(`fileKV.get(${key}) => None`);\n\t\t\t\t\t\treturn Option.none();\n\t\t\t\t\t}\n\n\t\t\t\t\tconst value = yield* fs.readFile(path).pipe(Effect.orDie);\n\t\t\t\t\tconst etag = yield* getEtag(value);\n\t\t\t\t\tyield* Effect.logDebug(`fileKV.get(${key}) => Some(${etag})`);\n\t\t\t\t\treturn Option.some({ etag, value });\n\t\t\t\t}),\n\t\t\tgetIfChanged: (key, etag) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* Effect.logDebug(`fileKV.getIfChanged(${key}, ${etag})`);\n\n\t\t\t\t\tconst path = `${rootDirectory}/${key}`;\n\t\t\t\t\tif (!(yield* fs.exists(path).pipe(Effect.orDie))) {\n\t\t\t\t\t\tyield* Effect.logDebug(`fileKV.getIfChanged(${key}) => None`);\n\t\t\t\t\t\treturn Option.none();\n\t\t\t\t\t}\n\n\t\t\t\t\tconst value = yield* fs.readFile(path).pipe(Effect.orDie);\n\t\t\t\t\tconst currentEtag = yield* getEtag(value);\n\t\t\t\t\tif (currentEtag === etag) {\n\t\t\t\t\t\tyield* Effect.logDebug(`fileKV.getIfChanged(${key}) => None (not changed)`);\n\t\t\t\t\t\treturn Option.none();\n\t\t\t\t\t}\n\n\t\t\t\t\tyield* Effect.logDebug(`fileKV.getIfChanged(${key}) => Some(${currentEtag})`);\n\t\t\t\t\treturn Option.some({ etag: currentEtag, value });\n\t\t\t\t}),\n\t\t\texists: (key) =>\n\t\t\t\tEffect.logDebug(`fileKV.exists(${key})`).pipe(\n\t\t\t\t\tEffect.flatMap(() => fs.exists(`${rootDirectory}/${key}`).pipe(Effect.orDie)),\n\t\t\t\t\tEffect.tap((result) => Effect.logDebug(`fileKV.exists(${key}) => ${result}`)),\n\t\t\t\t),\n\t\t\tset: (key, value) =>\n\t\t\t\tEffect.logDebug(`fileKV.set(${key}, ${value.length} bytes)`).pipe(\n\t\t\t\t\tEffect.flatMap(() => writeBytes(key, value)),\n\t\t\t\t\tEffect.tap((result) => Effect.logDebug(`fileKV.set(${key}) => ${result.etag}`)),\n\t\t\t\t),\n\t\t\tcas: (key, value, etag) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* Effect.logDebug(`fileKV.cas(${key}, ${value.length} bytes, ${etag})`);\n\n\t\t\t\t\tconst path = `${rootDirectory}/${key}`;\n\t\t\t\t\tif (!(yield* fs.exists(path).pipe(Effect.orDie))) {\n\t\t\t\t\t\tyield* Effect.logDebug(`fileKV.cas(${key}) => ConflictError (missing)`);\n\t\t\t\t\t\treturn yield* Effect.fail(new ConflictError({ key }));\n\t\t\t\t\t}\n\n\t\t\t\t\tconst existingValue = yield* fs.readFile(path).pipe(Effect.orDie);\n\t\t\t\t\tif ((yield* getEtag(existingValue)) !== etag) {\n\t\t\t\t\t\tyield* Effect.logDebug(\n\t\t\t\t\t\t\t`fileKV.cas(${key}) => ConflictError (etag mismatch)`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn yield* Effect.fail(new ConflictError({ key }));\n\t\t\t\t\t}\n\n\t\t\t\t\tconst result = yield* writeBytes(key, value);\n\t\t\t\t\tyield* Effect.logDebug(`fileKV.cas(${key}) => ${result.etag}`);\n\t\t\t\t\treturn result;\n\t\t\t\t}),\n\t\t\tdelete: (key) =>\n\t\t\t\tEffect.logDebug(`fileKV.delete(${key})`).pipe(\n\t\t\t\t\tEffect.flatMap(() =>\n\t\t\t\t\t\tfs.remove(`${rootDirectory}/${key}`, { force: true }).pipe(Effect.orDie),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.tap(() => Effect.logDebug(`fileKV.delete(${key}) => void`)),\n\t\t\t\t),\n\t\t\treadStream: (key) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* Effect.logDebug(`fileKV.readStream(${key})`);\n\n\t\t\t\t\tconst path = `${rootDirectory}/${key}`;\n\t\t\t\t\tif (!(yield* fs.exists(path).pipe(Effect.orDie))) {\n\t\t\t\t\t\tyield* Effect.logDebug(`fileKV.readStream(${key}) => None`);\n\t\t\t\t\t\treturn Option.none();\n\t\t\t\t\t}\n\n\t\t\t\t\tyield* Effect.logDebug(`fileKV.readStream(${key}) => Some(stream)`);\n\t\t\t\t\treturn Option.some(fs.stream(path).pipe(Stream.orDie));\n\t\t\t\t}),\n\t\t\twriteStream: (key, content) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* Effect.logDebug(`fileKV.writeStream(${key}, stream)`);\n\n\t\t\t\t\tconst path = `${rootDirectory}/${key}`;\n\t\t\t\t\tconst directory = path.split(\"/\").slice(0, -1).join(\"/\");\n\t\t\t\t\tyield* fs.makeDirectory(directory, { recursive: true }).pipe(Effect.orDie);\n\t\t\t\t\tconst tempPath = `${path}.${crypto.randomUUID()}.tmp`;\n\t\t\t\t\tyield* Stream.run(content, fs.sink(tempPath)).pipe(Effect.orDie);\n\t\t\t\t\tyield* fs.rename(tempPath, path).pipe(Effect.orDie);\n\t\t\t\t\tconst value = yield* fs.readFile(path).pipe(Effect.orDie);\n\t\t\t\t\tconst etag = yield* getEtag(value);\n\t\t\t\t\tyield* Effect.logDebug(`fileKV.writeStream(${key}) => ${etag}`);\n\t\t\t\t\treturn { etag };\n\t\t\t\t}),\n\t\t\tclone: (from, to) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* Effect.logDebug(`fileKV.clone(${from}, ${to})`);\n\n\t\t\t\t\tconst fromPath = `${rootDirectory}/${from}`;\n\t\t\t\t\tconst toPath = `${rootDirectory}/${to}`;\n\t\t\t\t\tconst directory = toPath.split(\"/\").slice(0, -1).join(\"/\");\n\t\t\t\t\tyield* fs.makeDirectory(directory, { recursive: true }).pipe(Effect.orDie);\n\t\t\t\t\tconst cloneAttempt = yield* Effect.either(\n\t\t\t\t\t\tEffect.promise(() =>\n\t\t\t\t\t\t\tcopyFilePromise(fromPath, toPath, constants.COPYFILE_FICLONE),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\n\t\t\t\t\tif (cloneAttempt._tag === \"Right\") {\n\t\t\t\t\t\tyield* Effect.logDebug(`fileKV.clone(${from}, ${to}) => void (copied)`);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst errorOption =\n\t\t\t\t\t\ttypeof cloneAttempt.left === \"object\"\n\t\t\t\t\t\t\t? Option.fromNullable(cloneAttempt.left)\n\t\t\t\t\t\t\t: Option.none();\n\t\t\t\t\tconst currentError = Option.map(\n\t\t\t\t\t\terrorOption,\n\t\t\t\t\t\t(error) => error as { code?: unknown },\n\t\t\t\t\t);\n\n\t\t\t\t\tif (\n\t\t\t\t\t\tOption.isNone(currentError) ||\n\t\t\t\t\t\t!(\"code\" in currentError.value) ||\n\t\t\t\t\t\t![\"ENOTSUP\", \"EINVAL\", \"ENOSYS\", \"EXDEV\", \"EOPNOTSUPP\"].includes(\n\t\t\t\t\t\t\tString(currentError.value.code),\n\t\t\t\t\t\t)\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn yield* Effect.die(cloneAttempt.left);\n\t\t\t\t\t}\n\n\t\t\t\t\tyield* fs.copyFile(fromPath, toPath).pipe(Effect.orDie);\n\t\t\t\t\tyield* Effect.logDebug(`fileKV.clone(${from}, ${to}) => void (copied)`);\n\t\t\t\t}),\n\t\t\tconnect: (key) =>\n\t\t\t\tEffect.logDebug(`fileKV.connect(${key})`).pipe(\n\t\t\t\t\tEffect.flatMap(() =>\n\t\t\t\t\t\tEffect.acquireRelease(\n\t\t\t\t\t\t\tEffect.tryPromise({\n\t\t\t\t\t\t\t\ttry: async () => {\n\t\t\t\t\t\t\t\t\tconst connection = await connectTurso(\n\t\t\t\t\t\t\t\t\t\t`${rootDirectory}/${key}`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tawait connection.exec(\n\t\t\t\t\t\t\t\t\t\t\"PRAGMA capture_data_changes_conn('full')\",\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\treturn connection;\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tcatch: (e) =>\n\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t`Failed to connect to database at key ${key}: ${String(e)}`,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t(db) => Effect.promise(() => db.close()),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.tap(() => Effect.logDebug(`fileKV.connect(${key}) => database`)),\n\t\t\t\t),\n\t\t};\n\t});"]}
@@ -0,0 +1,4 @@
1
+ import { Data } from "effect";
2
+ export class ConflictError extends Data.TaggedError("ConflictError") {
3
+ }
4
+ //# sourceMappingURL=kv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kv.js","sourceRoot":"","sources":["../../../../src/kv/kv.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAG9B,MAAM,OAAO,aAAc,SAAQ,IAAI,CAAC,WAAW,CAAC,eAAe,CAEjE;CAAG","sourcesContent":["import type { Database } from \"@tursodatabase/database\";\nimport { Data } from \"effect\";\nimport type { Effect, Option, Scope, Stream } from \"effect\";\n\nexport class ConflictError extends Data.TaggedError(\"ConflictError\")<{\n\treadonly key: string;\n}> {}\n\nexport type KV = {\n\tget(key: string): Effect.Effect<Option.Option<{ value: Uint8Array; etag: string }>>;\n\t/** Optimization for S3 calls, so there isn't head+get, but only one request*/\n\tgetIfChanged(\n\t\tkey: string,\n\t\tetag: string,\n\t): Effect.Effect<Option.Option<{ value: Uint8Array; etag: string }>>;\n\texists(key: string): Effect.Effect<boolean>;\n\tset(key: string, value: Uint8Array): Effect.Effect<{ etag: string }>;\n\tcas(\n\t\tkey: string,\n\t\tvalue: Uint8Array,\n\t\tetag: string,\n\t): Effect.Effect<{ etag: string }, ConflictError>;\n\tdelete(key: string): Effect.Effect<void>;\n\n\treadStream(key: string): Effect.Effect<Option.Option<Stream.Stream<Uint8Array>>>;\n\twriteStream(key: string, content: Stream.Stream<Uint8Array>): Effect.Effect<{ etag: string }>;\n};\n\nexport type CloneTrait = {\n\tclone(from: string, to: string): Effect.Effect<void>;\n};\n\nexport type ConnectDatabaseTrait = {\n\t/**\n\t * Creates connection with CDC full enabled\n\t */\n\tconnect(key: string): Effect.Effect<Database, Error, Scope.Scope>;\n};"]}
@@ -0,0 +1,12 @@
1
+ import { FileSystem } from "@effect/platform/FileSystem";
2
+ import { Effect } from "effect";
3
+ import { makeFileKV } from "./fileKV.js";
4
+ export const makeMemoryKV = () => Effect.gen(function* () {
5
+ const fs = yield* FileSystem;
6
+ const rootDirectory = `/dev/shm/s3qlite-${crypto.randomUUID()}`;
7
+ yield* Effect.logDebug(`memoryKV.makeMemoryKV(${rootDirectory})`);
8
+ yield* fs.makeDirectory(rootDirectory, { recursive: true }).pipe(Effect.orDie);
9
+ yield* Effect.addFinalizer(() => fs.remove(rootDirectory, { recursive: true, force: true }).pipe(Effect.orDie));
10
+ return yield* makeFileKV(rootDirectory);
11
+ });
12
+ //# sourceMappingURL=memoryKV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memoryKV.js","sourceRoot":"","sources":["../../../../src/kv/memoryKV.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGtC,MAAM,CAAC,MAAM,YAAY,GAAG,GAI1B,EAAE,CACH,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC;IAC7B,MAAM,aAAa,GAAG,oBAAoB,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC;IAEhE,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,aAAa,GAAG,CAAC,CAAC;IAClE,KAAK,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC/E,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,CAC/B,EAAE,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAC7E,CAAC;IAEF,OAAO,KAAK,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AACzC,CAAC,CAAC,CAAC","sourcesContent":["import { FileSystem } from \"@effect/platform/FileSystem\";\nimport { Effect } from \"effect\";\nimport type { Scope } from \"effect\";\n\nimport { makeFileKV } from \"./fileKV\";\nimport type { CloneTrait, ConnectDatabaseTrait, KV } from \"./kv\";\n\nexport const makeMemoryKV = (): Effect.Effect<\n\tKV & CloneTrait & ConnectDatabaseTrait,\n\tnever,\n\tFileSystem | Scope.Scope\n> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem;\n\t\tconst rootDirectory = `/dev/shm/s3qlite-${crypto.randomUUID()}`;\n\n\t\tyield* Effect.logDebug(`memoryKV.makeMemoryKV(${rootDirectory})`);\n\t\tyield* fs.makeDirectory(rootDirectory, { recursive: true }).pipe(Effect.orDie);\n\t\tyield* Effect.addFinalizer(() =>\n\t\t\tfs.remove(rootDirectory, { recursive: true, force: true }).pipe(Effect.orDie),\n\t\t);\n\n\t\treturn yield* makeFileKV(rootDirectory);\n\t});"]}
@@ -0,0 +1,279 @@
1
+ import { S3 } from "@effect-aws/client-s3";
2
+ import { Chunk, Effect, Option, Stream } from "effect";
3
+ import { ConflictError } from "./kv.js";
4
+ const isMissingObjectError = (error) => Effect.runSync(Effect.sync(() => {
5
+ if (typeof error !== "object") {
6
+ return false;
7
+ }
8
+ const errorOption = Option.fromNullable(error);
9
+ if (Option.isNone(errorOption)) {
10
+ return false;
11
+ }
12
+ const currentError = errorOption.value;
13
+ return ((typeof currentError.Code === "string" && currentError.Code === "NoSuchKey") ||
14
+ (typeof currentError.name === "string" &&
15
+ (currentError.name === "NoSuchKey" || currentError.name === "NotFound")));
16
+ }));
17
+ const isNotModifiedError = (error) => Effect.runSync(Effect.sync(() => {
18
+ if (typeof error !== "object") {
19
+ return false;
20
+ }
21
+ const errorOption = Option.fromNullable(error);
22
+ if (Option.isNone(errorOption)) {
23
+ return false;
24
+ }
25
+ const currentError = errorOption.value;
26
+ const metadata = typeof currentError.$metadata === "object" &&
27
+ Option.isSome(Option.fromNullable(currentError.$metadata))
28
+ ? Option.some(currentError.$metadata)
29
+ : Option.none();
30
+ return ((typeof currentError.Code === "string" && currentError.Code === "NotModified") ||
31
+ (typeof currentError.name === "string" && currentError.name === "NotModified") ||
32
+ Option.exists(metadata, (currentMetadata) => currentMetadata.httpStatusCode === 304));
33
+ }));
34
+ const getEtag = (result, key) => Option.fromNullable(result.ETag).pipe(Option.match({
35
+ onNone: () => Effect.die(new Error(`Expected ETag for ${key}`)),
36
+ onSome: (etag) => Effect.succeed({ etag }),
37
+ }));
38
+ const readBodyBytes = (body, key) => {
39
+ if (body instanceof Uint8Array) {
40
+ return Effect.succeed(body);
41
+ }
42
+ if (typeof body === "object") {
43
+ const bodyOption = Option.fromNullable(body);
44
+ if (Option.isSome(bodyOption) &&
45
+ "transformToByteArray" in bodyOption.value &&
46
+ typeof bodyOption.value.transformToByteArray === "function") {
47
+ const currentBody = bodyOption.value;
48
+ return Effect.promise(() => currentBody.transformToByteArray()).pipe(Effect.orDie);
49
+ }
50
+ }
51
+ return Effect.die(new Error(`Expected readable body for ${key}`));
52
+ };
53
+ const readBodyStream = (body, key) => {
54
+ if (body instanceof Uint8Array) {
55
+ return Effect.succeed(Stream.fromIterable([body]));
56
+ }
57
+ if (body instanceof ReadableStream) {
58
+ return Effect.succeed(Stream.fromReadableStream(() => body, () => new Error(`Failed to stream ${key}`)).pipe(Stream.orDie));
59
+ }
60
+ if (typeof body === "object") {
61
+ const bodyOption = Option.fromNullable(body);
62
+ if (Option.isSome(bodyOption) &&
63
+ "transformToWebStream" in bodyOption.value &&
64
+ typeof bodyOption.value.transformToWebStream === "function") {
65
+ const currentBody = bodyOption.value;
66
+ return Effect.succeed(Stream.fromReadableStream(() => currentBody.transformToWebStream(), () => new Error(`Failed to stream ${key}`)).pipe(Stream.orDie));
67
+ }
68
+ }
69
+ if (typeof body === "object") {
70
+ const bodyOption = Option.fromNullable(body);
71
+ if (Option.isSome(bodyOption) &&
72
+ "transformToByteArray" in bodyOption.value &&
73
+ typeof bodyOption.value.transformToByteArray === "function") {
74
+ return readBodyBytes(bodyOption.value, key).pipe(Effect.map((bytes) => Stream.fromIterable([bytes])));
75
+ }
76
+ }
77
+ return Effect.die(new Error(`Expected streamable body for ${key}`));
78
+ };
79
+ const minimumMultipartPartSize = 5 * 1024 * 1024;
80
+ const combineChunks = (chunks, totalLength) => {
81
+ const combined = new Uint8Array(totalLength);
82
+ let offset = 0;
83
+ for (const chunk of chunks) {
84
+ combined.set(chunk, offset);
85
+ offset += chunk.length;
86
+ }
87
+ return combined;
88
+ };
89
+ export const makeS3KV = (bucket) => Effect.gen(function* () {
90
+ const s3 = yield* S3;
91
+ return {
92
+ get: (key) => Effect.gen(function* () {
93
+ yield* Effect.logDebug(`s3KV.get(${key})`);
94
+ const response = yield* Effect.either(s3.getObject({ Bucket: bucket, Key: key }));
95
+ if (response._tag === "Left") {
96
+ if (isMissingObjectError(response.left)) {
97
+ yield* Effect.logDebug(`s3KV.get(${key}) => None`);
98
+ return Option.none();
99
+ }
100
+ return yield* Effect.die(response.left instanceof Error
101
+ ? response.left
102
+ : new Error(`Failed to read ${key}: ${String(response.left)}`));
103
+ }
104
+ const { etag } = yield* getEtag(response.right, key);
105
+ const value = yield* readBodyBytes(response.right.Body, key);
106
+ yield* Effect.logDebug(`s3KV.get(${key}) => Some(${etag})`);
107
+ return Option.some({ etag, value });
108
+ }),
109
+ getIfChanged: (key, etag) => Effect.gen(function* () {
110
+ yield* Effect.logDebug(`s3KV.getIfChanged(${key}, ${etag})`);
111
+ const response = yield* Effect.either(s3.getObject({ Bucket: bucket, IfNoneMatch: etag, Key: key }));
112
+ if (response._tag === "Left") {
113
+ if (isMissingObjectError(response.left) ||
114
+ isNotModifiedError(response.left)) {
115
+ yield* Effect.logDebug(`s3KV.getIfChanged(${key}) => None`);
116
+ return Option.none();
117
+ }
118
+ return yield* Effect.die(response.left instanceof Error
119
+ ? response.left
120
+ : new Error(`Failed to read ${key}: ${String(response.left)}`));
121
+ }
122
+ const { etag: currentEtag } = yield* getEtag(response.right, key);
123
+ const value = yield* readBodyBytes(response.right.Body, key);
124
+ yield* Effect.logDebug(`s3KV.getIfChanged(${key}) => Some(${currentEtag})`);
125
+ return Option.some({ etag: currentEtag, value });
126
+ }),
127
+ exists: (key) => Effect.gen(function* () {
128
+ yield* Effect.logDebug(`s3KV.exists(${key})`);
129
+ const response = yield* Effect.either(s3.headObject({ Bucket: bucket, Key: key }));
130
+ if (response._tag === "Left") {
131
+ if (isMissingObjectError(response.left)) {
132
+ yield* Effect.logDebug(`s3KV.exists(${key}) => false`);
133
+ return false;
134
+ }
135
+ return yield* Effect.die(response.left instanceof Error
136
+ ? response.left
137
+ : new Error(`Failed to stat ${key}: ${String(response.left)}`));
138
+ }
139
+ yield* Effect.logDebug(`s3KV.exists(${key}) => true`);
140
+ return true;
141
+ }),
142
+ set: (key, value) => Effect.logDebug(`s3KV.set(${key}, ${value.length} bytes)`).pipe(Effect.flatMap(() => s3.putObject({ Body: value, Bucket: bucket, Key: key })), Effect.orDieWith((error) => error instanceof Error
143
+ ? error
144
+ : new Error(`Failed to write ${key}: ${String(error)}`)), Effect.flatMap((result) => getEtag(result, key)), Effect.tap((result) => Effect.logDebug(`s3KV.set(${key}) => ${result.etag}`))),
145
+ cas: (key, value, etag) => Effect.logDebug(`s3KV.cas(${key}, ${value.length} bytes, ${etag})`).pipe(Effect.flatMap(() => s3.putObject({ Body: value, Bucket: bucket, IfMatch: etag, Key: key })), Effect.catchAll((error) => {
146
+ const currentError = typeof error === "object" && Option.isSome(Option.fromNullable(error))
147
+ ? Option.some(error)
148
+ : Option.none();
149
+ const metadata = Option.isSome(currentError) &&
150
+ typeof currentError.value.$metadata === "object" &&
151
+ Option.isSome(Option.fromNullable(currentError.value.$metadata))
152
+ ? Option.some(currentError.value.$metadata)
153
+ : Option.none();
154
+ return Option.isSome(currentError) &&
155
+ ((typeof currentError.value.Code === "string" &&
156
+ currentError.value.Code === "PreconditionFailed") ||
157
+ (typeof currentError.value.name === "string" &&
158
+ currentError.value.name === "PreconditionFailed")) &&
159
+ Option.exists(metadata, (currentMetadata) => currentMetadata.httpStatusCode === 412)
160
+ ? Effect.fail(new ConflictError({ key }))
161
+ : Effect.die(error instanceof Error
162
+ ? error
163
+ : new Error(`Failed to write ${key}: ${String(error)}`));
164
+ }), Effect.flatMap((result) => getEtag(result, key)), Effect.tap((result) => Effect.logDebug(`s3KV.cas(${key}) => ${result.etag}`))),
165
+ delete: (key) => Effect.logDebug(`s3KV.delete(${key})`).pipe(Effect.flatMap(() => s3.deleteObject({ Bucket: bucket, Key: key })), Effect.asVoid, Effect.orDieWith((error) => error instanceof Error
166
+ ? error
167
+ : new Error(`Failed to delete ${key}: ${String(error)}`)), Effect.tap(() => Effect.logDebug(`s3KV.delete(${key}) => void`))),
168
+ readStream: (key) => Effect.gen(function* () {
169
+ yield* Effect.logDebug(`s3KV.readStream(${key})`);
170
+ const response = yield* Effect.either(s3.getObject({ Bucket: bucket, Key: key }));
171
+ if (response._tag === "Left") {
172
+ if (isMissingObjectError(response.left)) {
173
+ yield* Effect.logDebug(`s3KV.readStream(${key}) => None`);
174
+ return Option.none();
175
+ }
176
+ return yield* Effect.die(response.left instanceof Error
177
+ ? response.left
178
+ : new Error(`Failed to stream ${key}: ${String(response.left)}`));
179
+ }
180
+ const stream = yield* readBodyStream(response.right.Body, key);
181
+ yield* Effect.logDebug(`s3KV.readStream(${key}) => Some(stream)`);
182
+ return Option.some(stream);
183
+ }),
184
+ writeStream: (key, content) => Effect.gen(function* () {
185
+ yield* Effect.logDebug(`s3KV.writeStream(${key}, stream)`);
186
+ const multipartUpload = yield* s3
187
+ .createMultipartUpload({ Bucket: bucket, Key: key })
188
+ .pipe(Effect.orDieWith((error) => error instanceof Error
189
+ ? error
190
+ : new Error(`Failed to start upload ${key}: ${String(error)}`)));
191
+ const uploadIdOption = Option.fromNullable(multipartUpload.UploadId);
192
+ if (Option.isNone(uploadIdOption)) {
193
+ return yield* Effect.die(new Error(`Expected UploadId for ${key}`));
194
+ }
195
+ const uploadId = uploadIdOption.value;
196
+ const uploadParts = Effect.scoped(Effect.gen(function* () {
197
+ const pull = yield* Stream.toPull(content);
198
+ const completedParts = [];
199
+ let bufferedChunks = [];
200
+ let bufferedLength = 0;
201
+ let partNumber = 1;
202
+ const uploadBufferedPart = (force) => {
203
+ if (bufferedLength === 0 ||
204
+ (!force && bufferedLength < minimumMultipartPartSize)) {
205
+ return Effect.void;
206
+ }
207
+ const body = combineChunks(bufferedChunks, bufferedLength);
208
+ bufferedChunks = [];
209
+ bufferedLength = 0;
210
+ return s3
211
+ .uploadPart({
212
+ Body: body,
213
+ Bucket: bucket,
214
+ ContentLength: body.length,
215
+ Key: key,
216
+ PartNumber: partNumber,
217
+ UploadId: uploadId,
218
+ })
219
+ .pipe(Effect.orDieWith((error) => error instanceof Error
220
+ ? error
221
+ : new Error(`Failed to upload ${key} part ${partNumber}: ${String(error)}`)), Effect.flatMap((result) => Option.fromNullable(result.ETag).pipe(Option.match({
222
+ onNone: () => Effect.die(new Error(`Expected ETag for ${key} part ${partNumber}`)),
223
+ onSome: (etag) => Effect.sync(() => {
224
+ completedParts.push({
225
+ ETag: etag,
226
+ PartNumber: partNumber,
227
+ });
228
+ partNumber += 1;
229
+ }),
230
+ }))));
231
+ };
232
+ while (true) {
233
+ const nextChunk = yield* Effect.either(pull);
234
+ if (nextChunk._tag === "Left") {
235
+ return yield* uploadBufferedPart(true).pipe(Effect.as(completedParts));
236
+ }
237
+ for (const chunk of Chunk.toReadonlyArray(nextChunk.right)) {
238
+ bufferedChunks.push(chunk);
239
+ bufferedLength += chunk.length;
240
+ if (bufferedLength >= minimumMultipartPartSize) {
241
+ yield* uploadBufferedPart(false);
242
+ }
243
+ }
244
+ }
245
+ })).pipe(Effect.catchAllCause((cause) => s3
246
+ .abortMultipartUpload({
247
+ Bucket: bucket,
248
+ Key: key,
249
+ UploadId: uploadId,
250
+ })
251
+ .pipe(Effect.orDie, Effect.catchAll(() => Effect.void), Effect.zipRight(Effect.failCause(cause)))));
252
+ const completedParts = yield* uploadParts;
253
+ if (completedParts.length === 0) {
254
+ const emptyResult = yield* s3
255
+ .putObject({ Body: new Uint8Array(), Bucket: bucket, Key: key })
256
+ .pipe(Effect.orDieWith((error) => error instanceof Error
257
+ ? error
258
+ : new Error(`Failed to upload ${key}: ${String(error)}`)));
259
+ const emptyEtagResult = yield* getEtag(emptyResult, key);
260
+ yield* Effect.logDebug(`s3KV.writeStream(${key}) => ${emptyEtagResult.etag}`);
261
+ return emptyEtagResult;
262
+ }
263
+ const result = yield* s3
264
+ .completeMultipartUpload({
265
+ Bucket: bucket,
266
+ Key: key,
267
+ MultipartUpload: { Parts: completedParts },
268
+ UploadId: uploadId,
269
+ })
270
+ .pipe(Effect.orDieWith((error) => error instanceof Error
271
+ ? error
272
+ : new Error(`Failed to upload ${key}: ${String(error)}`)));
273
+ const finalResult = yield* getEtag(result, key);
274
+ yield* Effect.logDebug(`s3KV.writeStream(${key}) => ${finalResult.etag}`);
275
+ return finalResult;
276
+ }),
277
+ };
278
+ });
279
+ //# sourceMappingURL=s3KV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3KV.js","sourceRoot":"","sources":["../../../../src/kv/s3KV.ts"],"names":[],"mappings":"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","sourcesContent":["import { S3 } from \"@effect-aws/client-s3\";\nimport type { S3Service } from \"@effect-aws/client-s3\";\nimport { Chunk, Effect, Option, Stream } from \"effect\";\n\nimport { ConflictError } from \"./kv\";\nimport type { KV } from \"./kv\";\n\nconst isMissingObjectError = (error: unknown): boolean =>\n\tEffect.runSync(\n\t\tEffect.sync(() => {\n\t\t\tif (typeof error !== \"object\") {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst errorOption = Option.fromNullable(error);\n\t\t\tif (Option.isNone(errorOption)) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst currentError = errorOption.value as { Code?: unknown; name?: unknown };\n\t\t\treturn (\n\t\t\t\t(typeof currentError.Code === \"string\" && currentError.Code === \"NoSuchKey\") ||\n\t\t\t\t(typeof currentError.name === \"string\" &&\n\t\t\t\t\t(currentError.name === \"NoSuchKey\" || currentError.name === \"NotFound\"))\n\t\t\t);\n\t\t}),\n\t);\n\nconst isNotModifiedError = (error: unknown): boolean =>\n\tEffect.runSync(\n\t\tEffect.sync(() => {\n\t\t\tif (typeof error !== \"object\") {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst errorOption = Option.fromNullable(error);\n\t\t\tif (Option.isNone(errorOption)) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst currentError = errorOption.value as {\n\t\t\t\tCode?: unknown;\n\t\t\t\tname?: unknown;\n\t\t\t\t$metadata?: unknown;\n\t\t\t};\n\t\t\tconst metadata =\n\t\t\t\ttypeof currentError.$metadata === \"object\" &&\n\t\t\t\tOption.isSome(Option.fromNullable(currentError.$metadata))\n\t\t\t\t\t? Option.some(currentError.$metadata as { httpStatusCode?: number })\n\t\t\t\t\t: Option.none<{ httpStatusCode?: number }>();\n\n\t\t\treturn (\n\t\t\t\t(typeof currentError.Code === \"string\" && currentError.Code === \"NotModified\") ||\n\t\t\t\t(typeof currentError.name === \"string\" && currentError.name === \"NotModified\") ||\n\t\t\t\tOption.exists(metadata, (currentMetadata) => currentMetadata.httpStatusCode === 304)\n\t\t\t);\n\t\t}),\n\t);\n\nconst getEtag = (result: { ETag?: string }, key: string): Effect.Effect<{ etag: string }> =>\n\tOption.fromNullable(result.ETag).pipe(\n\t\tOption.match({\n\t\t\tonNone: () => Effect.die(new Error(`Expected ETag for ${key}`)),\n\t\t\tonSome: (etag) => Effect.succeed({ etag }),\n\t\t}),\n\t);\n\nconst readBodyBytes = (body: unknown, key: string): Effect.Effect<Uint8Array> => {\n\tif (body instanceof Uint8Array) {\n\t\treturn Effect.succeed(body);\n\t}\n\n\tif (typeof body === \"object\") {\n\t\tconst bodyOption = Option.fromNullable(body);\n\t\tif (\n\t\t\tOption.isSome(bodyOption) &&\n\t\t\t\"transformToByteArray\" in bodyOption.value &&\n\t\t\ttypeof bodyOption.value.transformToByteArray === \"function\"\n\t\t) {\n\t\t\tconst currentBody = bodyOption.value as {\n\t\t\t\ttransformToByteArray: () => Promise<Uint8Array>;\n\t\t\t};\n\t\t\treturn Effect.promise(() => currentBody.transformToByteArray()).pipe(Effect.orDie);\n\t\t}\n\t}\n\n\treturn Effect.die(new Error(`Expected readable body for ${key}`));\n};\n\nconst readBodyStream = (body: unknown, key: string): Effect.Effect<Stream.Stream<Uint8Array>> => {\n\tif (body instanceof Uint8Array) {\n\t\treturn Effect.succeed(Stream.fromIterable([body]));\n\t}\n\n\tif (body instanceof ReadableStream) {\n\t\treturn Effect.succeed(\n\t\t\tStream.fromReadableStream<Uint8Array, Error>(\n\t\t\t\t() => body as ReadableStream<Uint8Array>,\n\t\t\t\t() => new Error(`Failed to stream ${key}`),\n\t\t\t).pipe(Stream.orDie),\n\t\t);\n\t}\n\n\tif (typeof body === \"object\") {\n\t\tconst bodyOption = Option.fromNullable(body);\n\t\tif (\n\t\t\tOption.isSome(bodyOption) &&\n\t\t\t\"transformToWebStream\" in bodyOption.value &&\n\t\t\ttypeof bodyOption.value.transformToWebStream === \"function\"\n\t\t) {\n\t\t\tconst currentBody = bodyOption.value as {\n\t\t\t\ttransformToWebStream: () => ReadableStream<Uint8Array>;\n\t\t\t};\n\t\t\treturn Effect.succeed(\n\t\t\t\tStream.fromReadableStream<Uint8Array, Error>(\n\t\t\t\t\t() => currentBody.transformToWebStream(),\n\t\t\t\t\t() => new Error(`Failed to stream ${key}`),\n\t\t\t\t).pipe(Stream.orDie),\n\t\t\t);\n\t\t}\n\t}\n\n\tif (typeof body === \"object\") {\n\t\tconst bodyOption = Option.fromNullable(body);\n\t\tif (\n\t\t\tOption.isSome(bodyOption) &&\n\t\t\t\"transformToByteArray\" in bodyOption.value &&\n\t\t\ttypeof bodyOption.value.transformToByteArray === \"function\"\n\t\t) {\n\t\t\treturn readBodyBytes(bodyOption.value, key).pipe(\n\t\t\t\tEffect.map((bytes) => Stream.fromIterable([bytes])),\n\t\t\t);\n\t\t}\n\t}\n\n\treturn Effect.die(new Error(`Expected streamable body for ${key}`));\n};\n\nconst minimumMultipartPartSize = 5 * 1024 * 1024;\n\nconst combineChunks = (chunks: readonly Uint8Array[], totalLength: number): Uint8Array => {\n\tconst combined = new Uint8Array(totalLength);\n\tlet offset = 0;\n\n\tfor (const chunk of chunks) {\n\t\tcombined.set(chunk, offset);\n\t\toffset += chunk.length;\n\t}\n\n\treturn combined;\n};\n\nexport const makeS3KV = (bucket: string): Effect.Effect<KV, never, S3Service> =>\n\tEffect.gen(function* () {\n\t\tconst s3 = yield* S3;\n\n\t\treturn {\n\t\t\tget: (key) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* Effect.logDebug(`s3KV.get(${key})`);\n\n\t\t\t\t\tconst response = yield* Effect.either(\n\t\t\t\t\t\ts3.getObject({ Bucket: bucket, Key: key }),\n\t\t\t\t\t);\n\t\t\t\t\tif (response._tag === \"Left\") {\n\t\t\t\t\t\tif (isMissingObjectError(response.left)) {\n\t\t\t\t\t\t\tyield* Effect.logDebug(`s3KV.get(${key}) => None`);\n\t\t\t\t\t\t\treturn Option.none();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn yield* Effect.die(\n\t\t\t\t\t\t\tresponse.left instanceof Error\n\t\t\t\t\t\t\t\t? response.left\n\t\t\t\t\t\t\t\t: new Error(`Failed to read ${key}: ${String(response.left)}`),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { etag } = yield* getEtag(response.right, key);\n\t\t\t\t\tconst value = yield* readBodyBytes(response.right.Body, key);\n\t\t\t\t\tyield* Effect.logDebug(`s3KV.get(${key}) => Some(${etag})`);\n\t\t\t\t\treturn Option.some({ etag, value });\n\t\t\t\t}),\n\t\t\tgetIfChanged: (key, etag) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* Effect.logDebug(`s3KV.getIfChanged(${key}, ${etag})`);\n\n\t\t\t\t\tconst response = yield* Effect.either(\n\t\t\t\t\t\ts3.getObject({ Bucket: bucket, IfNoneMatch: etag, Key: key }),\n\t\t\t\t\t);\n\t\t\t\t\tif (response._tag === \"Left\") {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tisMissingObjectError(response.left) ||\n\t\t\t\t\t\t\tisNotModifiedError(response.left)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tyield* Effect.logDebug(`s3KV.getIfChanged(${key}) => None`);\n\t\t\t\t\t\t\treturn Option.none();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn yield* Effect.die(\n\t\t\t\t\t\t\tresponse.left instanceof Error\n\t\t\t\t\t\t\t\t? response.left\n\t\t\t\t\t\t\t\t: new Error(`Failed to read ${key}: ${String(response.left)}`),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { etag: currentEtag } = yield* getEtag(response.right, key);\n\t\t\t\t\tconst value = yield* readBodyBytes(response.right.Body, key);\n\t\t\t\t\tyield* Effect.logDebug(`s3KV.getIfChanged(${key}) => Some(${currentEtag})`);\n\t\t\t\t\treturn Option.some({ etag: currentEtag, value });\n\t\t\t\t}),\n\t\t\texists: (key) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* Effect.logDebug(`s3KV.exists(${key})`);\n\n\t\t\t\t\tconst response = yield* Effect.either(\n\t\t\t\t\t\ts3.headObject({ Bucket: bucket, Key: key }),\n\t\t\t\t\t);\n\t\t\t\t\tif (response._tag === \"Left\") {\n\t\t\t\t\t\tif (isMissingObjectError(response.left)) {\n\t\t\t\t\t\t\tyield* Effect.logDebug(`s3KV.exists(${key}) => false`);\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn yield* Effect.die(\n\t\t\t\t\t\t\tresponse.left instanceof Error\n\t\t\t\t\t\t\t\t? response.left\n\t\t\t\t\t\t\t\t: new Error(`Failed to stat ${key}: ${String(response.left)}`),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tyield* Effect.logDebug(`s3KV.exists(${key}) => true`);\n\t\t\t\t\treturn true;\n\t\t\t\t}),\n\t\t\tset: (key, value) =>\n\t\t\t\tEffect.logDebug(`s3KV.set(${key}, ${value.length} bytes)`).pipe(\n\t\t\t\t\tEffect.flatMap(() => s3.putObject({ Body: value, Bucket: bucket, Key: key })),\n\t\t\t\t\tEffect.orDieWith((error) =>\n\t\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t\t? error\n\t\t\t\t\t\t\t: new Error(`Failed to write ${key}: ${String(error)}`),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.flatMap((result) => getEtag(result, key)),\n\t\t\t\t\tEffect.tap((result) => Effect.logDebug(`s3KV.set(${key}) => ${result.etag}`)),\n\t\t\t\t),\n\t\t\tcas: (key, value, etag) =>\n\t\t\t\tEffect.logDebug(`s3KV.cas(${key}, ${value.length} bytes, ${etag})`).pipe(\n\t\t\t\t\tEffect.flatMap(() =>\n\t\t\t\t\t\ts3.putObject({ Body: value, Bucket: bucket, IfMatch: etag, Key: key }),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.catchAll((error) => {\n\t\t\t\t\t\tconst currentError =\n\t\t\t\t\t\t\ttypeof error === \"object\" && Option.isSome(Option.fromNullable(error))\n\t\t\t\t\t\t\t\t? Option.some(\n\t\t\t\t\t\t\t\t\t\terror as {\n\t\t\t\t\t\t\t\t\t\t\tCode?: unknown;\n\t\t\t\t\t\t\t\t\t\t\tname?: unknown;\n\t\t\t\t\t\t\t\t\t\t\t$metadata?: unknown;\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t: Option.none<{\n\t\t\t\t\t\t\t\t\t\tCode?: unknown;\n\t\t\t\t\t\t\t\t\t\tname?: unknown;\n\t\t\t\t\t\t\t\t\t\t$metadata?: unknown;\n\t\t\t\t\t\t\t\t\t}>();\n\t\t\t\t\t\tconst metadata =\n\t\t\t\t\t\t\tOption.isSome(currentError) &&\n\t\t\t\t\t\t\ttypeof currentError.value.$metadata === \"object\" &&\n\t\t\t\t\t\t\tOption.isSome(Option.fromNullable(currentError.value.$metadata))\n\t\t\t\t\t\t\t\t? Option.some(\n\t\t\t\t\t\t\t\t\t\tcurrentError.value.$metadata as { httpStatusCode?: number },\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t: Option.none<{ httpStatusCode?: number }>();\n\n\t\t\t\t\t\treturn Option.isSome(currentError) &&\n\t\t\t\t\t\t\t((typeof currentError.value.Code === \"string\" &&\n\t\t\t\t\t\t\t\tcurrentError.value.Code === \"PreconditionFailed\") ||\n\t\t\t\t\t\t\t\t(typeof currentError.value.name === \"string\" &&\n\t\t\t\t\t\t\t\t\tcurrentError.value.name === \"PreconditionFailed\")) &&\n\t\t\t\t\t\t\tOption.exists(\n\t\t\t\t\t\t\t\tmetadata,\n\t\t\t\t\t\t\t\t(currentMetadata) => currentMetadata.httpStatusCode === 412,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t? Effect.fail(new ConflictError({ key }))\n\t\t\t\t\t\t\t: Effect.die(\n\t\t\t\t\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t\t\t\t\t? error\n\t\t\t\t\t\t\t\t\t\t: new Error(`Failed to write ${key}: ${String(error)}`),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t}),\n\t\t\t\t\tEffect.flatMap((result) => getEtag(result, key)),\n\t\t\t\t\tEffect.tap((result) => Effect.logDebug(`s3KV.cas(${key}) => ${result.etag}`)),\n\t\t\t\t),\n\t\t\tdelete: (key) =>\n\t\t\t\tEffect.logDebug(`s3KV.delete(${key})`).pipe(\n\t\t\t\t\tEffect.flatMap(() => s3.deleteObject({ Bucket: bucket, Key: key })),\n\t\t\t\t\tEffect.asVoid,\n\t\t\t\t\tEffect.orDieWith((error) =>\n\t\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t\t? error\n\t\t\t\t\t\t\t: new Error(`Failed to delete ${key}: ${String(error)}`),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.tap(() => Effect.logDebug(`s3KV.delete(${key}) => void`)),\n\t\t\t\t),\n\t\t\treadStream: (key) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* Effect.logDebug(`s3KV.readStream(${key})`);\n\n\t\t\t\t\tconst response = yield* Effect.either(\n\t\t\t\t\t\ts3.getObject({ Bucket: bucket, Key: key }),\n\t\t\t\t\t);\n\t\t\t\t\tif (response._tag === \"Left\") {\n\t\t\t\t\t\tif (isMissingObjectError(response.left)) {\n\t\t\t\t\t\t\tyield* Effect.logDebug(`s3KV.readStream(${key}) => None`);\n\t\t\t\t\t\t\treturn Option.none();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn yield* Effect.die(\n\t\t\t\t\t\t\tresponse.left instanceof Error\n\t\t\t\t\t\t\t\t? response.left\n\t\t\t\t\t\t\t\t: new Error(`Failed to stream ${key}: ${String(response.left)}`),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst stream = yield* readBodyStream(response.right.Body, key);\n\t\t\t\t\tyield* Effect.logDebug(`s3KV.readStream(${key}) => Some(stream)`);\n\t\t\t\t\treturn Option.some(stream);\n\t\t\t\t}),\n\t\t\twriteStream: (key, content) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* Effect.logDebug(`s3KV.writeStream(${key}, stream)`);\n\n\t\t\t\t\tconst multipartUpload = yield* s3\n\t\t\t\t\t\t.createMultipartUpload({ Bucket: bucket, Key: key })\n\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\tEffect.orDieWith((error) =>\n\t\t\t\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t\t\t\t? error\n\t\t\t\t\t\t\t\t\t: new Error(`Failed to start upload ${key}: ${String(error)}`),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\tconst uploadIdOption = Option.fromNullable(multipartUpload.UploadId);\n\t\t\t\t\tif (Option.isNone(uploadIdOption)) {\n\t\t\t\t\t\treturn yield* Effect.die(new Error(`Expected UploadId for ${key}`));\n\t\t\t\t\t}\n\n\t\t\t\t\tconst uploadId = uploadIdOption.value;\n\t\t\t\t\tconst uploadParts = Effect.scoped(\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tconst pull = yield* Stream.toPull(content);\n\t\t\t\t\t\t\tconst completedParts: { ETag: string; PartNumber: number }[] = [];\n\t\t\t\t\t\t\tlet bufferedChunks: Uint8Array[] = [];\n\t\t\t\t\t\t\tlet bufferedLength = 0;\n\t\t\t\t\t\t\tlet partNumber = 1;\n\n\t\t\t\t\t\t\tconst uploadBufferedPart = (force: boolean): Effect.Effect<void> => {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\tbufferedLength === 0 ||\n\t\t\t\t\t\t\t\t\t(!force && bufferedLength < minimumMultipartPartSize)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn Effect.void;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst body = combineChunks(bufferedChunks, bufferedLength);\n\t\t\t\t\t\t\t\tbufferedChunks = [];\n\t\t\t\t\t\t\t\tbufferedLength = 0;\n\n\t\t\t\t\t\t\t\treturn s3\n\t\t\t\t\t\t\t\t\t.uploadPart({\n\t\t\t\t\t\t\t\t\t\tBody: body,\n\t\t\t\t\t\t\t\t\t\tBucket: bucket,\n\t\t\t\t\t\t\t\t\t\tContentLength: body.length,\n\t\t\t\t\t\t\t\t\t\tKey: key,\n\t\t\t\t\t\t\t\t\t\tPartNumber: partNumber,\n\t\t\t\t\t\t\t\t\t\tUploadId: uploadId,\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\t\t\tEffect.orDieWith((error) =>\n\t\t\t\t\t\t\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t\t\t\t\t\t\t? error\n\t\t\t\t\t\t\t\t\t\t\t\t: new Error(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t`Failed to upload ${key} part ${partNumber}: ${String(error)}`,\n\t\t\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\tEffect.flatMap((result) =>\n\t\t\t\t\t\t\t\t\t\t\tOption.fromNullable(result.ETag).pipe(\n\t\t\t\t\t\t\t\t\t\t\t\tOption.match({\n\t\t\t\t\t\t\t\t\t\t\t\t\tonNone: () =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tEffect.die(\n\t\t\t\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\t\t\t`Expected ETag for ${key} part ${partNumber}`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t\tonSome: (etag) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcompletedParts.push({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tETag: etag,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tPartNumber: partNumber,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpartNumber += 1;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t\t\t\t}),\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);\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\twhile (true) {\n\t\t\t\t\t\t\t\tconst nextChunk = yield* Effect.either(pull);\n\n\t\t\t\t\t\t\t\tif (nextChunk._tag === \"Left\") {\n\t\t\t\t\t\t\t\t\treturn yield* uploadBufferedPart(true).pipe(\n\t\t\t\t\t\t\t\t\t\tEffect.as(completedParts),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tfor (const chunk of Chunk.toReadonlyArray(nextChunk.right)) {\n\t\t\t\t\t\t\t\t\tbufferedChunks.push(chunk);\n\t\t\t\t\t\t\t\t\tbufferedLength += chunk.length;\n\t\t\t\t\t\t\t\t\tif (bufferedLength >= minimumMultipartPartSize) {\n\t\t\t\t\t\t\t\t\t\tyield* uploadBufferedPart(false);\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}\n\t\t\t\t\t\t}),\n\t\t\t\t\t).pipe(\n\t\t\t\t\t\tEffect.catchAllCause((cause) =>\n\t\t\t\t\t\t\ts3\n\t\t\t\t\t\t\t\t.abortMultipartUpload({\n\t\t\t\t\t\t\t\t\tBucket: bucket,\n\t\t\t\t\t\t\t\t\tKey: key,\n\t\t\t\t\t\t\t\t\tUploadId: uploadId,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\t\tEffect.orDie,\n\t\t\t\t\t\t\t\t\tEffect.catchAll(() => Effect.void),\n\t\t\t\t\t\t\t\t\tEffect.zipRight(Effect.failCause(cause)),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\n\t\t\t\t\tconst completedParts = yield* uploadParts;\n\n\t\t\t\t\tif (completedParts.length === 0) {\n\t\t\t\t\t\tconst emptyResult = yield* s3\n\t\t\t\t\t\t\t.putObject({ Body: new Uint8Array(), Bucket: bucket, Key: key })\n\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\tEffect.orDieWith((error) =>\n\t\t\t\t\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t\t\t\t\t? error\n\t\t\t\t\t\t\t\t\t\t: new Error(`Failed to upload ${key}: ${String(error)}`),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\tconst emptyEtagResult = yield* getEtag(emptyResult, key);\n\t\t\t\t\t\tyield* Effect.logDebug(\n\t\t\t\t\t\t\t`s3KV.writeStream(${key}) => ${emptyEtagResult.etag}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn emptyEtagResult;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst result = yield* s3\n\t\t\t\t\t\t.completeMultipartUpload({\n\t\t\t\t\t\t\tBucket: bucket,\n\t\t\t\t\t\t\tKey: key,\n\t\t\t\t\t\t\tMultipartUpload: { Parts: completedParts },\n\t\t\t\t\t\t\tUploadId: uploadId,\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\tEffect.orDieWith((error) =>\n\t\t\t\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t\t\t\t? error\n\t\t\t\t\t\t\t\t\t: new Error(`Failed to upload ${key}: ${String(error)}`),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\n\t\t\t\t\tconst finalResult = yield* getEtag(result, key);\n\t\t\t\t\tyield* Effect.logDebug(`s3KV.writeStream(${key}) => ${finalResult.etag}`);\n\t\t\t\t\treturn finalResult;\n\t\t\t\t}),\n\t\t} satisfies KV;\n\t});"]}
@@ -0,0 +1,27 @@
1
+ import { Effect, Option } from "effect";
2
+ import { LocalKV, RemoteKV } from "../storage.js";
3
+ export const pullFiles = (keys) => Effect.forEach(keys, (key) => Effect.gen(function* () {
4
+ const localKV = yield* LocalKV;
5
+ const remoteKV = yield* RemoteKV;
6
+ if (yield* localKV.exists(key)) {
7
+ return;
8
+ }
9
+ const readStream = yield* remoteKV.readStream(key);
10
+ if (Option.isNone(readStream)) {
11
+ return yield* Effect.fail(new Error(`File ${key} does not exist remotely or cannot be read`));
12
+ }
13
+ yield* localKV.writeStream(key, readStream.value);
14
+ }));
15
+ export const pushFiles = (keys) => Effect.forEach(keys, (key) => Effect.gen(function* () {
16
+ const localKV = yield* LocalKV;
17
+ const remoteKV = yield* RemoteKV;
18
+ if (yield* remoteKV.exists(key)) {
19
+ return;
20
+ }
21
+ const readStream = yield* localKV.readStream(key);
22
+ if (Option.isNone(readStream)) {
23
+ return yield* Effect.fail(new Error(`File ${key} does not exist locally or cannot be read`));
24
+ }
25
+ yield* remoteKV.writeStream(key, readStream.value);
26
+ }));
27
+ //# sourceMappingURL=syncFiles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syncFiles.js","sourceRoot":"","sources":["../../../../src/kv/syncFiles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAc,EAAE,EAAE,CAC3C,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CAC5B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC;IAC/B,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC;IAEjC,IAAI,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QAChC,OAAO;IACR,CAAC;IAED,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAEnD,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CACxB,IAAI,KAAK,CAAC,QAAQ,GAAG,4CAA4C,CAAC,CAClE,CAAC;IACH,CAAC;IAED,KAAK,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;AACnD,CAAC,CAAC,CACF,CAAC;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAc,EAAE,EAAE,CAC3C,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CAC5B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC;IAC/B,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC;IAEjC,IAAI,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACjC,OAAO;IACR,CAAC;IAED,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAElD,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CACxB,IAAI,KAAK,CAAC,QAAQ,GAAG,2CAA2C,CAAC,CACjE,CAAC;IACH,CAAC;IAED,KAAK,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;AACpD,CAAC,CAAC,CACF,CAAC","sourcesContent":["import { Effect, Option } from \"effect\";\n\nimport { LocalKV, RemoteKV } from \"../storage\";\n\nexport const pullFiles = (keys: string[]) =>\n\tEffect.forEach(keys, (key) =>\n\t\tEffect.gen(function* () {\n\t\t\tconst localKV = yield* LocalKV;\n\t\t\tconst remoteKV = yield* RemoteKV;\n\n\t\t\tif (yield* localKV.exists(key)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst readStream = yield* remoteKV.readStream(key);\n\n\t\t\tif (Option.isNone(readStream)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew Error(`File ${key} does not exist remotely or cannot be read`),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tyield* localKV.writeStream(key, readStream.value);\n\t\t}),\n\t);\n\nexport const pushFiles = (keys: string[]) =>\n\tEffect.forEach(keys, (key) =>\n\t\tEffect.gen(function* () {\n\t\t\tconst localKV = yield* LocalKV;\n\t\t\tconst remoteKV = yield* RemoteKV;\n\n\t\t\tif (yield* remoteKV.exists(key)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst readStream = yield* localKV.readStream(key);\n\n\t\t\tif (Option.isNone(readStream)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew Error(`File ${key} does not exist locally or cannot be read`),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tyield* remoteKV.writeStream(key, readStream.value);\n\t\t}),\n\t);"]}