@minnowdb/core 0.7.0 → 0.7.1

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 (95) hide show
  1. package/dist/block-format/block.js +254 -325
  2. package/dist/block-format/checksum.js +30 -52
  3. package/dist/block-format/codecs.js +112 -125
  4. package/dist/block-format/column.js +187 -205
  5. package/dist/block-format/index.js +5 -1
  6. package/dist/block-format/physical.js +371 -387
  7. package/dist/block-format/types.js +4 -1
  8. package/dist/block-format/unicode.js +30 -39
  9. package/dist/date-value.js +67 -78
  10. package/dist/engine/artifact-cache.js +106 -116
  11. package/dist/engine/batch.js +36 -44
  12. package/dist/engine/buffered-writer.js +156 -158
  13. package/dist/engine/byte-estimates.js +20 -22
  14. package/dist/engine/cache-limits.js +24 -25
  15. package/dist/engine/cancellation.js +5 -3
  16. package/dist/engine/catalog.js +64 -73
  17. package/dist/engine/client.js +715 -780
  18. package/dist/engine/database.js +15941 -19313
  19. package/dist/engine/defaults.js +104 -111
  20. package/dist/engine/errors.js +113 -128
  21. package/dist/engine/fts.js +233 -280
  22. package/dist/engine/group-index.js +287 -321
  23. package/dist/engine/index.js +23 -12
  24. package/dist/engine/join-index.js +177 -186
  25. package/dist/engine/keyed-live.js +168 -173
  26. package/dist/engine/live-api.js +0 -1
  27. package/dist/engine/live-equal.js +32 -37
  28. package/dist/engine/live.js +603 -647
  29. package/dist/engine/memory.js +109 -111
  30. package/dist/engine/optimizer.d.ts +19 -1
  31. package/dist/engine/optimizer.js +2633 -2934
  32. package/dist/engine/point-read.js +122 -182
  33. package/dist/engine/query-api.js +12 -3
  34. package/dist/engine/query-cache.js +61 -69
  35. package/dist/engine/query.d.ts +6 -0
  36. package/dist/engine/query.js +8503 -9980
  37. package/dist/engine/result-wire.js +221 -252
  38. package/dist/engine/schema-wire.js +97 -112
  39. package/dist/engine/schema.js +1099 -1254
  40. package/dist/engine/sort-keys.js +315 -389
  41. package/dist/engine/sql-domains.js +646 -769
  42. package/dist/engine/sql-driver.js +0 -1
  43. package/dist/engine/sql-functions.js +1031 -1125
  44. package/dist/engine/sql-json.js +199 -218
  45. package/dist/engine/sql-semantics.js +572 -639
  46. package/dist/engine/typed-live.js +271 -293
  47. package/dist/engine/vector.js +4484 -5145
  48. package/dist/engine/worker-host.d.ts +8 -63
  49. package/dist/engine/worker-host.js +28 -1130
  50. package/dist/engine/worker-indexeddb.d.ts +1 -0
  51. package/dist/engine/worker-indexeddb.js +3 -0
  52. package/dist/engine/worker-memory.d.ts +1 -0
  53. package/dist/engine/worker-memory.js +3 -0
  54. package/dist/engine/worker-opfs.d.ts +1 -0
  55. package/dist/engine/worker-opfs.js +3 -0
  56. package/dist/engine/worker-server.d.ts +93 -0
  57. package/dist/engine/worker-server.js +999 -0
  58. package/dist/engine/worker-store-indexeddb.d.ts +2 -0
  59. package/dist/engine/worker-store-indexeddb.js +11 -0
  60. package/dist/engine/worker-store-memory.d.ts +2 -0
  61. package/dist/engine/worker-store-memory.js +6 -0
  62. package/dist/engine/worker-store-opfs.d.ts +2 -0
  63. package/dist/engine/worker-store-opfs.js +9 -0
  64. package/dist/engine/worker.js +0 -12
  65. package/dist/engine/write-block-planner.js +93 -100
  66. package/dist/plan/index.js +15 -16
  67. package/dist/plan/model.d.ts +6 -0
  68. package/dist/plan/model.js +20 -27
  69. package/dist/storage/index.js +0 -12
  70. package/dist/storage/indexeddb.js +11776 -13171
  71. package/dist/storage/memory.js +1198 -1241
  72. package/dist/storage/opfs/files.js +238 -295
  73. package/dist/storage/opfs/index.js +9 -3
  74. package/dist/storage/opfs/leader.js +4386 -4856
  75. package/dist/storage/opfs/rpc.js +223 -259
  76. package/dist/storage/opfs/snapshot-ledger.js +219 -260
  77. package/dist/storage/opfs/store.js +884 -1003
  78. package/dist/storage/persistence.js +59 -74
  79. package/dist/storage/snapshot-stream.js +788 -826
  80. package/dist/storage/snapshot.js +36 -37
  81. package/dist/storage/toolkit/extents.js +444 -520
  82. package/dist/storage/toolkit/index.js +28 -29
  83. package/dist/storage/toolkit/record-core.js +5792 -6377
  84. package/dist/storage/toolkit/sync-file.js +34 -42
  85. package/dist/storage/toolkit/wal.js +92 -127
  86. package/dist/storage/toolkit/wire.js +232 -256
  87. package/dist/storage/types.js +2948 -3250
  88. package/dist/testing/block-store-conformance.js +1556 -1629
  89. package/dist/testing/index.js +310 -291
  90. package/dist/testing/opfs-shim.js +280 -312
  91. package/dist/testing/simulator.js +496 -534
  92. package/dist/testing/sqllogictest.js +425 -365
  93. package/dist/transactions/index.js +1464 -1712
  94. package/dist/worker-protocol/index.js +70 -69
  95. package/package.json +20 -2
