@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.
- package/dist/block-format/block.js +254 -325
- package/dist/block-format/checksum.js +30 -52
- package/dist/block-format/codecs.js +112 -125
- package/dist/block-format/column.js +187 -205
- package/dist/block-format/index.js +5 -1
- package/dist/block-format/physical.js +371 -387
- package/dist/block-format/types.js +4 -1
- package/dist/block-format/unicode.js +30 -39
- package/dist/date-value.js +67 -78
- package/dist/engine/artifact-cache.js +106 -116
- package/dist/engine/batch.js +36 -44
- package/dist/engine/buffered-writer.js +156 -158
- package/dist/engine/byte-estimates.js +20 -22
- package/dist/engine/cache-limits.js +24 -25
- package/dist/engine/cancellation.js +5 -3
- package/dist/engine/catalog.js +64 -73
- package/dist/engine/client.js +715 -780
- package/dist/engine/database.js +15941 -19313
- package/dist/engine/defaults.js +104 -111
- package/dist/engine/errors.js +113 -128
- package/dist/engine/fts.js +233 -280
- package/dist/engine/group-index.js +287 -321
- package/dist/engine/index.js +23 -12
- package/dist/engine/join-index.js +177 -186
- package/dist/engine/keyed-live.js +168 -173
- package/dist/engine/live-api.js +0 -1
- package/dist/engine/live-equal.js +32 -37
- package/dist/engine/live.js +603 -647
- package/dist/engine/memory.js +109 -111
- package/dist/engine/optimizer.d.ts +19 -1
- package/dist/engine/optimizer.js +2633 -2934
- package/dist/engine/point-read.js +122 -182
- package/dist/engine/query-api.js +12 -3
- package/dist/engine/query-cache.js +61 -69
- package/dist/engine/query.d.ts +6 -0
- package/dist/engine/query.js +8503 -9980
- package/dist/engine/result-wire.js +221 -252
- package/dist/engine/schema-wire.js +97 -112
- package/dist/engine/schema.js +1099 -1254
- package/dist/engine/sort-keys.js +315 -389
- package/dist/engine/sql-domains.js +646 -769
- package/dist/engine/sql-driver.js +0 -1
- package/dist/engine/sql-functions.js +1031 -1125
- package/dist/engine/sql-json.js +199 -218
- package/dist/engine/sql-semantics.js +572 -639
- package/dist/engine/typed-live.js +271 -293
- package/dist/engine/vector.js +4484 -5145
- package/dist/engine/worker-host.d.ts +8 -63
- package/dist/engine/worker-host.js +28 -1130
- package/dist/engine/worker-indexeddb.d.ts +1 -0
- package/dist/engine/worker-indexeddb.js +3 -0
- package/dist/engine/worker-memory.d.ts +1 -0
- package/dist/engine/worker-memory.js +3 -0
- package/dist/engine/worker-opfs.d.ts +1 -0
- package/dist/engine/worker-opfs.js +3 -0
- package/dist/engine/worker-server.d.ts +93 -0
- package/dist/engine/worker-server.js +999 -0
- package/dist/engine/worker-store-indexeddb.d.ts +2 -0
- package/dist/engine/worker-store-indexeddb.js +11 -0
- package/dist/engine/worker-store-memory.d.ts +2 -0
- package/dist/engine/worker-store-memory.js +6 -0
- package/dist/engine/worker-store-opfs.d.ts +2 -0
- package/dist/engine/worker-store-opfs.js +9 -0
- package/dist/engine/worker.js +0 -12
- package/dist/engine/write-block-planner.js +93 -100
- package/dist/plan/index.js +15 -16
- package/dist/plan/model.d.ts +6 -0
- package/dist/plan/model.js +20 -27
- package/dist/storage/index.js +0 -12
- package/dist/storage/indexeddb.js +11776 -13171
- package/dist/storage/memory.js +1198 -1241
- package/dist/storage/opfs/files.js +238 -295
- package/dist/storage/opfs/index.js +9 -3
- package/dist/storage/opfs/leader.js +4386 -4856
- package/dist/storage/opfs/rpc.js +223 -259
- package/dist/storage/opfs/snapshot-ledger.js +219 -260
- package/dist/storage/opfs/store.js +884 -1003
- package/dist/storage/persistence.js +59 -74
- package/dist/storage/snapshot-stream.js +788 -826
- package/dist/storage/snapshot.js +36 -37
- package/dist/storage/toolkit/extents.js +444 -520
- package/dist/storage/toolkit/index.js +28 -29
- package/dist/storage/toolkit/record-core.js +5792 -6377
- package/dist/storage/toolkit/sync-file.js +34 -42
- package/dist/storage/toolkit/wal.js +92 -127
- package/dist/storage/toolkit/wire.js +232 -256
- package/dist/storage/types.js +2948 -3250
- package/dist/testing/block-store-conformance.js +1556 -1629
- package/dist/testing/index.js +310 -291
- package/dist/testing/opfs-shim.js +280 -312
- package/dist/testing/simulator.js +496 -534
- package/dist/testing/sqllogictest.js +425 -365
- package/dist/transactions/index.js +1464 -1712
- package/dist/worker-protocol/index.js +70 -69
- 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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
26
|
+
for (const byte of segmentTextEncoder.encode(char)) {
|
|
27
|
+
encoded += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
28
|
+
assertEncodedSegmentLength(encoded.length);
|
|
71
29
|
}
|
|
72
|
-
|
|
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 (
|
|
76
|
-
|
|
51
|
+
if (!SAFE_SEGMENT_CHARS.test(char)) {
|
|
52
|
+
throw new Error("Invalid OPFS path segment encoding");
|
|
77
53
|
}
|
|
78
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
266
|
-
}
|
|
267
|
-
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
308
|
-
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
+
};
|