@minnowdb/core 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/README.md +4 -2
  2. package/dist/block-format/block.d.ts +32 -13
  3. package/dist/block-format/block.js +179 -55
  4. package/dist/block-format/checksum.d.ts +2 -1
  5. package/dist/block-format/checksum.js +5 -2
  6. package/dist/block-format/codecs.d.ts +7 -2
  7. package/dist/block-format/codecs.js +53 -12
  8. package/dist/block-format/column.d.ts +3 -2
  9. package/dist/block-format/column.js +96 -30
  10. package/dist/block-format/index.d.ts +2 -2
  11. package/dist/block-format/index.js +2 -2
  12. package/dist/block-format/physical.d.ts +8 -2
  13. package/dist/block-format/physical.js +14 -6
  14. package/dist/block-format/types.d.ts +7 -3
  15. package/dist/block-format/types.js +0 -1
  16. package/dist/block-format/unicode.d.ts +11 -0
  17. package/dist/block-format/unicode.js +46 -0
  18. package/dist/date-value.d.ts +15 -0
  19. package/dist/date-value.js +64 -0
  20. package/dist/engine/artifact-cache.d.ts +0 -1
  21. package/dist/engine/artifact-cache.js +0 -1
  22. package/dist/engine/batch.d.ts +5 -2
  23. package/dist/engine/batch.js +0 -1
  24. package/dist/engine/buffered-writer.d.ts +2 -1
  25. package/dist/engine/buffered-writer.js +34 -9
  26. package/dist/engine/cache-limits.d.ts +25 -0
  27. package/dist/engine/cache-limits.js +25 -0
  28. package/dist/engine/catalog.d.ts +5 -6
  29. package/dist/engine/catalog.js +5 -5
  30. package/dist/engine/client.d.ts +27 -7
  31. package/dist/engine/client.js +197 -31
  32. package/dist/engine/database.d.ts +161 -38
  33. package/dist/engine/database.js +4320 -1523
  34. package/dist/engine/defaults.d.ts +0 -1
  35. package/dist/engine/defaults.js +2 -4
  36. package/dist/engine/errors.d.ts +19 -1
  37. package/dist/engine/errors.js +32 -2
  38. package/dist/engine/fts.d.ts +0 -6
  39. package/dist/engine/fts.js +39 -14
  40. package/dist/engine/group-index.d.ts +0 -1
  41. package/dist/engine/group-index.js +6 -11
  42. package/dist/engine/index.d.ts +16 -11
  43. package/dist/engine/index.js +12 -10
  44. package/dist/engine/join-index.d.ts +0 -1
  45. package/dist/engine/join-index.js +2 -2
  46. package/dist/engine/keyed-live.d.ts +57 -0
  47. package/dist/engine/keyed-live.js +226 -0
  48. package/dist/engine/live-api.d.ts +4 -0
  49. package/dist/engine/live-api.js +4 -0
  50. package/dist/engine/live.d.ts +43 -26
  51. package/dist/engine/live.js +499 -129
  52. package/dist/engine/memory.d.ts +2 -1
  53. package/dist/engine/memory.js +3 -2
  54. package/dist/engine/optimizer.d.ts +0 -1
  55. package/dist/engine/optimizer.js +4 -4
  56. package/dist/engine/query-api.d.ts +3 -0
  57. package/dist/engine/query-api.js +3 -0
  58. package/dist/engine/query-cache.d.ts +0 -1
  59. package/dist/engine/query-cache.js +4 -4
  60. package/dist/engine/query.d.ts +4 -7
  61. package/dist/engine/query.js +348 -155
  62. package/dist/engine/result-wire.d.ts +0 -1
  63. package/dist/engine/result-wire.js +2 -2
  64. package/dist/engine/schema-wire.d.ts +1 -2
  65. package/dist/engine/schema-wire.js +0 -1
  66. package/dist/engine/schema.d.ts +3 -8
  67. package/dist/engine/schema.js +41 -24
  68. package/dist/engine/sort-keys.d.ts +0 -1
  69. package/dist/engine/sort-keys.js +2 -2
  70. package/dist/engine/sql-domains.d.ts +6 -1
  71. package/dist/engine/sql-domains.js +209 -45
  72. package/dist/engine/sql-driver.d.ts +2 -2
  73. package/dist/engine/sql-driver.js +0 -1
  74. package/dist/engine/sql-json.d.ts +0 -1
  75. package/dist/engine/sql-json.js +53 -7
  76. package/dist/engine/sql-semantics.d.ts +8 -5
  77. package/dist/engine/sql-semantics.js +427 -65
  78. package/dist/engine/typed-live.d.ts +67 -0
  79. package/dist/engine/typed-live.js +349 -0
  80. package/dist/engine/vector.d.ts +7 -1
  81. package/dist/engine/vector.js +202 -58
  82. package/dist/engine/worker-host.d.ts +8 -2
  83. package/dist/engine/worker-host.js +616 -152
  84. package/dist/engine/worker.d.ts +0 -1
  85. package/dist/engine/worker.js +3 -3
  86. package/dist/engine/write-block-planner.d.ts +18 -0
  87. package/dist/engine/write-block-planner.js +112 -0
  88. package/dist/index.d.ts +0 -1
  89. package/dist/index.js +0 -1
  90. package/dist/plan/index.d.ts +0 -1
  91. package/dist/plan/index.js +0 -1
  92. package/dist/storage/index.d.ts +1 -1
  93. package/dist/storage/index.js +1 -1
  94. package/dist/storage/indexeddb.d.ts +74 -65
  95. package/dist/storage/indexeddb.js +12301 -2076
  96. package/dist/storage/memory.d.ts +56 -66
  97. package/dist/storage/memory.js +1004 -139
  98. package/dist/storage/opfs/files.d.ts +18 -7
  99. package/dist/storage/opfs/files.js +115 -28
  100. package/dist/storage/opfs/index.d.ts +1 -1
  101. package/dist/storage/opfs/index.js +1 -1
  102. package/dist/storage/opfs/leader.d.ts +403 -91
  103. package/dist/storage/opfs/leader.js +4306 -602
  104. package/dist/storage/opfs/rpc.d.ts +15 -3
  105. package/dist/storage/opfs/rpc.js +223 -2
  106. package/dist/storage/opfs/snapshot-ledger.d.ts +40 -0
  107. package/dist/storage/opfs/snapshot-ledger.js +281 -0
  108. package/dist/storage/opfs/store.d.ts +26 -121
  109. package/dist/storage/opfs/store.js +436 -388
  110. package/dist/storage/persistence.d.ts +37 -0
  111. package/dist/storage/persistence.js +78 -0
  112. package/dist/storage/snapshot-stream.d.ts +24 -0
  113. package/dist/storage/snapshot-stream.js +897 -0
  114. package/dist/storage/snapshot.d.ts +9 -99
  115. package/dist/storage/snapshot.js +33 -309
  116. package/dist/storage/toolkit/extents.d.ts +41 -7
  117. package/dist/storage/toolkit/extents.js +402 -39
  118. package/dist/storage/toolkit/index.d.ts +3 -4
  119. package/dist/storage/toolkit/index.js +27 -3
  120. package/dist/storage/toolkit/record-core.d.ts +174 -61
  121. package/dist/storage/toolkit/record-core.js +5401 -849
  122. package/dist/storage/toolkit/sync-file.d.ts +10 -1
  123. package/dist/storage/toolkit/sync-file.js +50 -2
  124. package/dist/storage/toolkit/wal.d.ts +23 -5
  125. package/dist/storage/toolkit/wal.js +89 -27
  126. package/dist/storage/toolkit/wire.d.ts +4 -6
  127. package/dist/storage/toolkit/wire.js +80 -25
  128. package/dist/storage/types.d.ts +1271 -218
  129. package/dist/storage/types.js +1471 -138
  130. package/dist/testing/block-store-conformance.d.ts +0 -1
  131. package/dist/testing/block-store-conformance.js +877 -138
  132. package/dist/testing/index.d.ts +42 -31
  133. package/dist/testing/index.js +100 -68
  134. package/dist/testing/opfs-shim.d.ts +13 -3
  135. package/dist/testing/opfs-shim.js +40 -6
  136. package/dist/testing/simulator.d.ts +0 -1
  137. package/dist/testing/simulator.js +43 -12
  138. package/dist/testing/sqllogictest.d.ts +0 -1
  139. package/dist/testing/sqllogictest.js +0 -1
  140. package/dist/transactions/index.d.ts +81 -20
  141. package/dist/transactions/index.js +1050 -268
  142. package/dist/worker-protocol/index.d.ts +3 -2
  143. package/dist/worker-protocol/index.js +2 -5
  144. package/package.json +49 -1
  145. package/dist/block-format/block.d.ts.map +0 -1
  146. package/dist/block-format/block.js.map +0 -1
  147. package/dist/block-format/checksum.d.ts.map +0 -1
  148. package/dist/block-format/checksum.js.map +0 -1
  149. package/dist/block-format/codecs.d.ts.map +0 -1
  150. package/dist/block-format/codecs.js.map +0 -1
  151. package/dist/block-format/column.d.ts.map +0 -1
  152. package/dist/block-format/column.js.map +0 -1
  153. package/dist/block-format/index.d.ts.map +0 -1
  154. package/dist/block-format/index.js.map +0 -1
  155. package/dist/block-format/physical.d.ts.map +0 -1
  156. package/dist/block-format/physical.js.map +0 -1
  157. package/dist/block-format/types.d.ts.map +0 -1
  158. package/dist/block-format/types.js.map +0 -1
  159. package/dist/engine/artifact-cache.d.ts.map +0 -1
  160. package/dist/engine/artifact-cache.js.map +0 -1
  161. package/dist/engine/batch.d.ts.map +0 -1
  162. package/dist/engine/batch.js.map +0 -1
  163. package/dist/engine/buffered-writer.d.ts.map +0 -1
  164. package/dist/engine/buffered-writer.js.map +0 -1
  165. package/dist/engine/catalog.d.ts.map +0 -1
  166. package/dist/engine/catalog.js.map +0 -1
  167. package/dist/engine/client.d.ts.map +0 -1
  168. package/dist/engine/client.js.map +0 -1
  169. package/dist/engine/coordinator.d.ts +0 -17
  170. package/dist/engine/coordinator.d.ts.map +0 -1
  171. package/dist/engine/coordinator.js +0 -60
  172. package/dist/engine/coordinator.js.map +0 -1
  173. package/dist/engine/database.d.ts.map +0 -1
  174. package/dist/engine/database.js.map +0 -1
  175. package/dist/engine/defaults.d.ts.map +0 -1
  176. package/dist/engine/defaults.js.map +0 -1
  177. package/dist/engine/errors.d.ts.map +0 -1
  178. package/dist/engine/errors.js.map +0 -1
  179. package/dist/engine/fts.d.ts.map +0 -1
  180. package/dist/engine/fts.js.map +0 -1
  181. package/dist/engine/group-index.d.ts.map +0 -1
  182. package/dist/engine/group-index.js.map +0 -1
  183. package/dist/engine/index.d.ts.map +0 -1
  184. package/dist/engine/index.js.map +0 -1
  185. package/dist/engine/join-index.d.ts.map +0 -1
  186. package/dist/engine/join-index.js.map +0 -1
  187. package/dist/engine/live.d.ts.map +0 -1
  188. package/dist/engine/live.js.map +0 -1
  189. package/dist/engine/memory.d.ts.map +0 -1
  190. package/dist/engine/memory.js.map +0 -1
  191. package/dist/engine/optimizer.d.ts.map +0 -1
  192. package/dist/engine/optimizer.js.map +0 -1
  193. package/dist/engine/query-cache.d.ts.map +0 -1
  194. package/dist/engine/query-cache.js.map +0 -1
  195. package/dist/engine/query.d.ts.map +0 -1
  196. package/dist/engine/query.js.map +0 -1
  197. package/dist/engine/result-wire.d.ts.map +0 -1
  198. package/dist/engine/result-wire.js.map +0 -1
  199. package/dist/engine/schema-wire.d.ts.map +0 -1
  200. package/dist/engine/schema-wire.js.map +0 -1
  201. package/dist/engine/schema.d.ts.map +0 -1
  202. package/dist/engine/schema.js.map +0 -1
  203. package/dist/engine/sort-keys.d.ts.map +0 -1
  204. package/dist/engine/sort-keys.js.map +0 -1
  205. package/dist/engine/sql-domains.d.ts.map +0 -1
  206. package/dist/engine/sql-domains.js.map +0 -1
  207. package/dist/engine/sql-driver.d.ts.map +0 -1
  208. package/dist/engine/sql-driver.js.map +0 -1
  209. package/dist/engine/sql-json.d.ts.map +0 -1
  210. package/dist/engine/sql-json.js.map +0 -1
  211. package/dist/engine/sql-semantics.d.ts.map +0 -1
  212. package/dist/engine/sql-semantics.js.map +0 -1
  213. package/dist/engine/vector.d.ts.map +0 -1
  214. package/dist/engine/vector.js.map +0 -1
  215. package/dist/engine/worker-host.d.ts.map +0 -1
  216. package/dist/engine/worker-host.js.map +0 -1
  217. package/dist/engine/worker.d.ts.map +0 -1
  218. package/dist/engine/worker.js.map +0 -1
  219. package/dist/index.d.ts.map +0 -1
  220. package/dist/index.js.map +0 -1
  221. package/dist/plan/index.d.ts.map +0 -1
  222. package/dist/plan/index.js.map +0 -1
  223. package/dist/storage/fixture-shape.d.ts +0 -42
  224. package/dist/storage/fixture-shape.d.ts.map +0 -1
  225. package/dist/storage/fixture-shape.js +0 -146
  226. package/dist/storage/fixture-shape.js.map +0 -1
  227. package/dist/storage/index.d.ts.map +0 -1
  228. package/dist/storage/index.js.map +0 -1
  229. package/dist/storage/indexeddb.d.ts.map +0 -1
  230. package/dist/storage/indexeddb.js.map +0 -1
  231. package/dist/storage/memory.d.ts.map +0 -1
  232. package/dist/storage/memory.js.map +0 -1
  233. package/dist/storage/opfs/files.d.ts.map +0 -1
  234. package/dist/storage/opfs/files.js.map +0 -1
  235. package/dist/storage/opfs/index.d.ts.map +0 -1
  236. package/dist/storage/opfs/index.js.map +0 -1
  237. package/dist/storage/opfs/leader.d.ts.map +0 -1
  238. package/dist/storage/opfs/leader.js.map +0 -1
  239. package/dist/storage/opfs/rpc.d.ts.map +0 -1
  240. package/dist/storage/opfs/rpc.js.map +0 -1
  241. package/dist/storage/opfs/store.d.ts.map +0 -1
  242. package/dist/storage/opfs/store.js.map +0 -1
  243. package/dist/storage/snapshot.d.ts.map +0 -1
  244. package/dist/storage/snapshot.js.map +0 -1
  245. package/dist/storage/toolkit/extents.d.ts.map +0 -1
  246. package/dist/storage/toolkit/extents.js.map +0 -1
  247. package/dist/storage/toolkit/index.d.ts.map +0 -1
  248. package/dist/storage/toolkit/index.js.map +0 -1
  249. package/dist/storage/toolkit/record-core.d.ts.map +0 -1
  250. package/dist/storage/toolkit/record-core.js.map +0 -1
  251. package/dist/storage/toolkit/sync-file.d.ts.map +0 -1
  252. package/dist/storage/toolkit/sync-file.js.map +0 -1
  253. package/dist/storage/toolkit/wal.d.ts.map +0 -1
  254. package/dist/storage/toolkit/wal.js.map +0 -1
  255. package/dist/storage/toolkit/wire.d.ts.map +0 -1
  256. package/dist/storage/toolkit/wire.js.map +0 -1
  257. package/dist/storage/types.d.ts.map +0 -1
  258. package/dist/storage/types.js.map +0 -1
  259. package/dist/testing/block-store-conformance.d.ts.map +0 -1
  260. package/dist/testing/block-store-conformance.js.map +0 -1
  261. package/dist/testing/index.d.ts.map +0 -1
  262. package/dist/testing/index.js.map +0 -1
  263. package/dist/testing/opfs-shim.d.ts.map +0 -1
  264. package/dist/testing/opfs-shim.js.map +0 -1
  265. package/dist/testing/seeds.d.ts +0 -11
  266. package/dist/testing/seeds.d.ts.map +0 -1
  267. package/dist/testing/seeds.js +0 -50
  268. package/dist/testing/seeds.js.map +0 -1
  269. package/dist/testing/simulator.d.ts.map +0 -1
  270. package/dist/testing/simulator.js.map +0 -1
  271. package/dist/testing/sqllogictest.d.ts.map +0 -1
  272. package/dist/testing/sqllogictest.js.map +0 -1
  273. package/dist/transactions/index.d.ts.map +0 -1
  274. package/dist/transactions/index.js.map +0 -1
  275. package/dist/worker-protocol/index.d.ts.map +0 -1
  276. package/dist/worker-protocol/index.js.map +0 -1
