@reventlessdev/reventless-local 3.0.0-alpha.199 → 3.0.0-alpha.201

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 (40) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/package.json +10 -9
  3. package/rescript.json +2 -1
  4. package/src/Platform.res +137 -86
  5. package/src/Platform.res.mjs +57 -44
  6. package/src/adapter/BackendState.res +13 -0
  7. package/src/adapter/BackendState.res.mjs +17 -1
  8. package/src/adapter/DcbEventLog/LocalDcbEventLogStorage.res.mjs +1 -1
  9. package/src/adapter/DomainGraphQL_Server.res +5 -5
  10. package/src/adapter/DomainGraphQL_Server.res.mjs +1 -1
  11. package/src/adapter/EventLog/LocalEventLogStorage.res.mjs +1 -1
  12. package/src/adapter/LocalBus.res +6 -46
  13. package/src/adapter/LocalBus.res.mjs +0 -29
  14. package/src/adapter/LocalStateChangeDescriptor.res +93 -0
  15. package/src/adapter/LocalStateChangeDescriptor.res.mjs +61 -0
  16. package/src/adapter/LocalUploadResolvers.res +16 -3
  17. package/src/adapter/LocalUploadResolvers.res.mjs +6 -4
  18. package/src/adapter/ObjectStore/LocalObjectStore.res +157 -0
  19. package/src/adapter/ObjectStore/LocalObjectStore.res.mjs +129 -0
  20. package/src/adapter/ObjectStore/ObjectStoreStorage_FileSystem.res +215 -0
  21. package/src/adapter/ObjectStore/ObjectStoreStorage_FileSystem.res.mjs +286 -0
  22. package/src/adapter/ObjectStore/ObjectStoreStorage_InMemory.res +33 -0
  23. package/src/adapter/ObjectStore/ObjectStoreStorage_InMemory.res.mjs +47 -0
  24. package/src/adapter/QueryDb/LocalQueryDbStorage.res.mjs +1 -1
  25. package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +4 -2
  26. package/src/adapter/QueryDb/QueryDbStorage_InMemory.res.mjs +3 -3
  27. package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +4 -2
  28. package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res.mjs +3 -3
  29. package/src/reset/LocalSeedReset.res +552 -0
  30. package/src/reset/LocalSeedReset.res.mjs +533 -0
  31. package/tests/adapter/BackendParityTest.res +51 -0
  32. package/tests/adapter/BackendParityTest.res.mjs +44 -0
  33. package/tests/adapter/GraphQL_SubscriptionResolversTest.res +6 -4
  34. package/tests/adapter/GraphQL_SubscriptionResolversTest.res.mjs +4 -3
  35. package/tests/adapter/ObjectStorePersistenceTest.res +243 -0
  36. package/tests/adapter/ObjectStorePersistenceTest.res.mjs +159 -0
  37. package/tests/reset/LocalSeedResetTest.res +252 -0
  38. package/tests/reset/LocalSeedResetTest.res.mjs +280 -0
  39. package/src/adapter/LocalObjectStore.res +0 -70
  40. package/src/adapter/LocalObjectStore.res.mjs +0 -60
