@weborigami/language 0.7.0-beta.1 → 0.7.0-beta.3

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 (43) hide show
  1. package/main.js +1 -0
  2. package/package.json +3 -2
  3. package/src/compiler/compile.js +0 -3
  4. package/src/compiler/optimize.js +56 -23
  5. package/src/compiler/parserHelpers.js +11 -8
  6. package/src/handlers/addExtensionKeyFn.js +18 -0
  7. package/src/handlers/epub_handler.js +54 -0
  8. package/src/handlers/getSource.js +1 -0
  9. package/src/handlers/handlers.js +2 -0
  10. package/src/handlers/ori_handler.js +2 -2
  11. package/src/handlers/oridocument_handler.js +5 -21
  12. package/src/handlers/{processOriExport.js → processOrigamiExport.js} +1 -1
  13. package/src/handlers/zip_handler.js +112 -0
  14. package/src/runtime/AsyncCacheTransform.d.ts +5 -1
  15. package/src/runtime/AsyncCacheTransform.js +22 -25
  16. package/src/runtime/OrigamiFileMap.js +3 -3
  17. package/src/runtime/SyncCacheTransform.d.ts +2 -1
  18. package/src/runtime/SyncCacheTransform.js +31 -34
  19. package/src/runtime/SystemCacheMap.js +28 -17
  20. package/src/runtime/counters.js +3 -0
  21. package/src/runtime/enableValueCaching.js +8 -7
  22. package/src/runtime/errors.js +33 -1
  23. package/src/runtime/execute.js +3 -0
  24. package/src/runtime/explainReferenceError.js +11 -24
  25. package/src/runtime/explainTraverseError.js +15 -6
  26. package/src/runtime/expressionObject.js +53 -34
  27. package/src/runtime/handleExtension.js +18 -9
  28. package/src/runtime/ops.js +4 -3
  29. package/test/compiler/compile.test.js +0 -1
  30. package/test/compiler/optimize.test.js +60 -15
  31. package/test/compiler/parse.test.js +38 -9
  32. package/test/handlers/epub_handler.test.js +27 -0
  33. package/test/handlers/fixtures/test.zip +0 -0
  34. package/test/handlers/zip_handler.test.js +45 -0
  35. package/test/runtime/AsyncCacheTransform.test.js +9 -4
  36. package/test/runtime/SyncCacheTransform.test.js +4 -0
  37. package/test/runtime/asyncCalcs.js +26 -4
  38. package/test/runtime/errors.test.js +4 -4
  39. package/test/runtime/expressionObject.test.js +23 -12
  40. package/test/runtime/handleExtension.test.js +0 -2
  41. package/test/runtime/ops.test.js +5 -3
  42. package/test/runtime/syncCalcs.js +26 -3
  43. package/test/runtime/systemCache.test.js +4 -0
package/main.js CHANGED
@@ -15,6 +15,7 @@ export { default as projectRoot } from "./src/project/projectRoot.js";
15
15
  export { default as projectRootFromPath } from "./src/project/projectRootFromPath.js";
16
16
  export { default as fetchAndHandleExtension } from "./src/protocols/fetchAndHandleExtension.js";
17
17
  export * as Protocols from "./src/protocols/protocols.js";
18
+ export { default as counters } from "./src/runtime/counters.js";
18
19
  export { formatError, highlightError, lineInfo } from "./src/runtime/errors.js";
19
20
  export { default as evaluate } from "./src/runtime/evaluate.js";
20
21
  export { default as EventTargetMixin } from "./src/runtime/EventTargetMixin.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/language",
3
- "version": "0.7.0-beta.1",
3
+ "version": "0.7.0-beta.3",
4
4
  "description": "Web Origami expression language compiler and runtime",
5
5
  "type": "module",
6
6
  "main": "./main.js",
@@ -11,7 +11,8 @@
11
11
  "typescript": "6.0.3"
12
12
  },