@@ -6,7 +6,13 @@
6
6
  * non-leading `.` pass through, everything else becomes `%XX` per UTF-8 byte — so names
7
7
  * round-trip exactly and none can collide with or escape into another's path.
8
8
  */
9
- import type { SyncFileHandle } from "../toolkit/sync-file.js";
9
+ import { type SyncFileHandle } from "../toolkit/sync-file.js";
10
+ /** Conservative cross-browser/filesystem ceiling for one already-encoded OPFS entry name. */
11
+ export declare const MAX_OPFS_ENCODED_SEGMENT_CHARACTERS = 240;
12
+ /** Directory handles have no close operation, so bound retained wrappers under path churn. */
13
+ export declare const MAX_OPFS_DIRECTORY_HANDLE_CACHE_ENTRIES = 128;
14
+ /** Internal layouts are shallow; reject adversarial/custom paths before recursive resolution. */
15
+ export declare const MAX_OPFS_PATH_SEGMENTS = 16;
10
16
  export declare function encodeSegment(segment: string): string;
11
17
  export declare function decodeSegment(encoded: string): string;
12
18
  export interface WriteFileOptions {
@@ -20,6 +26,8 @@ export interface ReadFileOptions {
20
26
  * default rethrows.
21
27
  */
22
28
  lockedMeansAbsent?: boolean;
29
+ /** Refuse before `arrayBuffer()` allocates when the file exceeds this exact byte ceiling. */
30
+ maxBytes?: number;
23
31
  }
24
32
  /**
25
33
  * A directory-handle cache plus the file operations the store needs. Handles are cached per
@@ -29,11 +37,15 @@ export interface ReadFileOptions {
29
37
  export declare class OpfsTree {
30
38
  #private;
31
39
  constructor(root: FileSystemDirectoryHandle);
40
+ /** @internal Hardening-test visibility for the cache's fixed-memory invariant. */
41
+ get directoryCacheSizeForTests(): number;
32
42
  getDirectory(path: readonly string[], create: boolean): Promise<FileSystemDirectoryHandle>;
33
43
  /** Drops cached handles at and under a path — call after deleting a directory. */
34
44
  invalidate(pathPrefix: readonly string[]): void;
35
45
  /** `undefined` when the file (or any parent) does not exist. */
36
46
  readFile(path: readonly string[], options?: ReadFileOptions): Promise<Uint8Array | undefined>;
47
+ /** File length without materializing its contents; `undefined` when the path is absent. */
48
+ fileSize(path: readonly string[]): Promise<number | undefined>;
37
49
  /**
38
50
  * Writes a whole file through an exclusive sync handle: truncate, write, optional flush,
39
51
  * close. Quota errors escape unwrapped. The handle is always closed, so a failed write never
@@ -52,13 +64,12 @@ export declare class OpfsTree {
52
64
  deleteFile(path: readonly string[]): Promise<boolean>;
53
65
  /** Removes a whole subtree; missing is fine. */
54
66
  deleteTree(path: readonly string[]): Promise<void>;
55
- /** Names of the entries directly inside a directory; `[]` when it does not exist. */
56
- listNames(path: readonly string[]): Promise<string[]>;
57
- /** Every file under a directory, with sizes the open-time block index walk. */
58
- walkFiles(path: readonly string[]): Promise<Array<{
67
+ /** Names directly inside a directory, streamed without retaining the directory cardinality. */
68
+ iterateNames(path: readonly string[]): AsyncGenerator<string>;
69
+ /** Every file under a directory, streamed with O(path depth) retained memory. */
70
+ walkFiles(path: readonly string[]): AsyncGenerator<{
59
71
  path: string[];
60
72
  size: number;
61
- }>>;
73
+ }>;
62
74
  }