@@ -0,0 +1,129 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as StoreLayout$ReventlessCore from "@reventlessdev/reventless-core/src/util/StoreLayout.res.mjs";
6
+ import * as BackendState$ReventlessLocal from "../BackendState.res.mjs";
7
+ import * as ObjectStoreStorage_InMemory$ReventlessLocal from "./ObjectStoreStorage_InMemory.res.mjs";
8
+ import * as ObjectStoreStorage_FileSystem$ReventlessLocal from "./ObjectStoreStorage_FileSystem.res.mjs";
9
+
10
+ let defaultUploadPrefix = "uploads";
11
+
12
+ let servedPrefixes = {
13
+ contents: [defaultUploadPrefix]
14
+ };
15
+
16
+ function registerServedPrefix(prefix) {
17
+ if (!servedPrefixes.contents.includes(prefix)) {
18
+ servedPrefixes.contents = servedPrefixes.contents.concat([prefix]);
19
+ return;
20
+ }
21
+ }
22
+
23
+ let declaredStores = {};
24
+
25
+ function localPrefixFor(qualified) {
26
+ return defaultUploadPrefix + `/` + StoreLayout$ReventlessCore.prefixOfQualified(qualified);
27
+ }
28
+
29
+ function registerStore(qualified, prefix) {
30
+ declaredStores[qualified] = prefix;
31
+ registerServedPrefix(prefix);
32
+ }
33
+
34
+ function storePrefix(qualified) {
35
+ return declaredStores[qualified];
36
+ }
37
+
38
+ function declaredStoreList() {
39
+ return Object.entries(declaredStores);
40
+ }
41
+
42
+ function servedKey(path) {
43
+ let trimmed = path.startsWith("/") ? path.slice(1, path.length) : path;
44
+ let segments = trimmed.split("/");
45
+ let underAPrefix = servedPrefixes.contents.some(p => {
46
+ if (p.length > 0 && trimmed.startsWith(p + "/")) {
47
+ return trimmed.length > (p.length + 1 | 0);
48
+ } else {
49
+ return false;
50
+ }
51
+ });
52
+ if (underAPrefix && segments.every(seg => seg !== "" && seg !== "." ? seg !== ".." : false)) {
53
+ return trimmed;
54
+ }
55
+ }
56
+
57
+ function put(key, bytes, contentType) {
58
+ let root = BackendState$ReventlessLocal.getObjectStoreRoot();
59
+ if (root !== undefined) {
60
+ return ObjectStoreStorage_FileSystem$ReventlessLocal.put(root, key, bytes, contentType);
61
+ } else {
62
+ return ObjectStoreStorage_InMemory$ReventlessLocal.put(key, bytes, contentType);
63
+ }
64
+ }
65
+
66
+ function get(key) {
67
+ let root = BackendState$ReventlessLocal.getObjectStoreRoot();
68
+ let stored = root !== undefined ? ObjectStoreStorage_FileSystem$ReventlessLocal.get(root, key) : ObjectStoreStorage_InMemory$ReventlessLocal.get(key);
69
+ return Stdlib_Option.map(stored, param => ({
70
+ bytes: param[0],
71
+ contentType: param[1]
72
+ }));
73
+ }
74
+
75
+ function $$delete(key) {
76
+ let root = BackendState$ReventlessLocal.getObjectStoreRoot();
77
+ if (root !== undefined) {
78
+ return ObjectStoreStorage_FileSystem$ReventlessLocal.$$delete(root, key);
79
+ } else {
80
+ return ObjectStoreStorage_InMemory$ReventlessLocal.$$delete(key);
81
+ }
82
+ }
83
+
84
+ function putOffload(key, bytes) {
85
+ let root = BackendState$ReventlessLocal.getObjectStoreRoot();
86
+ if (root !== undefined) {
87
+ return ObjectStoreStorage_FileSystem$ReventlessLocal.putOffload(root, key, bytes);
88
+ } else {
89
+ return ObjectStoreStorage_InMemory$ReventlessLocal.putOffload(key, bytes);
90
+ }
91
+ }
92
+
93
+ function getOffload(key) {
94
+ let root = BackendState$ReventlessLocal.getObjectStoreRoot();
95
+ if (root !== undefined) {
96
+ return ObjectStoreStorage_FileSystem$ReventlessLocal.getOffload(root, key);
97
+ } else {
98
+ return ObjectStoreStorage_InMemory$ReventlessLocal.getOffload(key);
99
+ }
100
+ }
101
+
102
+ function reset() {
103
+ ObjectStoreStorage_InMemory$ReventlessLocal.reset();
104
+ let root = BackendState$ReventlessLocal.getObjectStoreRoot();
105
+ if (root !== undefined) {
106
+ ObjectStoreStorage_FileSystem$ReventlessLocal.reset(root);
107
+ }
108
+ servedPrefixes.contents = [defaultUploadPrefix];
109
+ Object.keys(declaredStores).forEach(k => Stdlib_Dict.$$delete(declaredStores, k));
110
+ }
111
+
112
+ export {
113
+ defaultUploadPrefix,
114
+ servedPrefixes,
115
+ registerServedPrefix,
116
+ declaredStores,
117
+ localPrefixFor,
118
+ registerStore,
119
+ storePrefix,
120
+ declaredStoreList,
121
+ servedKey,
122
+ put,
123
+ get,
124
+ $$delete,
125
+ putOffload,
126
+ getOffload,
127
+ reset,
128
+ }
129
+ /* BackendState-ReventlessLocal Not a pure module */
@@ -0,0 +1,215 @@
1
+ // On-disk arm of the local object store: bytes under the same directory the
2
+ // SQLite database lives in, so uploads and offloaded payloads survive a restart
3
+ // exactly like the events that reference them.
4
+ //
5
+ // The two were previously out of step: with `REVENTLESS_LOCAL_BACKEND=sqlite` an
6
+ // event carrying an `Offloaded{store, key, …}` reference outlived the bytes it
7
+ // pointed at, so a restart replayed events whose payloads had evaporated.
8
+ //
9
+ // Layout, rooted at the SQLite file's directory (`./.reventless` by convention):
10
+ //
11
+ // objects/<key> served-object bytes; the key's slashes become real
12
+ // directories, so the tree mirrors the URL space the
13
+ // dev server serves at /{prefix}/*
14
+ // object-meta/<key>.json {"contentType": …} for that object
15
+ // offload/<key> content-addressed offload payload (JSON text)
16
+ //
17
+ // Content type needs a sidecar because a file holds bytes and nothing else.
18
+ // Keeping it in a PARALLEL tree rather than beside the object leaves `objects/`
19
+ // an exact mirror of what is served — no `.json` companions to skip when
20
+ // browsing, copying, or serving it directly.
21
+ //
22
+ // Every call is synchronous, matching the in-memory arm's signature: this is a
23
+ // dev-only store on a local disk, and going async here would push promises
24
+ // through the HTTP handlers and the offload hook for no benefit.
25
+
26
+ let objectsDir = (~root) => NodePath.join([root, "objects"])
27
+ let metaDir = (~root) => NodePath.join([root, "object-meta"])
28
+ let offloadDir = (~root) => NodePath.join([root, "offload"])
29
+
30
+ // A key becomes a path here, so it must not climb out of the root. Keys arrive
31
+ // from URL paths as well as from content addressing, and `..` segments that were
32
+ // inert as dict keys are not inert as path segments.
33
+ let isSafeKey = (key: string): bool =>
34
+ key != "" &&
35
+ !(key->String.startsWith("/")) &&
36
+ key->String.split("/")->Array.every(seg => seg != "" && seg != "." && seg != "..")
37
+
38
+ let writeUnder = (path: string, write: string => unit): unit => {
39
+ NodeFs.mkdirSync(NodePath.dirname(path), {recursive: true})
40
+ write(path)
41
+ }
42
+
43
+ // Drop directories the deleted object left behind, walking up until one still
44
+ // holds something or `stopAt` is reached. S3 has no directories, so the empty
45
+ // `<uuid>/` a released upload leaves is an artifact of the filesystem mapping,
46
+ // not something the AWS path would have.
47
+ let rec pruneEmptyDirs = (~stopAt: string, ~from: string): unit =>
48
+ if from != stopAt && from->String.length > stopAt->String.length {
49
+ let entries = try NodeFs.readdirSync(from, {withFileTypes: true}) catch {
50
+ | _ => []
51
+ }
52
+ if entries->Array.length == 0 {
53
+ try NodeFs.rmSync(from, {recursive: true, force: true}) catch {
54
+ | _ => ()
55
+ }
56
+ pruneEmptyDirs(~stopAt, ~from=NodePath.dirname(from))
57
+ }
58
+ }
59
+
60
+ // ── Served objects ──────────────────────────────────────────────────────────
61
+
62
+ let objectPath = (~root, ~key) => NodePath.join([objectsDir(~root), key])
63
+ let metaPath = (~root, ~key) => NodePath.join([metaDir(~root), key ++ ".json"])
64
+
65
+ let put = (~root: string, ~key: string, ~bytes: NodeBuffer.t, ~contentType: string): unit =>
66
+ if isSafeKey(key) {
67
+ writeUnder(objectPath(~root, ~key), path => NodeFs.writeFileSyncBuffer(path, bytes))
68
+ writeUnder(metaPath(~root, ~key), path =>
69
+ NodeFs.writeFileSync(
70
+ path,
71
+ Dict.fromArray([("contentType", JSON.Encode.string(contentType))])
72
+ ->JSON.Encode.object
73
+ ->JSON.stringify,
74
+ )
75
+ )
76
+ }
77
+
78
+ let defaultContentType = "application/octet-stream"
79
+
80
+ let readContentType = (~root, ~key): string =>
81
+ switch try Some(NodeFs.readFileSync(metaPath(~root, ~key))) catch {
82
+ | _ => None
83
+ } {
84
+ | Some(raw) =>
85
+ raw
86
+ ->JSON.parseOrThrow
87
+ ->JSON.Decode.object
88
+ ->Option.flatMap(d => d->Dict.get("contentType"))
89
+ ->Option.flatMap(JSON.Decode.string)
90
+ ->Option.getOr(defaultContentType)
91
+ | None => defaultContentType
92
+ }
93
+
94
+ // The try covers the case where the key names a directory rather than a file
95
+ // (`/uploads/<uuid>` when only `/uploads/<uuid>/logo.svg` was stored): reading it
96
+ // throws EISDIR, and the answer the caller wants is the same 404 a missing key
97
+ // gets.
98
+ let get = (~root: string, ~key: string): option<(NodeBuffer.t, string)> =>
99
+ if !isSafeKey(key) {
100
+ None
101
+ } else {
102
+ switch try Some(NodeFs.readFileSyncBuffer(objectPath(~root, ~key))) catch {
103
+ | _ => None
104
+ } {
105
+ | Some(bytes) => Some((bytes, readContentType(~root, ~key)))
106
+ | None => None
107
+ }
108
+ }
109
+
110
+ let removeFile = (path: string): unit =>
111
+ try NodeFs.unlinkSync(path) catch {
112
+ | _ => ()
113
+ }
114
+
115
+ let delete = (~root: string, ~key: string): unit =>
116
+ if isSafeKey(key) {
117
+ let object = objectPath(~root, ~key)
118
+ let meta = metaPath(~root, ~key)
119
+ removeFile(object)
120
+ removeFile(meta)
121
+ pruneEmptyDirs(~stopAt=objectsDir(~root), ~from=NodePath.dirname(object))
122
+ pruneEmptyDirs(~stopAt=metaDir(~root), ~from=NodePath.dirname(meta))
123
+ }
124
+
125
+ // ── Offload objects ─────────────────────────────────────────────────────────
126
+
127
+ let offloadPath = (~root, ~key) => NodePath.join([offloadDir(~root), key])
128
+
129
+ let putOffload = (~root: string, ~key: string, ~bytes: string): unit =>
130
+ if isSafeKey(key) {
131
+ writeUnder(offloadPath(~root, ~key), path => NodeFs.writeFileSync(path, bytes))
132
+ }
133
+
134
+ let getOffload = (~root: string, ~key: string): option<string> =>
135
+ if !isSafeKey(key) {
136
+ None
137
+ } else {
138
+ try Some(NodeFs.readFileSync(offloadPath(~root, ~key))) catch {
139
+ | _ => None
140
+ }
141
+ }
142
+
143
+ // ── Prefix-scoped inspection and removal ────────────────────────────────────
144
+ //
145
+ // What a scoped reset works in: an object belongs to the store whose key prefix
146
+ // it sits under, so a wipe names a prefix and never a whole tree — the same
147
+ // prefix-scoped delete the deployed reset issues against S3.
148
+
149
+ let rec walkKeys = (~dir: string, ~prefix: string, ~into: array<string>): unit =>
150
+ switch try Some(NodeFs.readdirSync(dir, {withFileTypes: true})) catch {
151
+ | _ => None
152
+ } {
153
+ | None => ()
154
+ | Some(entries) =>
155
+ entries->Array.forEach(entry => {
156
+ let name = NodeFs.direntName(entry)
157
+ let key = prefix == "" ? name : `${prefix}/${name}`
158
+ if NodeFs.isDirectory(entry) {
159
+ walkKeys(~dir=NodePath.join([dir, name]), ~prefix=key, ~into)
160
+ } else {
161
+ into->Array.push(key)
162
+ }
163
+ })
164
+ }
165
+
166
+ /** Every stored object key under `prefix` (the prefix itself included when it
167
+ names an object). An absent prefix is simply empty, not an error. */
168
+ let keysUnder = (~root: string, ~prefix: string): array<string> => {
169
+ let into = []
170
+ walkKeys(~dir=NodePath.join([objectsDir(~root), prefix]), ~prefix, ~into)
171
+ into
172
+ }
173
+
174
+ /** Top-level key prefixes present in the store, one segment deep — what a plan
175
+ lists when it has to report objects no declared store claims. */
176
+ let topLevelPrefixes = (~root: string): array<string> =>
177
+ switch try Some(NodeFs.readdirSync(objectsDir(~root), {withFileTypes: true})) catch {
178
+ | _ => None
179
+ } {
180
+ | None => []
181
+ | Some(entries) =>
182
+ entries->Array.filter(NodeFs.isDirectory)->Array.map(NodeFs.direntName)
183
+ }
184
+
185
+ /** Deletes every object under `prefix`, returning how many were removed. Goes
186
+ through `delete` so the content-type sidecar and the emptied directories go
187
+ with each object. */
188
+ let deleteUnder = (~root: string, ~prefix: string): int => {
189
+ let keys = keysUnder(~root, ~prefix)
190
+ keys->Array.forEach(key => delete(~root, ~key))
191
+ keys->Array.length
192
+ }
193
+
194
+ let offloadKeys = (~root: string): array<string> => {
195
+ let into = []
196
+ walkKeys(~dir=offloadDir(~root), ~prefix="", ~into)
197
+ into
198
+ }
199
+
200
+ let deleteOffloadAll = (~root: string): int => {
201
+ let keys = offloadKeys(~root)
202
+ try NodeFs.rmSync(offloadDir(~root), {recursive: true, force: true}) catch {
203
+ | _ => ()
204
+ }
205
+ keys->Array.length
206
+ }
207
+
208
+ // Removes the three trees, leaving the rest of the root (the database file, the
209
+ // dev `users.yaml`) untouched.
210
+ let reset = (~root: string): unit =>
211
+ [objectsDir(~root), metaDir(~root), offloadDir(~root)]->Array.forEach(dir =>
212
+ try NodeFs.rmSync(dir, {recursive: true, force: true}) catch {
213
+ | _ => ()
214
+ }
215
+ )
@@ -0,0 +1,286 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Nodefs from "node:fs";
4
+ import * as Nodepath from "node:path";
5
+ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
6
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
+
8
+ function objectsDir(root) {
9
+ return Nodepath.join(root, "objects");
10
+ }
11
+
12
+ function metaDir(root) {
13
+ return Nodepath.join(root, "object-meta");
14
+ }
15
+
16
+ function offloadDir(root) {
17
+ return Nodepath.join(root, "offload");
18
+ }
19
+
20
+ function isSafeKey(key) {
21
+ if (key !== "" && !key.startsWith("/")) {
22
+ return key.split("/").every(seg => {
23
+ if (seg !== "" && seg !== ".") {
24
+ return seg !== "..";
25
+ } else {
26
+ return false;
27
+ }
28
+ });
29
+ } else {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ function writeUnder(path, write) {
35
+ Nodefs.mkdirSync(Nodepath.dirname(path), {
36
+ recursive: true
37
+ });
38
+ write(path);
39
+ }
40
+
41
+ function pruneEmptyDirs(stopAt, _from) {
42
+ while (true) {
43
+ let from = _from;
44
+ if (!(from !== stopAt && from.length > stopAt.length)) {
45
+ return;
46
+ }
47
+ let entries;
48
+ try {
49
+ entries = Nodefs.readdirSync(from, {
50
+ withFileTypes: true
51
+ });
52
+ } catch (exn) {
53
+ entries = [];
54
+ }
55
+ if (entries.length !== 0) {
56
+ return;
57
+ }
58
+ try {
59
+ Nodefs.rmSync(from, {
60
+ recursive: true,
61
+ force: true
62
+ });
63
+ } catch (exn$1) {
64
+
65
+ }
66
+ _from = Nodepath.dirname(from);
67
+ continue;
68
+ };
69
+ }
70
+
71
+ function objectPath(root, key) {
72
+ return Nodepath.join(Nodepath.join(root, "objects"), key);
73
+ }
74
+
75
+ function metaPath(root, key) {
76
+ return Nodepath.join(Nodepath.join(root, "object-meta"), key + ".json");
77
+ }
78
+
79
+ function put(root, key, bytes, contentType) {
80
+ if (isSafeKey(key)) {
81
+ writeUnder(objectPath(root, key), path => {
82
+ Nodefs.writeFileSync(path, bytes);
83
+ });
84
+ return writeUnder(metaPath(root, key), path => {
85
+ Nodefs.writeFileSync(path, JSON.stringify(Object.fromEntries([[
86
+ "contentType",
87
+ contentType
88
+ ]])), "utf8");
89
+ });
90
+ }
91
+ }
92
+
93
+ let defaultContentType = "application/octet-stream";
94
+
95
+ function readContentType(root, key) {
96
+ let raw;
97
+ try {
98
+ raw = Nodefs.readFileSync(metaPath(root, key), "utf8");
99
+ } catch (exn) {
100
+ raw = undefined;
101
+ }
102
+ if (raw !== undefined) {
103
+ return Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(JSON.parse(raw)), d => d["contentType"]), Stdlib_JSON.Decode.string), defaultContentType);
104
+ } else {
105
+ return defaultContentType;
106
+ }
107
+ }
108
+
109
+ function get(root, key) {
110
+ if (!isSafeKey(key)) {
111
+ return;
112
+ }
113
+ let bytes;
114
+ try {
115
+ bytes = Nodefs.readFileSync(objectPath(root, key));
116
+ } catch (exn) {
117
+ bytes = undefined;
118
+ }
119
+ if (bytes !== undefined) {
120
+ return [
121
+ bytes,
122
+ readContentType(root, key)
123
+ ];
124
+ }
125
+ }
126
+
127
+ function removeFile(path) {
128
+ try {
129
+ Nodefs.unlinkSync(path);
130
+ return;
131
+ } catch (exn) {
132
+ return;
133
+ }
134
+ }
135
+
136
+ function $$delete(root, key) {
137
+ if (!isSafeKey(key)) {
138
+ return;
139
+ }
140
+ let object = objectPath(root, key);
141
+ let meta = metaPath(root, key);
142
+ removeFile(object);
143
+ removeFile(meta);
144
+ pruneEmptyDirs(Nodepath.join(root, "objects"), Nodepath.dirname(object));
145
+ pruneEmptyDirs(Nodepath.join(root, "object-meta"), Nodepath.dirname(meta));
146
+ }
147
+
148
+ function offloadPath(root, key) {
149
+ return Nodepath.join(Nodepath.join(root, "offload"), key);
150
+ }
151
+
152
+ function putOffload(root, key, bytes) {
153
+ if (isSafeKey(key)) {
154
+ return writeUnder(offloadPath(root, key), path => {
155
+ Nodefs.writeFileSync(path, bytes, "utf8");
156
+ });
157
+ }
158
+ }
159
+
160
+ function getOffload(root, key) {
161
+ if (!isSafeKey(key)) {
162
+ return;
163
+ }
164
+ try {
165
+ return Nodefs.readFileSync(offloadPath(root, key), "utf8");
166
+ } catch (exn) {
167
+ return;
168
+ }
169
+ }
170
+
171
+ function walkKeys(dir, prefix, into) {
172
+ let entries;
173
+ try {
174
+ entries = Nodefs.readdirSync(dir, {
175
+ withFileTypes: true
176
+ });
177
+ } catch (exn) {
178
+ entries = undefined;
179
+ }
180
+ if (entries !== undefined) {
181
+ entries.forEach(entry => {
182
+ let name = entry.name;
183
+ let key = prefix === "" ? name : prefix + `/` + name;
184
+ if (entry.isDirectory()) {
185
+ return walkKeys(Nodepath.join(dir, name), key, into);
186
+ } else {
187
+ into.push(key);
188
+ return;
189
+ }
190
+ });
191
+ return;
192
+ }
193
+ }
194
+
195
+ function keysUnder(root, prefix) {
196
+ let into = [];
197
+ walkKeys(Nodepath.join(Nodepath.join(root, "objects"), prefix), prefix, into);
198
+ return into;
199
+ }
200
+
201
+ function topLevelPrefixes(root) {
202
+ let entries;
203
+ try {
204
+ entries = Nodefs.readdirSync(Nodepath.join(root, "objects"), {
205
+ withFileTypes: true
206
+ });
207
+ } catch (exn) {
208
+ entries = undefined;
209
+ }
210
+ if (entries !== undefined) {
211
+ return entries.filter(prim => prim.isDirectory()).map(prim => prim.name);
212
+ } else {
213
+ return [];
214
+ }
215
+ }
216
+
217
+ function deleteUnder(root, prefix) {
218
+ let keys = keysUnder(root, prefix);
219
+ keys.forEach(key => $$delete(root, key));
220
+ return keys.length;
221
+ }
222
+
223
+ function offloadKeys(root) {
224
+ let into = [];
225
+ walkKeys(Nodepath.join(root, "offload"), "", into);
226
+ return into;
227
+ }
228
+
229
+ function deleteOffloadAll(root) {
230
+ let keys = offloadKeys(root);
231
+ try {
232
+ Nodefs.rmSync(Nodepath.join(root, "offload"), {
233
+ recursive: true,
234
+ force: true
235
+ });
236
+ } catch (exn) {
237
+
238
+ }
239
+ return keys.length;
240
+ }
241
+
242
+ function reset(root) {
243
+ [
244
+ Nodepath.join(root, "objects"),
245
+ Nodepath.join(root, "object-meta"),
246
+ Nodepath.join(root, "offload")
247
+ ].forEach(dir => {
248
+ try {
249
+ Nodefs.rmSync(dir, {
250
+ recursive: true,
251
+ force: true
252
+ });
253
+ return;
254
+ } catch (exn) {
255
+ return;
256
+ }
257
+ });
258
+ }
259
+
260
+ export {
261
+ objectsDir,
262
+ metaDir,
263
+ offloadDir,
264
+ isSafeKey,
265
+ writeUnder,
266
+ pruneEmptyDirs,
267
+ objectPath,
268
+ metaPath,
269
+ put,
270
+ defaultContentType,
271
+ readContentType,
272
+ get,
273
+ removeFile,
274
+ $$delete,
275
+ offloadPath,
276
+ putOffload,
277
+ getOffload,
278
+ walkKeys,
279
+ keysUnder,
280
+ topLevelPrefixes,
281
+ deleteUnder,
282
+ offloadKeys,
283
+ deleteOffloadAll,
284
+ reset,
285
+ }
286
+ /* node:fs Not a pure module */
@@ -0,0 +1,33 @@
1
+ // In-process arm of the local object store: two dicts, wiped on restart.
2
+ //
3
+ // Selected when nothing durable is on local disk (Memory backend, a `:memory:`
4
+ // SQLite, Postgres) — see BackendState.getObjectStoreRoot. Ephemeral by design:
5
+ // with the events themselves gone on restart, the bytes they reference have
6
+ // nothing to outlive.
7
+ //
8
+ // `get` returns a `(bytes, contentType)` pair rather than a record so that this
9
+ // arm and ObjectStoreStorage_FileSystem share one signature without either
10
+ // owning a type the other must import; LocalObjectStore names the pair.
11
+
12
+ type stored = (NodeBuffer.t, string)
13
+
14
+ let objects: dict<stored> = Dict.make()
15
+
16
+ let put = (~key: string, ~bytes: NodeBuffer.t, ~contentType: string): unit =>
17
+ objects->Dict.set(key, (bytes, contentType))
18
+
19
+ let get = (~key: string): option<stored> => objects->Dict.get(key)
20
+
21
+ let delete = (~key: string): unit => objects->Dict.delete(key)
22
+
23
+ // Offload payloads: a separate keyspace, for the reason LocalObjectStore gives.
24
+ let offloadObjects: dict<string> = Dict.make()
25
+
26
+ let putOffload = (~key: string, ~bytes: string): unit => offloadObjects->Dict.set(key, bytes)
27
+
28
+ let getOffload = (~key: string): option<string> => offloadObjects->Dict.get(key)
29
+
30
+ let reset = (): unit => {
31
+ objects->Dict.keysToArray->Array.forEach(k => objects->Dict.delete(k))
32
+ offloadObjects->Dict.keysToArray->Array.forEach(k => offloadObjects->Dict.delete(k))
33
+ }
@@ -0,0 +1,47 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
4
+
5
+ let objects = {};
6
+
7
+ function put(key, bytes, contentType) {
8
+ objects[key] = [
9
+ bytes,
10
+ contentType
11
+ ];
12
+ }
13
+
14
+ function get(key) {
15
+ return objects[key];
16
+ }
17
+
18
+ function $$delete(key) {
19
+ Stdlib_Dict.$$delete(objects, key);
20
+ }
21
+
22
+ let offloadObjects = {};
23
+
24
+ function putOffload(key, bytes) {
25
+ offloadObjects[key] = bytes;
26
+ }
27
+
28
+ function getOffload(key) {
29
+ return offloadObjects[key];
30
+ }
31
+
32
+ function reset() {
33
+ Object.keys(objects).forEach(k => Stdlib_Dict.$$delete(objects, k));
34
+ Object.keys(offloadObjects).forEach(k => Stdlib_Dict.$$delete(offloadObjects, k));
35
+ }
36
+
37
+ export {
38
+ objects,
39
+ put,
40
+ get,
41
+ $$delete,
42
+ offloadObjects,
43
+ putOffload,
44
+ getOffload,
45
+ reset,
46
+ }
47
+ /* No side effect */