13
13
  "dependencies": {
14
- "@weborigami/async-tree": "0.7.0-beta.1",
14
+ "@weborigami/async-tree": "0.7.0-beta.3",
15
+ "adm-zip": "0.5.17",
15
16
  "exif-parser": "0.1.12",
16
17
  "watcher": "2.3.1",
17
18
  "yaml": "2.9.0"
@@ -1,4 +1,3 @@
1
- import { trailingSlash } from "@weborigami/async-tree";
2
1
  import { createExpressionFunction } from "../runtime/expressionFunction.js";
3
2
  import optimize from "./optimize.js";
4
3
  import { parse } from "./parse.js";
@@ -33,13 +32,11 @@ function compile(source, options) {
33
32
 
34
33
  // Select a path the code will use for caching
35
34
  const cachePath = source.cachePath ?? `_compile${count++}`;
36
- const objectCachePath = cachePath ? trailingSlash.add(cachePath) : null;
37
35
 
38
36
  // Optimize the code
39
37
  const optimized = optimize(code, {
40
38
  cachePath,
41
39
  globals,
42
- objectCachePath,
43
40
  });
44
41
 
45
42
  // Create a function that executes the optimized code.
@@ -1,4 +1,4 @@
1
- import { pathFromKeys, trailingSlash } from "@weborigami/async-tree";
1
+ import { trailingSlash } from "@weborigami/async-tree";
2
2
  import jsGlobals from "../project/jsGlobals.js";
3
3
  import {
4
4
  KEY_TYPE,
@@ -31,9 +31,13 @@ export const REFERENCE_EXTERNAL = 4;
31
31
  * @returns {AnnotatedCode}
32
32
  */
33
33
  export default function optimize(code, options = {}) {
34
- // Cache path for this source file
35
- const cachePath = options.cachePath ?? null;
36
34
  const globals = options.globals ?? jsGlobals;
35
+ const cachePath = options.cachePath; // top-level cache path for this code
36
+ // Cache path for attached objects
37
+ const objectCachePath =
38
+ options.objectCachePath ??
39
+ (cachePath ? trailingSlash.add(cachePath) : null);
40
+ const attached = options.attached ?? true;
37
41
 
38
42
  // The locals is an array, one item for each function or object context that
39
43
  // has been entered. The array grows to the right. Array items are objects
@@ -66,7 +70,8 @@ export default function optimize(code, options = {}) {
66
70
  return inlineLiteral(code);
67
71
 
68
72
  case ops.object:
69
- const propertyNames = getPropertyNames(args);
73
+ const entries = args.slice(1);
74
+ const propertyNames = getPropertyNames(entries);
70
75
  locals.push({
71
76
  type: REFERENCE_INHERITED,
72
77
  names: propertyNames,
@@ -80,14 +85,24 @@ export default function optimize(code, options = {}) {
80
85
  if (op === ops.object) {
81
86
  if (index === 0) {
82
87
  return child; // return op as is
88
+ } else if (index === 1) {
89
+ // Replace null cache path with actual value
90
+ return objectCachePath;
83
91
  } else {
84
92
  // Object entry
85
93
  const [key, value] = child;
86
94
  const adjustedLocals = avoidLocalRecursion(locals, key);
87
95
  const isStringKey = typeof key === "string";
96
+ const cachePathKey = isStringKey
97
+ ? baseKeyName(key)
98
+ : `_arg${index - 2}`;
99
+ const childObjectCachePath = attached
100
+ ? SystemCacheMap.joinPath(objectCachePath, cachePathKey + "/")
101
+ : objectCachePath;
88
102
  const childOptions = {
89
103
  ...options,
90
104
  locals: adjustedLocals,
105
+ objectCachePath: childObjectCachePath,
91
106
  };
92
107
  const optimizedKey = isStringKey
93
108
  ? key
@@ -98,12 +113,30 @@ export default function optimize(code, options = {}) {
98
113
  );
99
114
  return annotate([optimizedKey, optimizedValue], child.location);
100
115
  }
116
+ } else if (op === ops.getter) {
117
+ if (index === 0) {
118
+ return child; // return op as is
119
+ } else {
120
+ // Already set up to optimize the (single) child
121
+ return optimize(/** @type {AnnotatedCode} */ (child), options);
122
+ }
101
123
  } else if (Array.isArray(child) && "location" in child) {
102
124
  // Review: Aside from ops.object (above), what non-instruction arrays
103
125
  // does this descend into?
104
- const childOptions = { ...options, locals };
105
- // Objects below here are detached from top-level object; not cached
106
- delete childOptions.objectCachePath;
126
+ let childObjectCachePath = objectCachePath;
127
+ if (attached) {
128
+ // Objects below this point are detached, use `_objects` cache path
129
+ childObjectCachePath = SystemCacheMap.joinPath(
130
+ objectCachePath,
131
+ `_objects/`,
132
+ );
133
+ }
134
+ const childOptions = {
135
+ ...options,
136
+ attached: false,
137
+ locals,
138
+ objectCachePath: childObjectCachePath,
139
+ };
107
140
  return optimize(/** @type {AnnotatedCode} */ (child), childOptions);
108
141
  } else {
109
142
  return child;
@@ -124,23 +157,20 @@ export default function optimize(code, options = {}) {
124
157
  // remove any local variable with that name from the stack of locals to avoid a
125
158
  // recursive reference.
126
159
  function avoidLocalRecursion(locals, key) {
127
- if (key[0] === "(" && key[key.length - 1] === ")") {
128
- // Non-enumerable property, remove parentheses
129
- key = key.slice(1, -1);
130
- }
131
-
132
160
  const currentFrameIndex = locals.length - 1;
133
161
  if (locals[currentFrameIndex]?.type !== REFERENCE_INHERITED) {
134
162
  // Not an inherited context, nothing to do
135
163
  return locals;
136
164
  }
137
165
 
166
+ const baseKey = baseKeyName(key);
167
+
138
168
  // See if the key matches any of the local variable names in the current
139
169
  // context (ignoring trailing slashes)
140
170
  const matchingKeyIndex = locals[currentFrameIndex].names.findIndex(
141
171
  (name) =>
142
172
  // Ignore trailing slashes when comparing keys
143
- trailingSlash.remove(name) === trailingSlash.remove(key),
173
+ trailingSlash.remove(name) === trailingSlash.remove(baseKey),
144
174
  );
145
175
 
146
176
  if (matchingKeyIndex >= 0) {
@@ -158,11 +188,12 @@ function avoidLocalRecursion(locals, key) {
158
188
  }
159
189
  }
160
190
 
161
- function cacheExternalPath(code, cachePath) {
162
- const keys = code.map(keyFromCode).filter((key) => key !== null);
163
- const keysPath = pathFromKeys(keys);
164
- const refCachePath = SystemCacheMap.joinPath(cachePath, "_refs", keysPath);
165
- return annotate([ops.cache, refCachePath, code], code.location);
191
+ function baseKeyName(key) {
192
+ if (key[0] === "(" && key[key.length - 1] === ")") {
193
+ // Non-enumerable property, remove parentheses
194
+ key = key.slice(1, -1);
195
+ }
196
+ return key;
166
197
  }
167
198
 
168
199
  // A reference with periods like x.y.z
@@ -366,6 +397,13 @@ function resolvePath(code, globals, locals, cachePath) {
366
397
 
367
398
  let { type, result } = reference(head, globals, locals);
368
399
 
400
+ if (type === REFERENCE_EXTERNAL && cachePath) {
401
+ // External references should be cached
402
+ const headKey = keyFromCode(head);
403
+ const refCachePath = SystemCacheMap.joinPath(cachePath, "_refs", headKey);
404
+ result = annotate([ops.cache, refCachePath, result], head.location);
405
+ }
406
+
369
407
  if (tail.length > 0) {
370
408
  // If the result is a traversal, we can safely extend it
371
409
  const extendResult =
@@ -383,10 +421,5 @@ function resolvePath(code, globals, locals, cachePath) {
383
421
  }
384
422
  }
385
423
 
386
- if (type === REFERENCE_EXTERNAL && cachePath) {
387
- // External references should be cached
388
- return cacheExternalPath(result, cachePath);
389
- }
390
-
391
424
  return result;
392
425
  }
@@ -272,7 +272,7 @@ export function makeDocument(front, body, location) {
272
272
  entries.push(annotate(["_body", body], location));
273
273
 
274
274
  // Return the code for the document object
275
- return annotate([ops.object, ...entries], location);
275
+ return annotate([ops.object, null, ...entries], location);
276
276
  }
277
277
 
278
278
  /**
@@ -341,7 +341,7 @@ function makeMerge(spreads, location) {
341
341
 
342
342
  for (const spread of spreads) {
343
343
  if (spread[0] === ops.object) {
344
- const spreadEntries = spread.slice(1);
344
+ const spreadEntries = spread.slice(2);
345
345
  topEntries.push(...spreadEntries);
346
346
  // Also add an object to the result with indirect references
347
347
  const indirectEntries = spreadEntries.map((entry) => {
@@ -352,7 +352,7 @@ function makeMerge(spreads, location) {
352
352
  return annotate([key, getter], entry.location);
353
353
  });
354
354
  const indirectObject = annotate(
355
- [ops.object, ...indirectEntries],
355
+ [ops.object, null, ...indirectEntries],
356
356
  location,
357
357
  );
358
358
  resultEntries.push(indirectObject);
@@ -368,7 +368,7 @@ function makeMerge(spreads, location) {
368
368
  topEntries.push(annotate(["_result", result], location));
369
369
 
370
370
  // Construct the top-level object
371
- const topObject = annotate([ops.object, ...topEntries], location);
371
+ const topObject = annotate([ops.object, null, ...topEntries], location);
372
372
 
373
373
  // Get the _result property
374
374
  const code = annotate([topObject, "_result"], location);
@@ -390,14 +390,17 @@ export function makeObject(entries, location) {
390
390
  if (key === markers.spread) {
391
391
  if (value[0] === ops.object) {
392
392
  // Spread of an object; fold into current object
393
- const spreadEntries = value.slice(1);
393
+ const spreadEntries = value.slice(2);
394
394
  currentEntries.push(...spreadEntries);
395
395
  } else {
396
396
  // Spread of a tree; accumulate
397
397
  if (currentEntries.length > 0) {
398
398
  const location = { ...currentEntries[0].location };
399
399
  location.end = currentEntries[currentEntries.length - 1].location.end;
400
- const spread = annotate([ops.object, ...currentEntries], location);
400
+ const spread = annotate(
401
+ [ops.object, null, ...currentEntries],
402
+ location,
403
+ );
401
404
  spreads.push(spread);
402
405
  currentEntries = [];
403
406
  }
@@ -424,7 +427,7 @@ export function makeObject(entries, location) {
424
427
  if (currentEntries.length > 0) {
425
428
  const location = { ...currentEntries[0].location };
426
429
  location.end = currentEntries[currentEntries.length - 1].location.end;
427
- const spread = annotate([ops.object, ...currentEntries], location);
430
+ const spread = annotate([ops.object, null, ...currentEntries], location);
428
431
  spreads.push(spread);
429
432
  currentEntries = [];
430
433
  }
@@ -442,7 +445,7 @@ export function makeObject(entries, location) {
442
445
  }
443
446
  } else {
444
447
  // Empty object
445
- code = [ops.object];
448
+ code = [ops.object, null];
446
449
  }
447
450
 
448
451
  return annotate(code, location);
@@ -0,0 +1,18 @@
1
+ import { extension, trailingSlash } from "@weborigami/async-tree";
2
+
3
+ // Return a function that adds the given extension
4
+ export default function addExtensionKeyFn(resultExtension) {
5
+ const keyFn = (sourceValue, sourceKey) => {
6
+ if (sourceKey === undefined) {
7
+ return undefined;
8
+ }
9
+ const normalizedKey = trailingSlash.remove(sourceKey);
10
+ const sourceExtension = extension.extname(normalizedKey);
11
+ const resultKey = sourceExtension
12
+ ? extension.replace(normalizedKey, sourceExtension, resultExtension)
13
+ : normalizedKey + resultExtension;
14
+ return resultKey;
15
+ };
16
+ keyFn.needsSourceValue = false;
17
+ return keyFn;
18
+ }
@@ -0,0 +1,54 @@
1
+ import { AsyncMap, isUnpackable, Tree } from "@weborigami/async-tree";
2
+ import addExtensionKeyFn from "./addExtensionKeyFn.js";
3
+ import zip_handler from "./zip_handler.js";
4
+
5
+ /**
6
+ * Handler for EPUB files
7
+ */
8
+ const epub_handler = {
9
+ mediaType: "application/epub+zip",
10
+
11
+ /**
12
+ * Package a tree of files as an EPUB file in Buffer form.
13
+ *
14
+ * This calls the pack() method for ZIP files, but ensures the `mimetype` file
15
+ * is the first file in the package -- a requirement for EPUB files.
16
+ *
17
+ * @param {import("@weborigami/async-tree").Maplike} maplike
18
+ */
19
+ async pack(maplike) {
20
+ if (isUnpackable(maplike)) {
21
+ maplike = await maplike.unpack();
22
+ }
23
+ const tree = Tree.from(maplike, { deep: true });
24
+ return zip_handler.pack(mimetypeFirst(tree));
25
+ },
26
+
27
+ async unpack(buffer, options) {
28
+ return zip_handler.unpack(buffer, options);
29
+ },
30
+ };
31
+
32
+ /** @type {any} */ (epub_handler.pack).key = addExtensionKeyFn(".epub");
33
+
34
+ export default epub_handler;
35
+
36
+ // A tree with its `mimetype` file first
37
+ function mimetypeFirst(tree) {
38
+ return Object.assign(new AsyncMap(), {
39
+ async get(key) {
40
+ return tree.get(key);
41
+ },
42
+
43
+ async *keys() {
44
+ const keys = await Tree.keys(tree);
45
+ // Move `mimetype` (if present) to the front of the list.
46
+ const index = keys.indexOf("mimetype");
47
+ if (index >= 0) {
48
+ keys.splice(index, 1);
49
+ keys.unshift("mimetype");
50
+ }
51
+ yield* keys;
52
+ },
53
+ });
54
+ }
@@ -30,6 +30,7 @@ export default function getSource(packed, options = {}) {
30
30
  const parentPath = /** @type {any} */ (parent).path;
31
31
  cachePath = SystemCacheMap.joinPath(parentPath, sourceName);
32
32
  } else {
33
+ // TODO: Avoid cache path collisions in this case
33
34
  cachePath = sourceName;
34
35
  }
35
36
  }
@@ -20,6 +20,7 @@ export { default as txt_handler } from "./txt_handler.js";
20
20
 
21
21
  export { default as css_handler } from "./css_handler.js";
22
22
  export { default as csv_handler } from "./csv_handler.js";
23
+ export { default as epub_handler } from "./epub_handler.js";
23
24
  export { default as htm_handler } from "./htm_handler.js";
24
25
  export { default as html_handler } from "./html_handler.js";
25
26
  export { default as jpeg_handler } from "./jpeg_handler.js";
@@ -33,3 +34,4 @@ export { default as wasm_handler } from "./wasm_handler.js";
33
34
  export { default as xhtml_handler } from "./xhtml_handler.js";
34
35
  export { default as yaml_handler } from "./yaml_handler.js";
35
36
  export { default as yml_handler } from "./yml_handler.js";
37
+ export { default as zip_handler } from "./zip_handler.js";
@@ -3,7 +3,7 @@ import * as compile from "../compiler/compile.js";
3
3
  import coreGlobals from "../project/coreGlobals.js";
4
4
  import getGlobalsForTree from "../project/getGlobalsForTree.js";
5
5
  import getSource from "./getSource.js";
6
- import processOriExport from "./processOriExport.js";
6
+ import processOrigamiExport from "./processOrigamiExport.js";
7
7
 
8
8
  /**
9
9
  * An Origami expression file
@@ -31,7 +31,7 @@ export default {
31
31
  // Evaluate the program
32
32
  let result = await fn();
33
33
 
34
- result = processOriExport(result, source, parent);
34
+ result = processOrigamiExport(result, source, parent);
35
35
 
36
36
  return result;
37
37
  },
@@ -1,9 +1,10 @@
1
- import { extension, getParent, trailingSlash } from "@weborigami/async-tree";
1
+ import { extension, getParent } from "@weborigami/async-tree";
2
2
  import * as compile from "../compiler/compile.js";
3
3
  import coreGlobals from "../project/coreGlobals.js";
4
4
  import getGlobalsForTree from "../project/getGlobalsForTree.js";
5
+ import addExtensionKeyFn from "./addExtensionKeyFn.js";
5
6
  import getSource from "./getSource.js";
6
- import processOriExport from "./processOriExport.js";
7
+ import processOrigamiExport from "./processOrigamiExport.js";
7
8
 
8
9
  /**
9
10
  * An Origami template document: a plain text file that contains Origami
@@ -30,34 +31,17 @@ export default {
30
31
  // Invoke the definition to get back the template function or object
31
32
  let result = await fn();
32
33
 
33
- result = processOriExport(result, source, parent);
34
+ result = processOrigamiExport(result, source, parent);
34
35
 
35
36
  if (result instanceof Function) {
36
37
  const key = options.key;
37
38
  const resultExtension = key ? extension.extname(key) : null;
38
39
  if (resultExtension && Object.isExtensible(result)) {
39
40
  // Add sidecar function so this template can be used in a map.
40
- result.key = addExtension(resultExtension);
41
+ result.key = addExtensionKeyFn(resultExtension);
41
42
  }
42
43
  }
43
44
 
44
45
  return result;
45
46
  },
46
47
  };
47
-
48
- // Return a function that adds the given extension
49
- function addExtension(resultExtension) {
50
- const keyFn = (sourceValue, sourceKey) => {
51
- if (sourceKey === undefined) {
52
- return undefined;
53
- }
54
- const normalizedKey = trailingSlash.remove(sourceKey);
55
- const sourceExtension = extension.extname(normalizedKey);
56
- const resultKey = sourceExtension
57
- ? extension.replace(normalizedKey, sourceExtension, resultExtension)
58
- : normalizedKey + resultExtension;
59
- return resultKey;
60
- };
61
- keyFn.needsSourceValue = false;
62
- return keyFn;
63
- }
@@ -5,7 +5,7 @@ import enableValueCaching from "../runtime/enableValueCaching.js";
5
5
  * Given an object that's the top-level result of an Origami file, perform any
6
6
  * necessary processing.
7
7
  */
8
- export default function processOriExport(value, source, parent) {
8
+ export default function processOrigamiExport(value, source, parent) {
9
9
  setParent(value, parent);
10
10
 
11
11
  if (source.cachePath) {
@@ -0,0 +1,112 @@
1
+ import { isUnpackable, SyncMap, Tree } from "@weborigami/async-tree";
2
+ import {
3
+ getGlobalsForTree,
4
+ HandleExtensionsTransform,
5
+ } from "@weborigami/language";
6
+ import Zip from "adm-zip";
7
+ import addExtensionKeyFn from "./addExtensionKeyFn.js";
8
+
9
+ /**
10
+ * Handler for ZIP files
11
+ */
12
+ const zip_handler = {
13
+ mediaType: "application/zip",
14
+
15
+ /**
16
+ * Pack a tree of files as a ZIP file in Buffer form.
17
+ *
18
+ * @param {import("@weborigami/async-tree").Maplike} maplike
19
+ */
20
+ async pack(maplike) {
21
+ // The ZIP file should leave the files in tree order.
22
+ const zip = new Zip({ noSort: true });
23
+
24
+ if (isUnpackable(maplike)) {
25
+ maplike = await maplike.unpack();
26
+ }
27
+ const tree = Tree.from(maplike, { deep: true });
28
+ const deflated = await Tree.deflatePaths(tree);
29
+ for (let [path, value] of deflated) {
30
+ if (typeof value === "function") {
31
+ value = value();
32
+ }
33
+ if (value instanceof Promise) {
34
+ value = await value;
35
+ } else if (value instanceof String) {
36
+ value = value.toString(); // adm-zip wants simple strings
37
+ }
38
+ zip.addFile(path, value);
39
+
40
+ // Special case for EPUB files, where `mimetype` must be uncompressed.
41
+ if (path === "mimetype") {
42
+ const entry = zip.getEntry(path);
43
+ entry.header.method = 0; // STORE (not DEFLATE)
44
+ }
45
+ }
46
+ const buffer = zip.toBuffer();
47
+ return buffer;
48
+ },
49
+
50
+ /**
51
+ * Unpack a ZIP file
52
+ */
53
+ async unpack(buffer, options) {
54
+ // Origami generally prefers keeping things as an Uint8Array or ArrayBuffer,
55
+ // but adm-zip only accepts a Buffer.
56
+ if (buffer instanceof Uint8Array || buffer instanceof ArrayBuffer) {
57
+ // @ts-ignore
58
+ buffer = Buffer.from(buffer);
59
+ }
60
+
61
+ const zip = new Zip(buffer);
62
+
63
+ const entries = zip.getEntries();
64
+ const filtered = entries.filter(
65
+ (entry) =>
66
+ !entry.entryName.startsWith("__MACOSX/") &&
67
+ !entry.entryName.endsWith("/"),
68
+ );
69
+ const deflated = Object.fromEntries(
70
+ filtered.map((entry) => [entry.entryName, () => entry.getData()]),
71
+ );
72
+
73
+ // The final tree will include extension handlers and have functions invoked
74
+ // to retrieve data from the ZIP file. While the base map is a SyncMap, the
75
+ // final tree will be async.
76
+ const classFn = HandleExtensionsTransform(
77
+ InvokeFunctionsTransform(SyncMap),
78
+ );
79
+ const result = await Tree.inflatePaths(deflated, { classFn });
80
+
81
+ const parent = options?.parent;
82
+ const globals = parent ? getGlobalsForTree(parent) : null;
83
+ if (globals) {
84
+ result.globals = globals;
85
+ }
86
+
87
+ return result;
88
+ },
89
+ };
90
+
91
+ /** @type {any} */ (zip_handler.pack).key = addExtensionKeyFn(".zip");
92
+
93
+ export default zip_handler;
94
+
95
+ function InvokeFunctionsTransform(Base) {
96
+ return class InvokeFunctions extends Base {
97
+ delete(key) {
98
+ return super.delete(key);
99
+ }
100
+
101
+ get(key) {
102
+ const value = super.get(key);
103
+ return typeof value === "function" ? value() : value;
104
+ }
105
+
106
+ set(key, value) {
107
+ return super.set(key, value);
108
+ }
109
+
110
+ trailingSlashKeys = true;
111
+ };
112
+ }
@@ -1,5 +1,9 @@
1
1
  import { Mixin } from "../../index.ts";
2
2
 
3
- declare const AsyncCacheTransform: Mixin<{}>
3
+ declare const AsyncCacheTransform: Mixin<{
4
+ cachePathForKey(key: string): string;
5
+ onKeysChange(key: string): void;
6
+ onValueChange(key: string): void;
7
+ }>
4
8
 
5
9
  export default AsyncCacheTransform;
@@ -45,24 +45,21 @@ export default function AsyncCacheTransform(Base) {
45
45
  this.cache = systemCache;
46
46
  }
47
47
 
48
- get cachePath() {
49
- // @ts-ignore
50
- return this[cachePathSymbol];
51
- }
52
-
53
48
  cachePathForKey(key) {
54
49
  return key === "."
55
- ? this.cachePath
56
- : SystemCacheMap.joinPath(this.cachePath, key);
50
+ ? // @ts-ignore
51
+ this[cachePathSymbol]
52
+ : // @ts-ignore
53
+ SystemCacheMap.joinPath(this[cachePathSymbol], key);
57
54
  }
58
55
 
59
- async delete(key) {
60
- const deleted = await super.delete(key);
61
- if (typeof key === "string") {
62
- systemCache.delete(this.cachePathForKey(key));
63
- }
64
- return deleted;
65
- }
56
+ // async delete(key) {
57
+ // const deleted = await super.delete(key);
58
+ // if (typeof key === "string") {
59
+ // systemCache.delete(this.cachePathForKey(key));
60
+ // }
61
+ // return deleted;
62
+ // }
66
63
 
67
64
  async get(key) {
68
65
  if (typeof key !== "string" || key.length === 0) {
@@ -119,16 +116,16 @@ export default function AsyncCacheTransform(Base) {
119
116
  systemCache.delete(this.cachePathForKey(key));
120
117
  }
121
118
 
122
- async set(key, value) {
123
- if (typeof key !== "string") {
124
- return super.set(key, value);
125
- }
126
- systemCache.delete(this.cachePathForKey(key));
127
- if (!this.has(key)) {
128
- // Adding a new key, need to invalidate cached keys
129
- this.invalidateKeys();
130
- }
131
- super.set(key, value);
132
- }
119
+ // async set(key, value) {
120
+ // if (typeof key !== "string") {
121
+ // return super.set(key, value);
122
+ // }
123
+ // systemCache.delete(this.cachePathForKey(key));
124
+ // if (!this.has(key)) {
125
+ // // Adding a new key, need to invalidate cached keys
126
+ // this.invalidateKeys();
127
+ // }
128
+ // super.set(key, value);
129
+ // }
133
130
  };
134
131
  }
@@ -4,15 +4,15 @@ import HandleExtensionsTransform from "./HandleExtensionsTransform.js";
4
4
  import ImportModulesMixin from "./ImportModulesMixin.js";
5
5
  import SyncCacheTransform from "./SyncCacheTransform.js";
6
6
  import WatchFilesMixin from "./WatchFilesMixin.js";
7
- import { noCacheSymbol } from "./symbols.js";
7
+ import { cachePathSymbol, noCacheSymbol } from "./symbols.js";
8
8
 
9
9
  export default class OrigamiFileMap extends SyncCacheTransform(
10
10
  HandleExtensionsTransform(
11
11
  ImportModulesMixin(WatchFilesMixin(EventTargetMixin(FileMap))),
12
12
  ),
13
13
  ) {
14
- get cachePath() {
15
- return super.cachePath ?? this.path;
14
+ get [cachePathSymbol]() {
15
+ return this.path;
16
16
  }
17
17
 
18
18
  // Workaround to register file paths in the system cache without trailing
@@ -1,8 +1,9 @@
1
1
  import { Mixin } from "../../index.ts";
2
2
 
3
3
  declare const SyncCacheTransform: Mixin<{
4
- get cachePath(): string;
5
4
  cachePathForKey(key: string): string;
5
+ onKeysChange(key: string): void;
6
+ onValueChange(key: string): void;
6
7
  }>
7
8
 
8
9
  export default SyncCacheTransform;