@lika85456/s3qlite 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/effect.js +18 -0
- package/cjs/effect.js.map +1 -0
- package/cjs/index.js +40 -0
- package/cjs/index.js.map +1 -0
- package/cjs/package.json +3 -0
- package/cjs/src/batches.js +39 -0
- package/cjs/src/batches.js.map +1 -0
- package/cjs/src/cdc/apply.js +404 -0
- package/cjs/src/cdc/apply.js.map +1 -0
- package/cjs/src/cdc/extract.js +38 -0
- package/cjs/src/cdc/extract.js.map +1 -0
- package/cjs/src/cdc/protobuf.js +135 -0
- package/cjs/src/cdc/protobuf.js.map +1 -0
- package/cjs/src/cdc/testUtils.js +49 -0
- package/cjs/src/cdc/testUtils.js.map +1 -0
- package/cjs/src/cdc/truncate.js +7 -0
- package/cjs/src/cdc/truncate.js.map +1 -0
- package/cjs/src/cdc/types.js +10 -0
- package/cjs/src/cdc/types.js.map +1 -0
- package/cjs/src/cdc/withoutCDC.js +7 -0
- package/cjs/src/cdc/withoutCDC.js.map +1 -0
- package/cjs/src/connection.js +128 -0
- package/cjs/src/connection.js.map +1 -0
- package/cjs/src/contexts.js +11 -0
- package/cjs/src/contexts.js.map +1 -0
- package/cjs/src/index.js +30 -0
- package/cjs/src/index.js.map +1 -0
- package/cjs/src/kv/fileKV.js +131 -0
- package/cjs/src/kv/fileKV.js.map +1 -0
- package/cjs/src/kv/kv.js +8 -0
- package/cjs/src/kv/kv.js.map +1 -0
- package/cjs/src/kv/memoryKV.js +16 -0
- package/cjs/src/kv/memoryKV.js.map +1 -0
- package/cjs/src/kv/s3KV.js +283 -0
- package/cjs/src/kv/s3KV.js.map +1 -0
- package/cjs/src/kv/syncFiles.js +32 -0
- package/cjs/src/kv/syncFiles.js.map +1 -0
- package/cjs/src/pull.js +101 -0
- package/cjs/src/pull.js.map +1 -0
- package/cjs/src/push.js +58 -0
- package/cjs/src/push.js.map +1 -0
- package/cjs/src/storage.js +41 -0
- package/cjs/src/storage.js.map +1 -0
- package/cjs/src/types.js +3 -0
- package/cjs/src/types.js.map +1 -0
- package/cjs/src/wrapDatabase.js +80 -0
- package/cjs/src/wrapDatabase.js.map +1 -0
- package/dts/effect.d.ts +1 -0
- package/dts/index.d.ts +16 -0
- package/dts/src/batches.d.ts +10 -0
- package/dts/src/cdc/apply.d.ts +14 -0
- package/dts/src/cdc/extract.d.ts +8 -0
- package/dts/src/cdc/protobuf.d.ts +3 -0
- package/dts/src/cdc/testUtils.d.ts +19 -0
- package/dts/src/cdc/truncate.d.ts +6 -0
- package/dts/src/cdc/types.d.ts +35 -0
- package/dts/src/cdc/withoutCDC.d.ts +3 -0
- package/dts/src/connection.d.ts +5 -0
- package/dts/src/contexts.d.ts +22 -0
- package/dts/src/index.d.ts +12 -0
- package/dts/src/kv/fileKV.d.ts +5 -0
- package/dts/src/kv/kv.d.ts +42 -0
- package/dts/src/kv/memoryKV.d.ts +5 -0
- package/dts/src/kv/s3KV.d.ts +4 -0
- package/dts/src/kv/syncFiles.d.ts +4 -0
- package/dts/src/pull.d.ts +8 -0
- package/dts/src/push.d.ts +4 -0
- package/dts/src/storage.d.ts +22 -0
- package/dts/src/types.d.ts +38 -0
- package/dts/src/wrapDatabase.d.ts +1 -0
- package/esm/effect.js +2 -0
- package/esm/effect.js.map +1 -0
- package/esm/index.js +22 -0
- package/esm/index.js.map +1 -0
- package/esm/src/batches.js +34 -0
- package/esm/src/batches.js.map +1 -0
- package/esm/src/cdc/apply.js +398 -0
- package/esm/src/cdc/apply.js.map +1 -0
- package/esm/src/cdc/extract.js +33 -0
- package/esm/src/cdc/extract.js.map +1 -0
- package/esm/src/cdc/protobuf.js +127 -0
- package/esm/src/cdc/protobuf.js.map +1 -0
- package/esm/src/cdc/testUtils.js +42 -0
- package/esm/src/cdc/testUtils.js.map +1 -0
- package/esm/src/cdc/truncate.js +3 -0
- package/esm/src/cdc/truncate.js.map +1 -0
- package/esm/src/cdc/types.js +7 -0
- package/esm/src/cdc/types.js.map +1 -0
- package/esm/src/cdc/withoutCDC.js +3 -0
- package/esm/src/cdc/withoutCDC.js.map +1 -0
- package/esm/src/connection.js +123 -0
- package/esm/src/connection.js.map +1 -0
- package/esm/src/contexts.js +6 -0
- package/esm/src/contexts.js.map +1 -0
- package/esm/src/index.js +12 -0
- package/esm/src/index.js.map +1 -0
- package/esm/src/kv/fileKV.js +127 -0
- package/esm/src/kv/fileKV.js.map +1 -0
- package/esm/src/kv/kv.js +4 -0
- package/esm/src/kv/kv.js.map +1 -0
- package/esm/src/kv/memoryKV.js +12 -0
- package/esm/src/kv/memoryKV.js.map +1 -0
- package/esm/src/kv/s3KV.js +279 -0
- package/esm/src/kv/s3KV.js.map +1 -0
- package/esm/src/kv/syncFiles.js +27 -0
- package/esm/src/kv/syncFiles.js.map +1 -0
- package/esm/src/pull.js +97 -0
- package/esm/src/pull.js.map +1 -0
- package/esm/src/push.js +54 -0
- package/esm/src/push.js.map +1 -0
- package/esm/src/storage.js +26 -0
- package/esm/src/storage.js.map +1 -0
- package/esm/src/types.js +2 -0
- package/esm/src/types.js.map +1 -0
- package/esm/src/wrapDatabase.js +76 -0
- package/esm/src/wrapDatabase.js.map +1 -0
- package/package.json +35 -0
|
@@ -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});"]}
|
package/esm/src/kv/kv.js
ADDED
|
@@ -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);"]}
|