@@ -1,330 +1,273 @@
1
- /**
2
- * Path encoding and file IO for the OPFS store.
3
- *
4
- * Names that come from callers (database names, temp owner and run ids) become one
5
- * conservatively percent-encoded entry name each. Encoding is bijective — `[A-Za-z0-9_-]` and
6
- * non-leading `.` pass through, everything else becomes `%XX` per UTF-8 byte — so names
7
- * round-trip exactly and none can collide with or escape into another's path.
8
- */
9
1
  import { assertWellFormedString } from "../../block-format/unicode.js";
10
2
  import { writeFully } from "../toolkit/sync-file.js";
11
3
  const SAFE_SEGMENT_CHARS = /^[A-Za-z0-9._-]$/;
12
4
  const UPPERCASE_HEX_BYTE = /^[0-9A-F]{2}$/;
13
5
  const segmentTextEncoder = new TextEncoder();
14
6
  const segmentTextDecoder = new TextDecoder("utf-8", { fatal: true });
15
- /** Conservative cross-browser/filesystem ceiling for one already-encoded OPFS entry name. */
16
- export const MAX_OPFS_ENCODED_SEGMENT_CHARACTERS = 240;
17
- /** Directory handles have no close operation, so bound retained wrappers under path churn. */
18
- export const MAX_OPFS_DIRECTORY_HANDLE_CACHE_ENTRIES = 128;
19
- /** Internal layouts are shallow; reject adversarial/custom paths before recursive resolution. */
20
- export const MAX_OPFS_PATH_SEGMENTS = 16;
7
+ const MAX_OPFS_ENCODED_SEGMENT_CHARACTERS = 240;
8
+ const MAX_OPFS_DIRECTORY_HANDLE_CACHE_ENTRIES = 128;
9
+ const MAX_OPFS_PATH_SEGMENTS = 16;
21
10
  function assertEncodedSegmentLength(length) {
22
- if (length > MAX_OPFS_ENCODED_SEGMENT_CHARACTERS) {
23
- throw new RangeError(`Encoded OPFS path segment exceeds ${String(MAX_OPFS_ENCODED_SEGMENT_CHARACTERS)} characters`);
24
- }
25
- }
26
- export function encodeSegment(segment) {
27
- assertWellFormedString(segment, "OPFS path segment");
28
- if (segment.length === 0)
29
- return "%";
30
- let encoded = "";
31
- for (const char of segment) {
32
- if (SAFE_SEGMENT_CHARS.test(char) && !(encoded.length === 0 && char === ".")) {
33
- encoded += char;
34
- assertEncodedSegmentLength(encoded.length);
35
- continue;
36
- }
37
- for (const byte of segmentTextEncoder.encode(char)) {
38
- encoded += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
39
- assertEncodedSegmentLength(encoded.length);
40
- }
41
- }
42
- return encoded;
11
+ if (length > MAX_OPFS_ENCODED_SEGMENT_CHARACTERS) {
12
+ throw new RangeError(`Encoded OPFS path segment exceeds ${String(MAX_OPFS_ENCODED_SEGMENT_CHARACTERS)} characters`);
13
+ }
43
14
  }