63
75
  export declare function isDomError(error: unknown, name: string): boolean;
64
- //# sourceMappingURL=files.d.ts.map
@@ -6,38 +6,86 @@
6
6
  * non-leading `.` pass through, everything else becomes `%XX` per UTF-8 byte — so names
7
7
  * round-trip exactly and none can collide with or escape into another's path.
8
8
  */
9
+ import { assertWellFormedString } from "../../block-format/unicode.js";
10
+ import { writeFully } from "../toolkit/sync-file.js";
9
11
  const SAFE_SEGMENT_CHARS = /^[A-Za-z0-9._-]$/;
12
+ const UPPERCASE_HEX_BYTE = /^[0-9A-F]{2}$/;
13
+ const segmentTextEncoder = new TextEncoder();
14
+ 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;
21
+ 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
+ }
10
26
  export function encodeSegment(segment) {
27
+ assertWellFormedString(segment, "OPFS path segment");
11
28
  if (segment.length === 0)
12
29
  return "%";
13
30
  let encoded = "";
14
31
  for (const char of segment) {
15
32
  if (SAFE_SEGMENT_CHARS.test(char) && !(encoded.length === 0 && char === ".")) {
16
33
  encoded += char;
34
+ assertEncodedSegmentLength(encoded.length);
17
35
  continue;
18
36
  }
19
- for (const byte of new TextEncoder().encode(char)) {
37
+ for (const byte of segmentTextEncoder.encode(char)) {
20
38
  encoded += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
39
+ assertEncodedSegmentLength(encoded.length);
21
40
  }
22
41
  }
23
42
  return encoded;
24
43
  }
25
44
  export function decodeSegment(encoded) {
45
+ assertEncodedSegmentLength(encoded.length);
26
46
  if (encoded === "%")
27
47
  return "";
48
+ if (encoded.length === 0)
49
+ throw new Error("Invalid OPFS path segment encoding");
28
50
  const bytes = [];
29
51
  for (let index = 0; index < encoded.length;) {
30
52
  const char = encoded[index] ?? "";
31
53
  if (char === "%") {
32
- bytes.push(Number.parseInt(encoded.slice(index + 1, index + 3), 16));
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));
33
59
  index += 3;
34
60
  continue;
35
61
  }
36
- for (const byte of new TextEncoder().encode(char))
37
- bytes.push(byte);
38
- index += char.length;
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;
67
+ }
68
+ let decoded;
69
+ try {
70
+ decoded = segmentTextDecoder.decode(new Uint8Array(bytes));
71
+ }
72
+ catch {
73
+ throw new Error("Invalid OPFS path segment encoding");
74
+ }
75
+ if (encodeSegment(decoded) !== encoded) {
76
+ throw new Error("Non-canonical OPFS path segment encoding");
77
+ }
78
+ return decoded;
79
+ }
80
+ 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
+ }
39
88
  }
