@minnowdb/core 0.6.9 → 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 (103) 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.d.ts +5 -0
  10. package/dist/date-value.js +71 -41
  11. package/dist/engine/artifact-cache.js +106 -116
  12. package/dist/engine/batch.js +36 -44
  13. package/dist/engine/buffered-writer.js +156 -158
  14. package/dist/engine/byte-estimates.js +20 -22
  15. package/dist/engine/cache-limits.js +24 -25
  16. package/dist/engine/cancellation.js +5 -3
  17. package/dist/engine/catalog.js +64 -73
  18. package/dist/engine/client.js +715 -780
  19. package/dist/engine/database.js +15952 -18465
  20. package/dist/engine/defaults.js +104 -111
  21. package/dist/engine/errors.js +113 -128
  22. package/dist/engine/fts.js +233 -280
  23. package/dist/engine/group-index.js +287 -321
  24. package/dist/engine/index.js +23 -12
  25. package/dist/engine/join-index.js +177 -186
  26. package/dist/engine/keyed-live.js +168 -173
  27. package/dist/engine/live-api.js +0 -1
  28. package/dist/engine/live-equal.js +32 -37
  29. package/dist/engine/live.js +603 -647
  30. package/dist/engine/memory.js +109 -111
  31. package/dist/engine/optimizer.d.ts +19 -1
  32. package/dist/engine/optimizer.js +2642 -2515
  33. package/dist/engine/point-read.d.ts +4 -2
  34. package/dist/engine/point-read.js +121 -171
  35. package/dist/engine/query-api.js +12 -3
  36. package/dist/engine/query-cache.js +61 -69
  37. package/dist/engine/query.d.ts +45 -0
  38. package/dist/engine/query.js +8506 -9375
  39. package/dist/engine/result-wire.js +221 -252
  40. package/dist/engine/schema-wire.js +97 -112
  41. package/dist/engine/schema.js +1099 -1254
  42. package/dist/engine/sort-keys.js +315 -389
  43. package/dist/engine/sql-domains.d.ts +7 -1
  44. package/dist/engine/sql-domains.js +648 -734
  45. package/dist/engine/sql-driver.js +0 -1
  46. package/dist/engine/sql-functions.d.ts +11 -0
  47. package/dist/engine/sql-functions.js +1092 -0
  48. package/dist/engine/sql-json.js +199 -218
  49. package/dist/engine/sql-semantics.d.ts +25 -4
  50. package/dist/engine/sql-semantics.js +579 -513
  51. package/dist/engine/typed-live.js +271 -293
  52. package/dist/engine/vector.d.ts +7 -1
  53. package/dist/engine/vector.js +4513 -4580
  54. package/dist/engine/worker-host.d.ts +8 -63
  55. package/dist/engine/worker-host.js +28 -1130
  56. package/dist/engine/worker-indexeddb.d.ts +1 -0
  57. package/dist/engine/worker-indexeddb.js +3 -0
  58. package/dist/engine/worker-memory.d.ts +1 -0
  59. package/dist/engine/worker-memory.js +3 -0
  60. package/dist/engine/worker-opfs.d.ts +1 -0
  61. package/dist/engine/worker-opfs.js +3 -0
  62. package/dist/engine/worker-server.d.ts +93 -0
  63. package/dist/engine/worker-server.js +999 -0
  64. package/dist/engine/worker-store-indexeddb.d.ts +2 -0
  65. package/dist/engine/worker-store-indexeddb.js +11 -0
  66. package/dist/engine/worker-store-memory.d.ts +2 -0
  67. package/dist/engine/worker-store-memory.js +6 -0
  68. package/dist/engine/worker-store-opfs.d.ts +2 -0
  69. package/dist/engine/worker-store-opfs.js +9 -0
  70. package/dist/engine/worker.js +0 -12
  71. package/dist/engine/write-block-planner.js +93 -100
  72. package/dist/plan/index.js +15 -16
  73. package/dist/plan/model.d.ts +9 -1
  74. package/dist/plan/model.js +20 -27
  75. package/dist/storage/index.js +0 -12
  76. package/dist/storage/indexeddb.js +11776 -13171
  77. package/dist/storage/memory.js +1198 -1241
  78. package/dist/storage/opfs/files.js +238 -295
  79. package/dist/storage/opfs/index.js +9 -3
  80. package/dist/storage/opfs/leader.js +4386 -4856
  81. package/dist/storage/opfs/rpc.js +223 -259
  82. package/dist/storage/opfs/snapshot-ledger.js +219 -260
  83. package/dist/storage/opfs/store.js +884 -1003
  84. package/dist/storage/persistence.js +59 -74
  85. package/dist/storage/snapshot-stream.js +788 -826
  86. package/dist/storage/snapshot.js +36 -37
  87. package/dist/storage/toolkit/extents.js +444 -520
  88. package/dist/storage/toolkit/index.js +28 -29
  89. package/dist/storage/toolkit/record-core.js +5792 -6377
  90. package/dist/storage/toolkit/sync-file.js +34 -42
  91. package/dist/storage/toolkit/wal.js +92 -127
  92. package/dist/storage/toolkit/wire.js +232 -256
  93. package/dist/storage/types.js +2952 -3228
  94. package/dist/testing/block-store-conformance.js +1556 -1629
  95. package/dist/testing/index.js +310 -291
  96. package/dist/testing/opfs-shim.js +280 -312
  97. package/dist/testing/simulator.js +496 -534
  98. package/dist/testing/sqllogictest.js +425 -365
  99. package/dist/transactions/index.js +1464 -1712
  100. package/dist/worker-protocol/index.js +70 -69
  101. package/package.json +20 -2
  102. package/postgres-feature-profile.json +15 -5
  103. package/sql-feature-matrix.json +252 -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
+ };