44
- export function decodeSegment(encoded) {
45
- assertEncodedSegmentLength(encoded.length);
46
- if (encoded === "%")
47
- return "";
48
- if (encoded.length === 0)
49
- throw new Error("Invalid OPFS path segment encoding");
50
- const bytes = [];
51
- for (let index = 0; index < encoded.length;) {
52
- const char = encoded[index] ?? "";
53
- if (char === "%") {
54
- const hex = encoded.slice(index + 1, index + 3);
55
- if (!UPPERCASE_HEX_BYTE.test(hex)) {
56
- throw new Error("Invalid OPFS path segment encoding");
57
- }
58
- bytes.push(Number.parseInt(hex, 16));
59
- index += 3;
60
- continue;
61
- }
62
- if (!SAFE_SEGMENT_CHARS.test(char)) {
63
- throw new Error("Invalid OPFS path segment encoding");
64
- }
65
- bytes.push(char.charCodeAt(0));
66
- index += 1;
15
+ function encodeSegment(segment) {
16
+ assertWellFormedString(segment, "OPFS path segment");
17
+ if (segment.length === 0)
18
+ return "%";
19
+ let encoded = "";
20
+ for (const char of segment) {
21
+ if (SAFE_SEGMENT_CHARS.test(char) && !(encoded.length === 0 && char === ".")) {
22
+ encoded += char;
23
+ assertEncodedSegmentLength(encoded.length);
24
+ continue;
67
25
  }
68
- let decoded;
69
- try {
70
- decoded = segmentTextDecoder.decode(new Uint8Array(bytes));
26
+ for (const byte of segmentTextEncoder.encode(char)) {
27
+ encoded += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
28
+ assertEncodedSegmentLength(encoded.length);
71
29
  }
72
- catch {
30
+ }
31
+ return encoded;
32
+ }
33
+ function decodeSegment(encoded) {
34
+ assertEncodedSegmentLength(encoded.length);
35
+ if (encoded === "%")
36
+ return "";
37
+ if (encoded.length === 0)
38
+ throw new Error("Invalid OPFS path segment encoding");
39
+ const bytes = [];
40
+ for (let index = 0; index < encoded.length; ) {
41
+ const char = encoded[index] ?? "";
42
+ if (char === "%") {
43
+ const hex = encoded.slice(index + 1, index + 3);
44
+ if (!UPPERCASE_HEX_BYTE.test(hex)) {
73
45
  throw new Error("Invalid OPFS path segment encoding");
46
+ }
47
+ bytes.push(Number.parseInt(hex, 16));
48
+ index += 3;
49
+ continue;
74
50
  }
75
- if (encodeSegment(decoded) !== encoded) {
76
- throw new Error("Non-canonical OPFS path segment encoding");
51
+ if (!SAFE_SEGMENT_CHARS.test(char)) {
52
+ throw new Error("Invalid OPFS path segment encoding");
77
53
  }
78
- return decoded;
54
+ bytes.push(char.charCodeAt(0));
55
+ index += 1;
56
+ }
57
+ let decoded;
58
+ try {
59
+ decoded = segmentTextDecoder.decode(new Uint8Array(bytes));
60
+ } catch {
61
+ throw new Error("Invalid OPFS path segment encoding");
62
+ }
63
+ if (encodeSegment(decoded) !== encoded) {
64
+ throw new Error("Non-canonical OPFS path segment encoding");
65
+ }
66
+ return decoded;
79
67
  }
80
68
  function validatePath(path) {
81
- if (path.length > MAX_OPFS_PATH_SEGMENTS) {
82
- throw new RangeError(`OPFS path exceeds ${String(MAX_OPFS_PATH_SEGMENTS)} segments`);
83
- }
84
- for (const segment of path) {
85
- if (segment.length === 0 || segment.length > MAX_OPFS_ENCODED_SEGMENT_CHARACTERS) {
86
- throw new RangeError("OPFS path contains an invalid segment");
87
- }
69
+ if (path.length > MAX_OPFS_PATH_SEGMENTS) {
70
+ throw new RangeError(`OPFS path exceeds ${String(MAX_OPFS_PATH_SEGMENTS)} segments`);
71
+ }
72
+ for (const segment of path) {
73
+ if (segment.length === 0 || segment.length > MAX_OPFS_ENCODED_SEGMENT_CHARACTERS) {
74
+ throw new RangeError("OPFS path contains an invalid segment");
88
75
  }
76
+ }
89
77
  }
90
- /**
91
- * A directory-handle cache plus the file operations the store needs. Handles are cached per
92
- * path a bulk write of one segment's blocks resolves its directory chain once — and the
93
- * cache is invalidated on deletes underneath it.
94
- */
95
- export class OpfsTree {
96
- #root;
97
- #directories = new Map();
98
- constructor(root) {
99
- this.#root = root;
78
+ class OpfsTree {
79
+ #root;
80
+ #directories = /* @__PURE__ */ new Map();
81
+ constructor(root) {
82
+ this.#root = root;
83
+ }
84
+ get directoryCacheSizeForTests() {
85
+ return this.#directories.size;
86
+ }
87
+ async getDirectory(path, create) {
88
+ validatePath(path);
89
+ if (path.length === 0)
90
+ return this.#root;
91
+ const key = path.join("/");
92
+ const cached = this.#directories.get(key);
93
+ if (cached !== void 0) {
94
+ this.#directories.delete(key);
95
+ this.#directories.set(key, cached);
96
+ return cached;
100
97
  }
101
- /** @internal Hardening-test visibility for the cache's fixed-memory invariant. */
102
- get directoryCacheSizeForTests() {
103
- return this.#directories.size;
98
+ const parent = await this.getDirectory(path.slice(0, -1), create);
99
+ const directory = await retryTransient(() => parent.getDirectoryHandle(path[path.length - 1] ?? "", { create }));
100
+ this.#directories.set(key, directory);
101
+ if (this.#directories.size > MAX_OPFS_DIRECTORY_HANDLE_CACHE_ENTRIES) {
102
+ const oldest = this.#directories.keys().next().value;
103
+ if (oldest !== void 0)
104
+ this.#directories.delete(oldest);
104
105
  }
105
- async getDirectory(path, create) {
106
- validatePath(path);
107
- if (path.length === 0)
108
- return this.#root;
109
- const key = path.join("/");
110
- const cached = this.#directories.get(key);
111
- if (cached !== undefined) {
112
- // Map insertion order is the LRU order. Refresh without retaining a second reference.
113
- this.#directories.delete(key);
114
- this.#directories.set(key, cached);
115
- return cached;
116
- }
117
- const parent = await this.getDirectory(path.slice(0, -1), create);
118
- const directory = await retryTransient(() => parent.getDirectoryHandle(path[path.length - 1] ?? "", { create }));
119
- this.#directories.set(key, directory);
120
- if (this.#directories.size > MAX_OPFS_DIRECTORY_HANDLE_CACHE_ENTRIES) {
121
- const oldest = this.#directories.keys().next().value;
122
- if (oldest !== undefined)
123
- this.#directories.delete(oldest);
124
- }
125
- return directory;
106
+ return directory;
107
+ }
108
+ invalidate(pathPrefix) {
109
+ const prefix = pathPrefix.join("/");
110
+ for (const key of [...this.#directories.keys()]) {
111
+ if (key === prefix || key.startsWith(`${prefix}/`))
112
+ this.#directories.delete(key);
126
113
  }
127
- /** Drops cached handles at and under a path — call after deleting a directory. */
128
- invalidate(pathPrefix) {
129
- const prefix = pathPrefix.join("/");
130
- for (const key of [...this.#directories.keys()]) {
131
- if (key === prefix || key.startsWith(`${prefix}/`))
132
- this.#directories.delete(key);
133
- }
114
+ }
115
+ async readFile(path, options = {}) {
116
+ validatePath(path);
117
+ if (options.maxBytes !== void 0 && (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0)) {
118
+ throw new RangeError("OPFS read byte limit must be a non-negative safe integer");
134
119
  }
135
- /** `undefined` when the file (or any parent) does not exist. */
136
- async readFile(path, options = {}) {
137
- validatePath(path);
138
- if (options.maxBytes !== undefined &&
139
- (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0)) {
140
- throw new RangeError("OPFS read byte limit must be a non-negative safe integer");
141
- }
142
- try {
143
- return await retryTransient(async () => {
144
- const directory = await this.getDirectory(path.slice(0, -1), false);
145
- const handle = await directory.getFileHandle(path[path.length - 1] ?? "");
146
- const file = await handle.getFile();
147
- if (options.maxBytes !== undefined && file.size > options.maxBytes) {
148
- throw new RangeError(`OPFS file exceeds the ${String(options.maxBytes)}-byte read limit`);
149
- }
150
- return new Uint8Array(await file.arrayBuffer());
151
- });
152
- }
153
- catch (error) {
154
- if (isDomError(error, "NotFoundError") || isDomError(error, "TypeMismatchError")) {
155
- return undefined;
156
- }
157
- // Browsers disagree on how a read racing a writer or a delete surfaces: Firefox refuses
158
- // at getFile() with NoModificationAllowedError, Chromium and WebKit hand out the File
159
- // and then fail its arrayBuffer() with NotReadableError — and WebKit sometimes refuses
160
- // with InvalidStateError instead. All mean the same thing for a checksummed control
161
- // file: not committed at this instant.
162
- if (options.lockedMeansAbsent === true &&
163
- (isDomError(error, "NoModificationAllowedError") ||
164
- isDomError(error, "NotReadableError") ||
165
- isDomError(error, "InvalidStateError"))) {
166
- return undefined;
167
- }
168
- throw error;
120
+ try {
121
+ return await retryTransient(async () => {
122
+ const directory = await this.getDirectory(path.slice(0, -1), false);
123
+ const handle = await directory.getFileHandle(path[path.length - 1] ?? "");
124
+ const file = await handle.getFile();
125
+ if (options.maxBytes !== void 0 && file.size > options.maxBytes) {
126
+ throw new RangeError(`OPFS file exceeds the ${String(options.maxBytes)}-byte read limit`);
169
127
  }
128
+ return new Uint8Array(await file.arrayBuffer());
129
+ });
130
+ } catch (error) {
131
+ if (isDomError(error, "NotFoundError") || isDomError(error, "TypeMismatchError")) {
132
+ return void 0;
133
+ }
134
+ if (options.lockedMeansAbsent === true && (isDomError(error, "NoModificationAllowedError") || isDomError(error, "NotReadableError") || isDomError(error, "InvalidStateError"))) {
135
+ return void 0;
136
+ }
137
+ throw error;
170
138
  }
171
- /** File length without materializing its contents; `undefined` when the path is absent. */
172
- async fileSize(path) {
173
- validatePath(path);
174
- try {
175
- return await retryTransient(async () => {
176
- const directory = await this.getDirectory(path.slice(0, -1), false);
177
- const handle = await directory.getFileHandle(path[path.length - 1] ?? "");
178
- return (await handle.getFile()).size;
179
- });
180
- }
181
- catch (error) {
182
- if (isDomError(error, "NotFoundError") || isDomError(error, "TypeMismatchError")) {
183
- return undefined;
184
- }
185
- throw error;
186
- }
139
+ }
140
+ async fileSize(path) {
141
+ validatePath(path);
142
+ try {
143
+ return await retryTransient(async () => {
144
+ const directory = await this.getDirectory(path.slice(0, -1), false);
145
+ const handle = await directory.getFileHandle(path[path.length - 1] ?? "");
146
+ return (await handle.getFile()).size;
147
+ });
148
+ } catch (error) {
149
+ if (isDomError(error, "NotFoundError") || isDomError(error, "TypeMismatchError")) {
150
+ return void 0;
151
+ }
152
+ throw error;
187
153
  }
188
- /**
189
- * Writes a whole file through an exclusive sync handle: truncate, write, optional flush,
190
- * close. Quota errors escape unwrapped. The handle is always closed, so a failed write never
191
- * leaves the file locked — only torn, which readers detect by checksum.
192
- */
193
- async writeFile(path, bytes, options = {}) {
194
- validatePath(path);
195
- await retryTransient(async () => {
196
- const handle = await this.openHandle(path, { create: true });
197
- try {
198
- handle.truncate(0);
199
- writeFully(handle, bytes, 0, `writing ${path.join("/")}`);
200
- if (options.flush === true)
201
- handle.flush();
202
- }
203
- finally {
204
- handle.close();
205
- }
206
- });
154
+ }
155
+ async writeFile(path, bytes, options = {}) {
156
+ validatePath(path);
157
+ await retryTransient(async () => {
158
+ const handle = await this.openHandle(path, { create: true });
159
+ try {
160
+ handle.truncate(0);
161
+ writeFully(handle, bytes, 0, `writing ${path.join("/")}`);
162
+ if (options.flush === true)
163
+ handle.flush();
164
+ } finally {
165
+ handle.close();
166
+ }
167
+ });
168
+ }
169
+ async openHandle(path, options) {
170
+ validatePath(path);
171
+ return retryTransient(async () => {
172
+ const directory = await this.getDirectory(path.slice(0, -1), options.create);
173
+ const handle = await directory.getFileHandle(path[path.length - 1] ?? "", {
174
+ create: options.create
175
+ });
176
+ return handle.createSyncAccessHandle();
177
+ });
178
+ }
179
+ async deleteFile(path) {
180
+ validatePath(path);
181
+ try {
182
+ await retryTransient(async () => {
183
+ const directory = await this.getDirectory(path.slice(0, -1), false);
184
+ await directory.removeEntry(path[path.length - 1] ?? "");
185
+ });
186
+ return true;
187
+ } catch (error) {
188
+ if (isDomError(error, "NotFoundError"))
189
+ return false;
190
+ throw error;
207
191
  }
208
- /**
209
- * An exclusive sync handle; callers own the read-verify-write protocol and the close.
210
- * Declared as the toolkit's `SyncFileHandle` — the browser handle satisfies it, and the
211
- * platform-specific type lives in the WebWorker lib, which not every consumer loads.
212
- */
213
- async openHandle(path, options) {
214
- validatePath(path);
215
- return retryTransient(async () => {
216
- const directory = await this.getDirectory(path.slice(0, -1), options.create);
217
- const handle = await directory.getFileHandle(path[path.length - 1] ?? "", {
218
- create: options.create,
219
- });
220
- return handle.createSyncAccessHandle();
221
- });
192
+ }
193
+ async deleteTree(path) {
194
+ validatePath(path);
195
+ try {
196
+ await retryTransient(async () => {
197
+ const directory = await this.getDirectory(path.slice(0, -1), false);
198
+ await directory.removeEntry(path[path.length - 1] ?? "", { recursive: true });
199
+ });
200
+ } catch (error) {
201
+ if (!isDomError(error, "NotFoundError"))
202
+ throw error;
222
203
  }
223
- /** True when the file existed. Absence is not an error — deletes are idempotent. */
224
- async deleteFile(path) {
225
- validatePath(path);
226
- try {
227
- await retryTransient(async () => {
228
- const directory = await this.getDirectory(path.slice(0, -1), false);
229
- await directory.removeEntry(path[path.length - 1] ?? "");
230
- });
231
- return true;
232
- }
233
- catch (error) {
234
- if (isDomError(error, "NotFoundError"))
235
- return false;
236
- throw error;
237
- }
204
+ this.invalidate(path);
205
+ }
206
+ async *iterateNames(path) {
207
+ validatePath(path);
208
+ let directory;
209
+ try {
210
+ directory = await this.getDirectory(path, false);
211
+ } catch (error) {
212
+ if (isDomError(error, "NotFoundError") || isDomError(error, "TypeMismatchError")) {
213
+ return;
214
+ }
215
+ throw error;
238
216
  }
239
- /** Removes a whole subtree; missing is fine. */
240
- async deleteTree(path) {
241
- validatePath(path);
242
- try {
243
- await retryTransient(async () => {
244
- const directory = await this.getDirectory(path.slice(0, -1), false);
245
- await directory.removeEntry(path[path.length - 1] ?? "", { recursive: true });
246
- });
247
- }
248
- catch (error) {
249
- if (!isDomError(error, "NotFoundError"))
250
- throw error;
251
- }
252
- this.invalidate(path);
217
+ for await (const name of directory.keys())
218
+ yield name;
219
+ }
220
+ async *walkFiles(path) {
221
+ validatePath(path);
222
+ let root;
223
+ try {
224
+ root = await this.getDirectory(path, false);
225
+ } catch (error) {
226
+ if (isDomError(error, "NotFoundError"))
227
+ return;
228
+ throw error;
253
229
  }
254
- /** Names directly inside a directory, streamed without retaining the directory cardinality. */
255
- async *iterateNames(path) {
256
- validatePath(path);
257
- let directory;
258
- try {
259
- directory = await this.getDirectory(path, false);
260
- }
261
- catch (error) {
262
- if (isDomError(error, "NotFoundError") || isDomError(error, "TypeMismatchError")) {
263
- return;
230
+ const walk = async function* (directory, prefix) {
231
+ for await (const [name, entry] of directory) {
232
+ if (entry.kind === "file") {
233
+ try {
234
+ const file = await entry.getFile();
235
+ yield { path: [...prefix, name], size: file.size };
236
+ } catch (error) {
237
+ if (!isDomError(error, "NotFoundError") && !isDomError(error, "NoModificationAllowedError") && !isDomError(error, "NotReadableError")) {
238
+ throw error;
264
239
  }
265
- throw error;
266
- }
267
- for await (const name of directory.keys())
268
- yield name;
269
- }
270
- /** Every file under a directory, streamed with O(path depth) retained memory. */
271
- async *walkFiles(path) {
272
- validatePath(path);
273
- let root;
274
- try {
275
- root = await this.getDirectory(path, false);
276
- }
277
- catch (error) {
278
- if (isDomError(error, "NotFoundError"))
279
- return;
280
- throw error;
240
+ }
241
+ } else {
242
+ yield* walk(entry, [...prefix, name]);
281
243
  }
282
- const walk = async function* (directory, prefix) {
283
- for await (const [name, entry] of directory) {
284
- if (entry.kind === "file") {
285
- try {
286
- const file = await entry.getFile();
287
- yield { path: [...prefix, name], size: file.size };
288
- }
289
- catch (error) {
290
- // A file another instance is writing or deleting this instant is not part of any
291
- // committed state; skip it rather than failing the whole walk.
292
- if (!isDomError(error, "NotFoundError") &&
293
- !isDomError(error, "NoModificationAllowedError") &&
294
- !isDomError(error, "NotReadableError")) {
295
- throw error;
296
- }
297
- }
298
- }
299
- else {
300
- yield* walk(entry, [...prefix, name]);
301
- }
302
- }
303
- };
304
- yield* walk(root, []);
305
- }
244
+ }
245
+ };
246
+ yield* walk(root, []);
247
+ }
306
248
  }
307
- export function isDomError(error, name) {
308
- return error instanceof DOMException && error.name === name;
249
+ function isDomError(error, name) {
250
+ return error instanceof DOMException && error.name === name;
309
251
  }
310
- /**
311
- * WebKit's OPFS backend occasionally fails an operation with `UnknownError` and a message that
312
- * literally says "for an unknown transient reason" — observed under nothing more exotic than
313
- * opening and closing handles in quick succession. The declared response to a declared
314
- * transient failure is a bounded retry; anything else, or persistence past the budget,
315
- * propagates.
316
- */
317
252
  async function retryTransient(operation) {
318
- for (let attempt = 0;; attempt += 1) {
319
- try {
320
- return await operation();
321
- }
322
- catch (error) {
323
- if (isDomError(error, "UnknownError") && attempt < 20) {
324
- await new Promise((resolve) => setTimeout(resolve, Math.min(50, 2 ** Math.min(attempt, 6))));
325
- continue;
326
- }
327
- throw error;
328
- }
253
+ for (let attempt = 0; ; attempt += 1) {
254
+ try {
255
+ return await operation();
256
+ } catch (error) {
257
+ if (isDomError(error, "UnknownError") && attempt < 20) {
258
+ await new Promise((resolve) => setTimeout(resolve, Math.min(50, 2 ** Math.min(attempt, 6))));
259
+ continue;
260
+ }
261
+ throw error;
329
262
  }
263
+ }
330
264
  }
265
+ export {
266
+ MAX_OPFS_DIRECTORY_HANDLE_CACHE_ENTRIES,
267
+ MAX_OPFS_ENCODED_SEGMENT_CHARACTERS,
268
+ MAX_OPFS_PATH_SEGMENTS,
269
+ OpfsTree,
270
+ decodeSegment,
271
+ encodeSegment,
272
+ isDomError
273
+ };
@@ -1,3 +1,9 @@
1
- export { OpfsBlockStore, deleteOpfsDatabase } from "./store.js";
2
- export { OpfsUncertainOutcomeError } from "../types.js";
3
- export { OpfsTree } from "./files.js";
1
+ import { OpfsBlockStore, deleteOpfsDatabase } from "./store.js";
2
+ import { OpfsUncertainOutcomeError } from "../types.js";
3
+ import { OpfsTree } from "./files.js";
4
+ export {
5
+ OpfsBlockStore,
6
+ OpfsTree,
7
+ OpfsUncertainOutcomeError,
8
+ deleteOpfsDatabase
9
+ };