40
- return new TextDecoder().decode(new Uint8Array(bytes));
41
89
  }
42
90
  /**
43
91
  * A directory-handle cache plus the file operations the store needs. Handles are cached per
@@ -50,16 +98,30 @@ export class OpfsTree {
50
98
  constructor(root) {
51
99
  this.#root = root;
52
100
  }
101
+ /** @internal Hardening-test visibility for the cache's fixed-memory invariant. */
102
+ get directoryCacheSizeForTests() {
103
+ return this.#directories.size;
104
+ }
53
105
  async getDirectory(path, create) {
106
+ validatePath(path);
54
107
  if (path.length === 0)
55
108
  return this.#root;
56
109
  const key = path.join("/");
57
110
  const cached = this.#directories.get(key);
58
- if (cached !== undefined)
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);
59
115
  return cached;
116
+ }
60
117
  const parent = await this.getDirectory(path.slice(0, -1), create);
61
118
  const directory = await retryTransient(() => parent.getDirectoryHandle(path[path.length - 1] ?? "", { create }));
62
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
+ }
63
125
  return directory;
64
126
  }
65
127
  /** Drops cached handles at and under a path — call after deleting a directory. */
@@ -72,11 +134,19 @@ export class OpfsTree {
72
134
  }
73
135
  /** `undefined` when the file (or any parent) does not exist. */
