@weborigami/async-tree 0.5.8 → 0.6.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 (178) hide show
  1. package/browser.js +1 -1
  2. package/index.ts +31 -35
  3. package/main.js +1 -2
  4. package/package.json +4 -7
  5. package/shared.js +77 -12
  6. package/src/Tree.js +8 -2
  7. package/src/drivers/AsyncMap.js +210 -0
  8. package/src/drivers/{BrowserFileTree.js → BrowserFileMap.js} +36 -27
  9. package/src/drivers/{calendarTree.js → CalendarMap.js} +81 -62
  10. package/src/drivers/ConstantMap.js +30 -0
  11. package/src/drivers/DeepObjectMap.js +27 -0
  12. package/src/drivers/{ExplorableSiteTree.js → ExplorableSiteMap.js} +7 -7
  13. package/src/drivers/FileMap.js +245 -0
  14. package/src/drivers/{FunctionTree.js → FunctionMap.js} +19 -22
  15. package/src/drivers/ObjectMap.js +139 -0
  16. package/src/drivers/SetMap.js +13 -0
  17. package/src/drivers/{SiteTree.js → SiteMap.js} +16 -17
  18. package/src/drivers/SyncMap.js +245 -0
  19. package/src/jsonKeys.d.ts +2 -2
  20. package/src/jsonKeys.js +6 -5
  21. package/src/operations/addNextPrevious.js +35 -36
  22. package/src/operations/assign.js +30 -21
  23. package/src/operations/cache.js +29 -35
  24. package/src/operations/cachedKeyFunctions.js +1 -1
  25. package/src/operations/calendar.js +5 -0
  26. package/src/operations/clear.js +13 -12
  27. package/src/operations/constant.js +5 -0
  28. package/src/operations/deepEntries.js +23 -0
  29. package/src/operations/deepMap.js +9 -9
  30. package/src/operations/deepMerge.js +36 -25
  31. package/src/operations/deepReverse.js +23 -16
  32. package/src/operations/deepTake.js +7 -7
  33. package/src/operations/deepText.js +4 -4
  34. package/src/operations/deepValues.js +3 -6
  35. package/src/operations/deepValuesIterator.js +11 -11
  36. package/src/operations/delete.js +8 -12
  37. package/src/operations/entries.js +17 -10
  38. package/src/operations/filter.js +9 -7
  39. package/src/operations/first.js +12 -10
  40. package/src/operations/forEach.js +10 -13
  41. package/src/operations/from.js +31 -39
  42. package/src/operations/globKeys.js +22 -17
  43. package/src/operations/group.js +2 -2
  44. package/src/operations/groupBy.js +24 -22
  45. package/src/operations/has.js +7 -9
  46. package/src/operations/indent.js +2 -2
  47. package/src/operations/inners.js +19 -15
  48. package/src/operations/invokeFunctions.js +22 -10
  49. package/src/operations/isAsyncMutableTree.js +5 -12
  50. package/src/operations/isAsyncTree.js +5 -20
  51. package/src/operations/isMap.js +39 -0
  52. package/src/operations/isMaplike.js +34 -0
  53. package/src/operations/isReadOnlyMap.js +14 -0
  54. package/src/operations/isTraversable.js +3 -3
  55. package/src/operations/isTreelike.js +5 -30
  56. package/src/operations/json.js +4 -12
  57. package/src/operations/keys.js +17 -8
  58. package/src/operations/length.js +9 -8
  59. package/src/operations/map.js +27 -30
  60. package/src/operations/mapExtension.js +20 -16
  61. package/src/operations/mapReduce.js +22 -17
  62. package/src/operations/mask.js +31 -22
  63. package/src/operations/match.js +13 -9
  64. package/src/operations/merge.js +43 -35
  65. package/src/operations/paginate.js +26 -18
  66. package/src/operations/parent.js +7 -7
  67. package/src/operations/paths.js +8 -8
  68. package/src/operations/plain.js +6 -6
  69. package/src/operations/regExpKeys.js +21 -12
  70. package/src/operations/reverse.js +21 -15
  71. package/src/operations/root.js +6 -5
  72. package/src/operations/scope.js +31 -26
  73. package/src/operations/shuffle.js +23 -16
  74. package/src/operations/size.js +13 -0
  75. package/src/operations/sort.js +55 -40
  76. package/src/operations/sync.js +21 -0
  77. package/src/operations/take.js +23 -11
  78. package/src/operations/text.js +4 -4
  79. package/src/operations/toFunction.js +7 -7
  80. package/src/operations/traverse.js +4 -4
  81. package/src/operations/traverseOrThrow.js +13 -9
  82. package/src/operations/traversePath.js +2 -2
  83. package/src/operations/values.js +18 -9
  84. package/src/operations/withKeys.js +22 -16
  85. package/src/symbols.js +1 -0
  86. package/src/utilities/castArraylike.js +10 -2
  87. package/src/utilities/getMapArgument.js +38 -0
  88. package/src/utilities/getParent.js +2 -2
  89. package/src/utilities/isStringlike.js +7 -5
  90. package/src/utilities/setParent.js +7 -7
  91. package/src/utilities/toFunction.js +2 -2
  92. package/src/utilities/toPlainValue.js +22 -18
  93. package/test/SampleAsyncMap.js +34 -0
  94. package/test/browser/assert.js +20 -0
  95. package/test/browser/index.html +54 -21
  96. package/test/drivers/AsyncMap.test.js +119 -0
  97. package/test/drivers/{BrowserFileTree.test.js → BrowserFileMap.test.js} +42 -23
  98. package/test/drivers/{calendarTree.test.js → CalendarMap.test.js} +17 -19
  99. package/test/drivers/ConstantMap.test.js +15 -0
  100. package/test/drivers/DeepObjectMap.test.js +36 -0
  101. package/test/drivers/{ExplorableSiteTree.test.js → ExplorableSiteMap.test.js} +29 -14
  102. package/test/drivers/FileMap.test.js +185 -0
  103. package/test/drivers/FunctionMap.test.js +56 -0
  104. package/test/drivers/ObjectMap.test.js +166 -0
  105. package/test/drivers/SetMap.test.js +35 -0
  106. package/test/drivers/{SiteTree.test.js → SiteMap.test.js} +14 -10
  107. package/test/drivers/SyncMap.test.js +321 -0
  108. package/test/jsonKeys.test.js +2 -2
  109. package/test/operations/addNextPrevious.test.js +3 -2
  110. package/test/operations/assign.test.js +30 -35
  111. package/test/operations/cache.test.js +8 -6
  112. package/test/operations/cachedKeyFunctions.test.js +6 -5
  113. package/test/operations/clear.test.js +6 -27
  114. package/test/operations/deepEntries.test.js +32 -0
  115. package/test/operations/deepMerge.test.js +6 -5
  116. package/test/operations/deepReverse.test.js +2 -2
  117. package/test/operations/deepTake.test.js +2 -2
  118. package/test/operations/deepText.test.js +4 -4
  119. package/test/operations/deepValuesIterator.test.js +2 -2
  120. package/test/operations/delete.test.js +2 -2
  121. package/test/operations/extensionKeyFunctions.test.js +6 -5
  122. package/test/operations/filter.test.js +3 -3
  123. package/test/operations/from.test.js +23 -31
  124. package/test/operations/globKeys.test.js +9 -9
  125. package/test/operations/groupBy.test.js +6 -5
  126. package/test/operations/inners.test.js +4 -4
  127. package/test/operations/invokeFunctions.test.js +2 -2
  128. package/test/operations/isMap.test.js +15 -0
  129. package/test/operations/isMaplike.test.js +15 -0
  130. package/test/operations/json.test.js +2 -2
  131. package/test/operations/keys.test.js +16 -3
  132. package/test/operations/map.test.js +20 -18
  133. package/test/operations/mapExtension.test.js +6 -6
  134. package/test/operations/mapReduce.test.js +2 -2
  135. package/test/operations/mask.test.js +4 -3
  136. package/test/operations/match.test.js +2 -2
  137. package/test/operations/merge.test.js +15 -11
  138. package/test/operations/paginate.test.js +5 -5
  139. package/test/operations/parent.test.js +3 -3
  140. package/test/operations/paths.test.js +6 -6
  141. package/test/operations/plain.test.js +8 -8
  142. package/test/operations/regExpKeys.test.js +12 -11
  143. package/test/operations/reverse.test.js +4 -3
  144. package/test/operations/scope.test.js +6 -5
  145. package/test/operations/shuffle.test.js +3 -2
  146. package/test/operations/sort.test.js +7 -10
  147. package/test/operations/sync.test.js +43 -0
  148. package/test/operations/take.test.js +2 -2
  149. package/test/operations/toFunction.test.js +2 -2
  150. package/test/operations/traverse.test.js +4 -5
  151. package/test/operations/withKeys.test.js +2 -2
  152. package/test/utilities/setParent.test.js +6 -6
  153. package/test/utilities/toFunction.test.js +2 -2
  154. package/test/utilities/toPlainValue.test.js +51 -12
  155. package/src/drivers/DeepMapTree.js +0 -23
  156. package/src/drivers/DeepObjectTree.js +0 -18
  157. package/src/drivers/DeferredTree.js +0 -81
  158. package/src/drivers/FileTree.js +0 -276
  159. package/src/drivers/MapTree.js +0 -70
  160. package/src/drivers/ObjectTree.js +0 -158
  161. package/src/drivers/SetTree.js +0 -34
  162. package/src/drivers/constantTree.js +0 -19
  163. package/src/drivers/limitConcurrency.js +0 -63
  164. package/src/internal.js +0 -16
  165. package/src/utilities/getTreeArgument.js +0 -43
  166. package/test/drivers/DeepMapTree.test.js +0 -17
  167. package/test/drivers/DeepObjectTree.test.js +0 -35
  168. package/test/drivers/DeferredTree.test.js +0 -22
  169. package/test/drivers/FileTree.test.js +0 -192
  170. package/test/drivers/FunctionTree.test.js +0 -46
  171. package/test/drivers/MapTree.test.js +0 -59
  172. package/test/drivers/ObjectTree.test.js +0 -163
  173. package/test/drivers/SetTree.test.js +0 -44
  174. package/test/drivers/constantTree.test.js +0 -13
  175. package/test/drivers/limitConcurrency.test.js +0 -41
  176. package/test/operations/isAsyncMutableTree.test.js +0 -17
  177. package/test/operations/isAsyncTree.test.js +0 -26
  178. package/test/operations/isTreelike.test.js +0 -13