74
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
+ }
75
142
  try {
76
143
  return await retryTransient(async () => {
77
144
  const directory = await this.getDirectory(path.slice(0, -1), false);
78
145
  const handle = await directory.getFileHandle(path[path.length - 1] ?? "");
79
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
+ }
80
150
  return new Uint8Array(await file.arrayBuffer());
81
151
  });
82
152
  }
@@ -98,17 +168,35 @@ export class OpfsTree {
98
168
  throw error;
99
169
  }
100
170
  }
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
+ }
187
+ }
101
188
  /**
102
189
  * Writes a whole file through an exclusive sync handle: truncate, write, optional flush,
103
190
  * close. Quota errors escape unwrapped. The handle is always closed, so a failed write never
104
191
  * leaves the file locked — only torn, which readers detect by checksum.
105
192
  */
106
193
  async writeFile(path, bytes, options = {}) {
194
+ validatePath(path);
107
195
  await retryTransient(async () => {
108
196
  const handle = await this.openHandle(path, { create: true });
109
197
  try {
110
198
  handle.truncate(0);
111
- handle.write(bytes, { at: 0 });
199
+ writeFully(handle, bytes, 0, `writing ${path.join("/")}`);
112
200
  if (options.flush === true)
113
201
  handle.flush();
114
202
  }
@@ -123,6 +211,7 @@ export class OpfsTree {
123
211
  * platform-specific type lives in the WebWorker lib, which not every consumer loads.
124
212
  */
125
213
  async openHandle(path, options) {
214
+ validatePath(path);
126
215
  return retryTransient(async () => {
127
216
  const directory = await this.getDirectory(path.slice(0, -1), options.create);
128
217
  const handle = await directory.getFileHandle(path[path.length - 1] ?? "", {
@@ -133,6 +222,7 @@ export class OpfsTree {
133
222
  }
134
223
  /** True when the file existed. Absence is not an error — deletes are idempotent. */
135
224
  async deleteFile(path) {
225
+ validatePath(path);
136
226
  try {
137
227
  await retryTransient(async () => {
138
228
  const directory = await this.getDirectory(path.slice(0, -1), false);
@@ -148,6 +238,7 @@ export class OpfsTree {
148
238
  }
149
239
  /** Removes a whole subtree; missing is fine. */
150
240
  async deleteTree(path) {
241
+ validatePath(path);
151
242
  try {
152
243
  await retryTransient(async () => {
153
244
  const directory = await this.getDirectory(path.slice(0, -1), false);
@@ -160,42 +251,40 @@ export class OpfsTree {
160
251
  }
161
252
  this.invalidate(path);
162
253
  }
163
- /** Names of the entries directly inside a directory; `[]` when it does not exist. */
164
- async listNames(path) {
254
+ /** Names directly inside a directory, streamed without retaining the directory cardinality. */
255
+ async *iterateNames(path) {
256
+ validatePath(path);
257
+ let directory;
165
258
  try {
166
- return await retryTransient(async () => {
167
- const directory = await this.getDirectory(path, false);
168
- const names = [];
169
- for await (const name of directory.keys())
170
- names.push(name);
171
- return names;
172
- });
259
+ directory = await this.getDirectory(path, false);
173
260
  }
174
261
  catch (error) {
175
262
  if (isDomError(error, "NotFoundError") || isDomError(error, "TypeMismatchError")) {
176
- return [];
263
+ return;
177
264
  }
178
265
  throw error;
179
266
  }
267
+ for await (const name of directory.keys())
268
+ yield name;
180
269
  }
181
- /** Every file under a directory, with sizes the open-time block index walk. */
182
- async walkFiles(path) {
183
- const results = [];
270
+ /** Every file under a directory, streamed with O(path depth) retained memory. */
271
+ async *walkFiles(path) {
272
+ validatePath(path);
184
273
  let root;
185
274
  try {
186
275
  root = await this.getDirectory(path, false);
187
276
  }
188
277
  catch (error) {
189
278
  if (isDomError(error, "NotFoundError"))
190
- return results;
279
+ return;
191
280
  throw error;
192
281
  }
193
- const walk = async (directory, prefix) => {
282
+ const walk = async function* (directory, prefix) {
194
283
  for await (const [name, entry] of directory) {
195
284
  if (entry.kind === "file") {
196
285
  try {
197
286
  const file = await entry.getFile();
198
- results.push({ path: [...prefix, name], size: file.size });
287
+ yield { path: [...prefix, name], size: file.size };
199
288
  }
200
289
  catch (error) {
201
290
  // A file another instance is writing or deleting this instant is not part of any
@@ -208,12 +297,11 @@ export class OpfsTree {
208
297
  }
209
298
  }
210
299
  else {
211
- await walk(entry, [...prefix, name]);
300
+ yield* walk(entry, [...prefix, name]);
212
301
  }
213
302
  }
214
303
  };
215
- await walk(root, []);
216
- return results;
304
+ yield* walk(root, []);
217
305
  }
218
306
  }
219
307
  export function isDomError(error, name) {
@@ -240,4 +328,3 @@ async function retryTransient(operation) {
240
328
  }
241
329
  }
242
330
  }
243
- //# sourceMappingURL=files.js.map
@@ -1,3 +1,3 @@
1
1
  export { OpfsBlockStore, deleteOpfsDatabase, type OpfsBlockStoreOptions } from "./store.js";
2
+ export { OpfsUncertainOutcomeError } from "../types.js";
2
3
  export { OpfsTree, type ReadFileOptions, type WriteFileOptions } from "./files.js";
3
- //# sourceMappingURL=index.d.ts.map
@@ -1,3 +1,3 @@
1
1
  export { OpfsBlockStore, deleteOpfsDatabase } from "./store.js";
2
+ export { OpfsUncertainOutcomeError } from "../types.js";
2
3
  export { OpfsTree } from "./files.js";
3
- //# sourceMappingURL=index.js.map