package/browser.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // Exports for browser
2
2
 
3
3
  export * from "./shared.js";
4
- export { default as BrowserFileTree } from "./src/drivers/BrowserFileTree.js";
4
+ export { default as BrowserFileMap } from "./src/drivers/BrowserFileMap.js";
package/index.ts CHANGED
@@ -1,10 +1,10 @@
1
- import type { AsyncTree } from "@weborigami/types";
1
+ import AsyncMap from "./src/drivers/AsyncMap.js";
2
2
 
3
3
  export * from "./main.js";
4
4
 
5
- export type Invocable = Function | Treelike | Unpackable;
5
+ export type Invocable = Function | Maplike | Unpackable;
6
6
 
7
- export type KeyFn = (key: any, innerTree: AsyncTree) => any;
7
+ export type KeyFn = (key: any, map: SyncOrAsyncMap) => any;
8
8
 
9
9
  /**
10
10
  * An object with a non-trivial `toString` method.
@@ -17,36 +17,23 @@ export type HasString = {
17
17
  toString(): string;
18
18
  };
19
19
 
20
- /**
21
- * A packed value is one that can be written to a file via fs.writeFile or into
22
- * an HTTP response via response.write, or readily converted to such a form.
23
- */
24
- export type Packed = (ArrayBuffer | Buffer | ReadableStream | string | String | TypedArray) & {
25
- parent?: AsyncTree|null;
26
- unpack?(): Promise<any>;
27
- };
28
-
29
- export type PlainObject = {
30
- [key: string]: any;
20
+ export type MapExtensionOptions = {
21
+ deep?: boolean;
22
+ description?: string;
23
+ extension?: string;
24
+ needsSourceValue?: boolean;
25
+ value?: ValueKeyFn;
31
26
  };
32
27
 
33
- export type ReduceFn = (values: any[], keys: any[], tree: AsyncTree) => Promise<any>;
34
-
35
- export type Stringlike = string | HasString;
36
-
37
- export type NativeTreelike =
28
+ export type Maplike =
38
29
  any[] |
39
- AsyncTree |
30
+ Iterator<any> |
40
31
  Function |
41
- Map<any, any> |
32
+ SyncOrAsyncMap |
42
33
  PlainObject |
43
34
  Set<any>;
44
35
 
45
- export type Treelike =
46
- NativeTreelike |
47
- Unpackable;
48
-
49
- export type TreeMapOptions = {
36
+ export type MapOptions = {
50
37
  deep?: boolean;
51
38
  description?: string;
52
39
  extension?: string;
@@ -56,15 +43,24 @@ export type TreeMapOptions = {
56
43
  value?: ValueKeyFn;
57
44
  };
58
45
 
59
- export type TreeMapExtensionOptions = {
60
- deep?: boolean;
61
- description?: string;
62
- extension?: string;
63
- needsSourceValue?: boolean;
64
- value?: ValueKeyFn;
46
+ /**
47
+ * A packed value is one that can be written to a file via fs.writeFile or into
48
+ * an HTTP response via response.write, or readily converted to such a form.
49
+ */
50
+ export type Packed = (ArrayBuffer | Buffer | ReadableStream | string | String | TypedArray) & {
51
+ parent?: SyncOrAsyncMap|null;
52
+ unpack?(): Promise<any>;
53
+ };
54
+
55
+ export type PlainObject = {
56
+ [key: string]: any;
65
57
  };
66
-
67
- export type TreeTransform = (treelike: Treelike) => AsyncTree;
58
+
59
+ export type ReduceFn = (values: any[], keys: any[], map: SyncOrAsyncMap) => any | Promise<any>;
60
+
61
+ export type Stringlike = string | HasString;
62
+
63
+ export type SyncOrAsyncMap = Map<any, any> | AsyncMap;
68
64
 
69
65
  export type TypedArray =
70
66
  Float32Array |
@@ -86,4 +82,4 @@ export type Unpackable = {
86
82
  */
87
83
  export type UnpackFunction = (input: Packed, options?: any) => any;
88
84
 
89
- export type ValueKeyFn = (value: any, key: any, innerTree: AsyncTree) => any;
85
+ export type ValueKeyFn = (value: any, key: any, map: SyncOrAsyncMap) => any;
package/main.js CHANGED
@@ -1,5 +1,4 @@
1
1
  // Exports for Node.js
2
2
 
3
3
  export * from "./shared.js";
4
- export { default as FileTree } from "./src/drivers/FileTree.js";
5
- export * as extension from "./src/extension.js";
4
+ export { default as FileMap } from "./src/drivers/FileMap.js";
package/package.json CHANGED
@@ -1,18 +1,15 @@
1
1
  {
2
2
  "name": "@weborigami/async-tree",
3
- "version": "0.5.8",
3
+ "version": "0.6.0",
4
4
  "description": "Asynchronous tree drivers based on standard JavaScript classes",
5
5
  "type": "module",
6
6
  "main": "./main.js",
7
7
  "browser": "./browser.js",
8
8
  "types": "./index.ts",
9
- "dependencies": {
10
- "@weborigami/types": "0.5.8"
11
- },
12
9
  "devDependencies": {
13
- "@types/node": "24.3.0",
14
- "puppeteer": "24.17.0",
15
- "typescript": "5.9.2"
10
+ "@types/node": "24.10.1",
11
+ "puppeteer": "24.30.0",
12
+ "typescript": "5.9.3"
16
13
  },
17
14
  "scripts": {
18
15
  "headlessTest": "node test/browser/headlessTest.js",
package/shared.js CHANGED
@@ -1,16 +1,18 @@
1
1
  // Exports for both Node.js and browser
2
2
 
3
- export { default as calendar } from "./src/drivers/calendarTree.js";
4
- export { default as constant } from "./src/drivers/constantTree.js";
5
- export { default as DeepMapTree } from "./src/drivers/DeepMapTree.js";
6
- export { default as DeepObjectTree } from "./src/drivers/DeepObjectTree.js";
7
- export { default as DeferredTree } from "./src/drivers/DeferredTree.js";
8
- export { default as ExplorableSiteTree } from "./src/drivers/ExplorableSiteTree.js";
9
- export { default as FunctionTree } from "./src/drivers/FunctionTree.js";
10
- export { default as MapTree } from "./src/drivers/MapTree.js";
11
- export { default as ObjectTree } from "./src/drivers/ObjectTree.js";
12
- export { default as SetTree } from "./src/drivers/SetTree.js";
13
- export { default as SiteTree } from "./src/drivers/SiteTree.js";
3
+ import { default as DeepObjectMap } from "./src/drivers/DeepObjectMap.js";
4
+ import { default as ExplorableSiteMap } from "./src/drivers/ExplorableSiteMap.js";
5
+ import { default as FileMap } from "./src/drivers/FileMap.js";
6
+ import { default as FunctionMap } from "./src/drivers/FunctionMap.js";
7
+ import { default as ObjectMap } from "./src/drivers/ObjectMap.js";
8
+ import { default as SetMap } from "./src/drivers/SetMap.js";
9
+ import { default as SiteMap } from "./src/drivers/SiteMap.js";
10
+
11
+ export { default as AsyncMap } from "./src/drivers/AsyncMap.js";
12
+ export { default as CalendarMap } from "./src/drivers/CalendarMap.js";
13
+ export { default as ConstantMap } from "./src/drivers/ConstantMap.js";
14
+ export { default as SyncMap } from "./src/drivers/SyncMap.js";
15
+ export * as extension from "./src/extension.js";
14
16
  export * as jsonKeys from "./src/jsonKeys.js";
15
17
  export { default as scope } from "./src/operations/scope.js";
16
18
  export * as symbols from "./src/symbols.js";
@@ -19,9 +21,9 @@ export { default as TraverseError } from "./src/TraverseError.js";
19
21
  export * as Tree from "./src/Tree.js";
20
22
  export { default as box } from "./src/utilities/box.js";
21
23
  export { default as castArraylike } from "./src/utilities/castArraylike.js";
24
+ export { default as getTreeArgument } from "./src/utilities/getMapArgument.js";
22
25
  export { default as getParent } from "./src/utilities/getParent.js";
23
26
  export { default as getRealmObjectPrototype } from "./src/utilities/getRealmObjectPrototype.js";
24
- export { default as getTreeArgument } from "./src/utilities/getTreeArgument.js";
25
27
  export { default as isPacked } from "./src/utilities/isPacked.js";
26
28
  export { default as isPlainObject } from "./src/utilities/isPlainObject.js";
27
29
  export { default as isStringlike } from "./src/utilities/isStringlike.js";
@@ -32,3 +34,66 @@ export { default as pathFromKeys } from "./src/utilities/pathFromKeys.js";
32
34
  export { default as setParent } from "./src/utilities/setParent.js";
33
35
  export { default as toPlainValue } from "./src/utilities/toPlainValue.js";
34
36
  export { default as toString } from "./src/utilities/toString.js";
37
+
38
+ export {
39
+ DeepObjectMap,
40
+ ExplorableSiteMap,
41
+ FileMap,
42
+ FunctionMap,
43
+ ObjectMap,
44
+ SetMap,
45
+ SiteMap,
46
+ };
47
+
48
+ export class ObjectTree extends ObjectMap {
49
+ constructor(...args) {
50
+ super(...args);
51
+ console.warn("ObjectTree is deprecated. Please use ObjectMap instead.");
52
+ }
53
+ }
54
+
55
+ export class DeepObjectTree extends DeepObjectMap {
56
+ constructor(...args) {
57
+ super(...args);
58
+ console.warn(
59
+ "DeepObjectTree is deprecated. Please use DeepObjectMap instead."
60
+ );
61
+ }
62
+ }
63
+
64
+ export class ExplorableSiteTree extends ExplorableSiteMap {
65
+ constructor(href) {
66
+ super(href);
67
+ console.warn(
68
+ "ExplorableSiteTree is deprecated. Please use ExplorableSiteMap instead."
69
+ );
70
+ }
71
+ }
72
+
73
+ export class FileTree extends FileMap {
74
+ constructor(...args) {
75
+ super(...args);
76
+ console.warn("FileTree is deprecated. Please use FileMap instead.");
77
+ }
78
+ }
79
+
80
+ export class FunctionTree extends FunctionMap {
81
+ constructor(...args) {
82
+ super(...args);
83
+ console.warn("FunctionTree is deprecated. Please use FunctionMap instead.");
84
+ }
85
+ }
86
+
87
+ export class SetTree extends SetMap {
88
+ constructor(set) {
89
+ super(set);
90
+ console.warn("SetTree is deprecated. Please use SetMap instead.");
91
+ }
92
+ }
93
+
94
+ export class SiteTree extends SiteMap {
95
+ constructor(...args) {
96
+ super(...args);
97
+ console.warn("SiteTree is deprecated. Please use SiteMap instead.");
98
+ }
99
+ }
package/src/Tree.js CHANGED
@@ -2,12 +2,13 @@
2
2
  * Collection of functions for working with async trees
3
3
  */
4
4
 
5
- export { default as calendar } from "./drivers/calendarTree.js";
6
- export { default as constant } from "./drivers/constantTree.js";
7
5
  export { default as addNextPrevious } from "./operations/addNextPrevious.js";
8
6
  export { default as assign } from "./operations/assign.js";
9
7
  export { default as cache } from "./operations/cache.js";
8
+ export { default as calendar } from "./operations/calendar.js";
10
9
  export { default as clear } from "./operations/clear.js";
10
+ export { default as constant } from "./operations/constant.js";
11
+ export { default as deepEntries } from "./operations/deepEntries.js";
11
12
  export { default as deepMap } from "./operations/deepMap.js";
12
13
  export { default as deepMerge } from "./operations/deepMerge.js";
13
14
  export { default as deepReverse } from "./operations/deepReverse.js";
@@ -30,6 +31,9 @@ export { default as inners } from "./operations/inners.js";
30
31
  export { default as invokeFunctions } from "./operations/invokeFunctions.js";
31
32
  export { default as isAsyncMutableTree } from "./operations/isAsyncMutableTree.js";
32
33
  export { default as isAsyncTree } from "./operations/isAsyncTree.js";
34
+ export { default as isMap } from "./operations/isMap.js";
35
+ export { default as isMaplike } from "./operations/isMaplike.js";
36
+ export { default as isReadOnlyMap } from "./operations/isReadOnlyMap.js";
33
37
  export { default as isTraversable } from "./operations/isTraversable.js";
34
38
  export { default as isTreelike } from "./operations/isTreelike.js";
35
39
  export { default as json } from "./operations/json.js";
@@ -50,7 +54,9 @@ export { default as reverse } from "./operations/reverse.js";
50
54
  export { default as root } from "./operations/root.js";
51
55
  export { default as scope } from "./operations/scope.js";
52
56
  export { default as shuffle } from "./operations/shuffle.js";
57
+ export { default as size } from "./operations/size.js";
53
58
  export { default as sort } from "./operations/sort.js";
59
+ export { default as sync } from "./operations/sync.js";
54
60
  export { default as take } from "./operations/take.js";
55
61
  export { default as text } from "./operations/text.js";
56
62
  export { default as toFunction } from "./operations/toFunction.js";
@@ -0,0 +1,210 @@
1
+ import * as trailingSlash from "../trailingSlash.js";
2
+
3
+ export default class AsyncMap {
4
+ /** @type {AsyncMap|null} */
5
+ _parent = null;
6
+
7
+ _readOnly;
8
+
9
+ [Symbol.asyncIterator]() {
10
+ return this.entries();
11
+ }
12
+
13
+ /**
14
+ * Remove all key/value entries from the map.
15
+ *
16
+ * This method invokes the `keys()` and `delete()` methods.
17
+ */
18
+ async clear() {
19
+ for await (const key of this.keys()) {
20
+ await this.delete(key);
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Removes the entry for the given key, return true if an entry was removed
26
+ * and false if there was no entry for the key.
27
+ *
28
+ * @param {any} key
29
+ * @returns {Promise<boolean>}
30
+ */
31
+ async delete(key) {
32
+ throw new Error("delete() not implemented");
33
+ }
34
+
35
+ static EMPTY = Symbol("EMPTY");
36
+
37
+ /**
38
+ * Returns a new `AsyncIterator` object that contains a two-member array of
39
+ * [key, value] for each element in the map in insertion order.
40
+ *
41
+ * This method invokes the `keys()` and `get()` methods.
42
+ *
43
+ * @returns {AsyncIterableIterator<[any, any]>}
44
+ */
45
+ async *entries() {
46
+ const keys = [];
47
+ const valuePromises = [];
48
+ // Invoke get() calls without waiting; some may take longer than others
49
+ for await (const key of this.keys()) {
50
+ keys.push(key);
51
+ valuePromises.push(this.get(key));
52
+ }
53
+ // Now wait for all promises to resolve
54
+ const values = await Promise.all(valuePromises);
55
+ for (let i = 0; i < keys.length; i++) {
56
+ yield [keys[i], values[i]];
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Calls `callback` once for each key/value pair in the map, in insertion order.
62
+ *
63
+ * This method invokes the `entries()` method.
64
+ *
65
+ * @param {(value: any, key: any, thisArg: any) => Promise<void>} callback
66
+ * @param {any?} thisArg
67
+ */
68
+ async forEach(callback, thisArg = this) {
69
+ for await (const [key, value] of this.entries()) {
70
+ await callback(value, key, thisArg);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Returns the value associated with the key, or undefined if there is none.
76
+ *
77
+ * @param {any} key
78
+ * @returns {Promise<any>}
79
+ */
80
+ async get(key) {
81
+ throw new Error("get() not implemented");
82
+ }
83
+
84
+ /**
85
+ * Groups items from an async iterable into an AsyncMap according to the keys
86
+ * returned by the given function.
87
+ *
88
+ * @param {Iterable<any>|AsyncIterable<any>} iterable
89
+ * @param {(element: any, index: any) => Promise<any>} keyFn
90
+ * @returns {Promise<Map>}
91
+ */
92
+ static async groupBy(iterable, keyFn) {
93
+ const map = new Map();
94
+ let index = 0;
95
+ for await (const element of iterable) {
96
+ const key = await keyFn(element, index);
97
+ if (!map.has(key)) {
98
+ map.set(key, []);
99
+ }
100
+ map.get(key).push(element);
101
+ index++;
102
+ }
103
+ return map;
104
+ }
105
+
106
+ /**
107
+ * Returns true if the given key appears in the set returned by keys().
108
+ *
109
+ * It doesn't matter whether the value returned by get() is defined or not.
110
+ *
111
+ * If the requested key has a trailing slash but has no associated value, but
112
+ * the alternate form with a slash does appear, this returns true.
113
+ *
114
+ * @param {any} key
115
+ */
116
+ async has(key) {
117
+ const alternateKey = !trailingSlash.has(key)
118
+ ? trailingSlash.add(key)
119
+ : null;
120
+ for await (const k of this.keys()) {
121
+ if (k === key) {
122
+ return true;
123
+ }
124
+ if (alternateKey && k === alternateKey) {
125
+ return true;
126
+ }
127
+ }
128
+ return false;
129
+ }
130
+
131
+ /**
132
+ * Returns a new `AsyncIterator` object that contains the keys for each
133
+ * element in the map in insertion order.
134
+ *
135
+ * @returns {AsyncIterableIterator<any>}
136
+ */
137
+ async *keys() {
138
+ throw new Error("keys() not implemented");
139
+ }
140
+
141
+ /**
142
+ * The parent of this node in a tree.
143
+ */
144
+ get parent() {
145
+ return this._parent;
146
+ }
147
+ set parent(parent) {
148
+ this._parent = parent;
149
+ }
150
+
151
+ /**
152
+ * True if the object is read-only. This will be true if `get()` has been
153
+ * overridden but `set()` and `delete()` have not.
154
+ */
155
+ get readOnly() {
156
+ return (
157
+ this.get !== AsyncMap.prototype.get &&
158
+ (this.set === AsyncMap.prototype.set ||
159
+ this.delete === AsyncMap.prototype.delete)
160
+ );
161
+ }
162
+
163
+ /**
164
+ * Sets the value for the given key.
165
+ *
166
+ * @param {any} key
167
+ * @param {any} value
168
+ * @returns {Promise<AsyncMap>}
169
+ */
170
+ async set(key, value) {
171
+ throw new Error("set() not implemented");
172
+ }
173
+
174
+ /**
175
+ * Returns the number of keys in the map.
176
+ *
177
+ * The `size` property invokes an overridden `keys()` to ensure proper
178
+ * behavior in subclasses. Because a subclass may not enforce a direct
179
+ * correspondence between `keys()` and `get()`, the size may not reflect the
180
+ * number of values that can be retrieved.
181
+ *
182
+ * @type {Promise<number>}
183
+ */
184
+ get size() {
185
+ return (async () => {
186
+ let count = 0;
187
+ for await (const _ of this.keys()) {
188
+ count++;
189
+ }
190
+ return count;
191
+ })();
192
+ }
193
+
194
+ /**
195
+ * Returns a new `AsyncIterator` object that contains the values for each
196
+ * element in the map in insertion order.
197
+ *
198
+ * @returns {AsyncIterableIterator<any>}
199
+ */
200
+ async *values() {
201
+ const valuePromises = [];
202
+ // Invoke get() calls without waiting; some may take longer than others
203
+ for await (const key of this.keys()) {
204
+ valuePromises.push(this.get(key));
205
+ }
206
+ // Now wait for all promises to resolve
207
+ const values = await Promise.all(valuePromises);
208
+ yield* values;
209
+ }
210
+ }
@@ -1,24 +1,22 @@
1
1
  import { hiddenFileNames } from "../constants.js";
2
2
  import assign from "../operations/assign.js";
3
- import isTreelike from "../operations/isTreelike.js";
3
+ import isMaplike from "../operations/isMaplike.js";
4
4
  import * as trailingSlash from "../trailingSlash.js";
5
5
  import isStringlike from "../utilities/isStringlike.js";
6
6
  import naturalOrder from "../utilities/naturalOrder.js";
7
7
  import setParent from "../utilities/setParent.js";
8
+ import AsyncMap from "./AsyncMap.js";
8
9
 
9
10
  const TypedArray = Object.getPrototypeOf(Uint8Array);
10
11
 
11
12
  /**
12
- * A tree of files backed by a browser-hosted file system such as the standard
13
+ * A map of files backed by a browser-hosted file system such as the standard
13
14
  * Origin Private File System or the (as of October 2023) experimental File
14
15
  * System Access API.
15
- *
16
- * @typedef {import("@weborigami/types").AsyncMutableTree} AsyncMutableTree
17
- * @implements {AsyncMutableTree}
18
16
  */
19
- export default class BrowserFileTree {
17
+ export default class BrowserFileMap extends AsyncMap {
20
18
  /**
21
- * Construct a tree of files backed by a browser-hosted file system.
19
+ * Construct a map of files backed by a browser-hosted file system.
22
20
  *
23
21
  * The directory handle can be obtained via any of the [methods that return a
24
22
  * FileSystemDirectoryHandle](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryHandle).
@@ -28,11 +26,28 @@ export default class BrowserFileTree {
28
26
  * @param {FileSystemDirectoryHandle} [directoryHandle]
29
27
  */
30
28
  constructor(directoryHandle) {
31
- /** @type {FileSystemDirectoryHandle}
32
- * @ts-ignore */
29
+ super();
33
30
  this.directory = directoryHandle;
34
31
  }
35
32
 
33
+ async delete(key) {
34
+ const baseKey = trailingSlash.remove(key);
35
+ const directory = await this.getDirectory();
36
+
37
+ // Delete file.
38
+ try {
39
+ await directory.removeEntry(baseKey);
40
+ } catch (error) {
41
+ // If the file didn't exist, ignore the error.
42
+ if (error instanceof DOMException && error.name === "NotFoundError") {
43
+ return false;
44
+ }
45
+ throw error;
46
+ }
47
+
48
+ return true;
49
+ }
50
+
36
51
  async get(key) {
37
52
  if (key == null) {
38
53
  // Reject nullish key.
@@ -90,7 +105,7 @@ export default class BrowserFileTree {
90
105
  return this.directory;
91
106
  }
92
107
 
93
- async keys() {
108
+ async *keys() {
94
109
  const directory = await this.getDirectory();
95
110
  let keys = [];
96
111
  // @ts-ignore
@@ -110,28 +125,13 @@ export default class BrowserFileTree {
110
125
  keys = keys.filter((key) => !hiddenFileNames.includes(key));
111
126
  keys.sort(naturalOrder);
112
127
 
113
- return keys;
128
+ yield* keys;
114
129
  }
115
130
 
116
131
  async set(key, value) {
117
132
  const baseKey = trailingSlash.remove(key);
118
133
  const directory = await this.getDirectory();
119
134
 
120
- if (value === undefined) {
121
- // Delete file.
122
- try {
123
- await directory.removeEntry(baseKey);
124
- } catch (error) {
125
- // If the file didn't exist, ignore the error.
126
- if (
127
- !(error instanceof DOMException && error.name === "NotFoundError")
128
- ) {
129
- throw error;
130
- }
131
- }
132
- return this;
133
- }
134
-
135
135
  // Treat null value as empty string; will create an empty file.
136
136
  if (value === null) {
137
137
  value = "";
@@ -158,7 +158,12 @@ export default class BrowserFileTree {
158
158
  const writable = await fileHandle.createWritable();
159
159
  await writable.write(value);
160
160
  await writable.close();
161
- } else if (isTreelike(value)) {
161
+ } else if (value === /** @type {any} */ (this.constructor).EMPTY) {
162
+ // Create empty subtree.
163
+ await directory.getDirectoryHandle(baseKey, {
164
+ create: true,
165
+ });
166
+ } else if (isMaplike(value)) {
162
167
  // Treat value as a tree and write it out as a subdirectory.
163
168
  const subdirectory = await directory.getDirectoryHandle(baseKey, {
164
169
  create: true,
@@ -172,4 +177,8 @@ export default class BrowserFileTree {
172
177
 
173
178
  return this;
174
179
  }
180
+
181
+ get trailingSlashKeys() {
182
+ return true;
183
+ }
175
184
  }