@weborigami/language 0.6.17 → 0.7.0-beta.2

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 (90) hide show
  1. package/index.ts +1 -0
  2. package/main.js +7 -1
  3. package/package.json +7 -6
  4. package/src/compiler/compile.js +10 -3
  5. package/src/compiler/optimize.js +71 -40
  6. package/src/compiler/parse.js +1 -1
  7. package/src/compiler/parserHelpers.js +5 -3
  8. package/src/handlers/addExtensionKeyFn.js +18 -0
  9. package/src/handlers/epub_handler.js +54 -0
  10. package/src/handlers/getSource.js +11 -0
  11. package/src/handlers/handlers.js +2 -0
  12. package/src/handlers/htm_handler.js +1 -1
  13. package/src/handlers/js_handler.js +13 -4
  14. package/src/handlers/mediaTypeExtensions.json +15 -0
  15. package/src/handlers/ori_handler.js +8 -7
  16. package/src/handlers/oridocument_handler.js +19 -28
  17. package/src/handlers/processOriExport.js +17 -0
  18. package/src/handlers/tsv_handler.js +1 -1
  19. package/src/handlers/txt_handler.js +4 -2
  20. package/src/handlers/xhtml_handler.js +1 -1
  21. package/src/handlers/yaml_handler.js +6 -3
  22. package/src/handlers/zip_handler.js +112 -0
  23. package/src/project/activeProjectRoot.js +9 -0
  24. package/src/project/getGlobalsForTree.js +5 -0
  25. package/src/project/{projectGlobals.js → initializeGlobalsForTree.js} +8 -13
  26. package/src/project/jsGlobals.js +1 -0
  27. package/src/project/projectConfig.js +2 -2
  28. package/src/project/projectRootFromPath.js +2 -0
  29. package/src/protocols/constructHref.js +3 -3
  30. package/src/protocols/constructSiteTree.js +11 -2
  31. package/src/protocols/explore.js +1 -1
  32. package/src/protocols/explorehttp.js +1 -1
  33. package/src/protocols/fetchAndHandleExtension.js +23 -11
  34. package/src/protocols/files.js +1 -0
  35. package/src/protocols/http.js +4 -1
  36. package/src/protocols/https.js +4 -1
  37. package/src/protocols/httpstree.js +1 -1
  38. package/src/protocols/httptree.js +1 -1
  39. package/src/protocols/package.js +15 -3
  40. package/src/runtime/AsyncCacheTransform.d.ts +5 -0
  41. package/src/runtime/AsyncCacheTransform.js +134 -0
  42. package/src/runtime/HandleExtensionsTransform.d.ts +3 -1
  43. package/src/runtime/HandleExtensionsTransform.js +18 -2
  44. package/src/runtime/OrigamiFileMap.d.ts +5 -2
  45. package/src/runtime/OrigamiFileMap.js +27 -4
  46. package/src/runtime/ScopeMap.js +72 -0
  47. package/src/runtime/SyncCacheTransform.d.ts +8 -0
  48. package/src/runtime/SyncCacheTransform.js +133 -0
  49. package/src/runtime/SystemCacheMap.js +259 -0
  50. package/src/runtime/WatchFilesMixin.js +52 -19
  51. package/src/runtime/enableValueCaching.js +192 -0
  52. package/src/runtime/execute.js +2 -2
  53. package/src/runtime/executionContext.js +7 -0
  54. package/src/runtime/explainReferenceError.js +7 -2
  55. package/src/runtime/expressionObject.js +54 -46
  56. package/src/runtime/handleExtension.js +65 -34
  57. package/src/runtime/interop.js +2 -2
  58. package/src/runtime/mergeTrees.js +1 -1
  59. package/src/runtime/ops.js +28 -33
  60. package/src/runtime/symbols.js +3 -0
  61. package/src/runtime/systemCache.js +3 -0
  62. package/src/runtime/volatile.js +14 -0
  63. package/test/compiler/codeHelpers.js +2 -1
  64. package/test/compiler/optimize.test.js +62 -54
  65. package/test/handlers/epub_handler.test.js +27 -0
  66. package/test/handlers/fixtures/test.zip +0 -0
  67. package/test/handlers/ori_handler.test.js +22 -3
  68. package/test/handlers/oridocument_handler.test.js +1 -1
  69. package/test/handlers/zip_handler.test.js +45 -0
  70. package/test/protocols/https.test.js +19 -0
  71. package/test/protocols/package.test.js +7 -2
  72. package/test/runtime/AsyncCacheTransform.test.js +91 -0
  73. package/test/runtime/OrigamiFileMap.test.js +26 -23
  74. package/test/runtime/ScopeMap.test.js +49 -0
  75. package/test/runtime/SyncCacheTransform.test.js +93 -0
  76. package/test/runtime/SystemCacheMap.test.js +239 -0
  77. package/test/runtime/asyncCalcs.js +28 -0
  78. package/test/runtime/enableValueCaching.test.js +55 -0
  79. package/test/runtime/errors.test.js +53 -30
  80. package/test/runtime/evaluate.test.js +9 -4
  81. package/test/runtime/execute.test.js +6 -1
  82. package/test/runtime/expressionObject.test.js +55 -15
  83. package/test/runtime/fetchAndHandleExtension.test.js +24 -0
  84. package/test/runtime/fixtures/unpack/hello.json +1 -0
  85. package/test/runtime/handleExtension.test.js +12 -1
  86. package/test/runtime/ops.test.js +70 -65
  87. package/test/runtime/syncCalcs.js +27 -0
  88. package/test/runtime/systemCache.test.js +66 -0
  89. package/src/runtime/assignPropertyDescriptors.js +0 -23
  90. package/src/runtime/asyncStorage.js +0 -7
@@ -6,13 +6,15 @@
6
6
  * @typedef {import("@weborigami/async-tree").SyncOrAsyncMap} SyncOrAsyncMap
7
7
  */
8
8
 
9
- import { getParent, isUnpackable, Tree } from "@weborigami/async-tree";
9
+ import { getParent, isUnpackable, SyncMap, Tree } from "@weborigami/async-tree";
10
10
  import os from "node:os";
11
11
  import execute from "./execute.js";
12
12
  import expressionObject from "./expressionObject.js";
13
13
  import mergeTrees from "./mergeTrees.js";
14
14
  import OrigamiFileMap from "./OrigamiFileMap.js";
15
+ import ScopeMap from "./ScopeMap.js";
15
16
  import { codeSymbol } from "./symbols.js";
17
+ import systemCache from "./systemCache.js";
16
18
 
17
19
  function addOpLabel(op, label) {
18
20
  Object.defineProperty(op, "toString", {
@@ -81,31 +83,18 @@ addOpLabel(bitwiseXor, "«ops.bitwiseXor»");
81
83
  /**
82
84
  * Cache the value of the code for an external reference
83
85
  *
84
- * @param {any} cache
85
- * @param {string} path
86
+ * @param {string} cachePath
86
87
  * @param {AnnotatedCode} code
88
+ * @param {RuntimeState} state
87
89
  */
88
- export async function cache(cache, path, code) {
89
- if (path in cache) {
90
- // Cache hit
91
- return cache[path];
92
- }
93
-
94
- // Don't await: might get another request for this before promise resolves
95
- const promise = execute(code);
96
-
97
- // Save promise so another request will get the same promise
98
- cache[path] = promise;
99
-
100
- // Now wait for the value
101
- const value = await promise;
102
-
103
- // Update the cache with the actual value
104
- cache[path] = value;
105
-
106
- return value;
90
+ export function cache(cachePath, code, state) {
91
+ const result = systemCache.getOrInsertComputedAsync(cachePath, () =>
92
+ execute(code, state),
93
+ );
94
+ return result;
107
95
  }
108
96
  addOpLabel(cache, "«ops.cache»");
97
+ cache.needsState = true;
109
98
  cache.unevaluatedArgs = true;
110
99
 
111
100
  /**
@@ -114,13 +103,17 @@ cache.unevaluatedArgs = true;
114
103
  * @param {...AnnotatedCode} args
115
104
  */
116
105
  export async function comma(...args) {
106
+ /** @type {RuntimeState} */
107
+ // @ts-ignore
108
+ const state = args.pop(); // The runtime state is passed as the last argument
117
109
  let result;
118
110
  for (const arg of args) {
119
- result = await execute(arg);
111
+ result = await execute(arg, state);
120
112
  }
121
113
  return result;
122
114
  }
123
115
  addOpLabel(comma, "«ops.comma»");
116
+ comma.needsState = true;
124
117
  comma.unevaluatedArgs = true;
125
118
 
126
119
  export async function conditional(condition, truthy, falsy) {
@@ -143,7 +136,7 @@ export async function construct(constructor, ...args) {
143
136
  export async function deepText(...args) {
144
137
  return Tree.deepText(args);
145
138
  }
146
- addOpLabel(deepText, "«ops.deepText");
139
+ addOpLabel(deepText, "«ops.deepText»");
147
140
 
148
141
  /**
149
142
  * Default value for a parameter: if the value is defined, return that;
@@ -180,8 +173,7 @@ addOpLabel(exponentiation, "«ops.exponentiation»");
180
173
  export async function flat(...args) {
181
174
  // Unpack packed arguments so they can be flattened
182
175
  const unpacked = args.map((arg) => (isUnpackable(arg) ? arg.unpack() : arg));
183
- // Flatten to depth 2: 1 for args array, 1 for flattening
184
- return Tree.flat(unpacked, 2);
176
+ return Tree.flat(unpacked);
185
177
  }
186
178
  addOpLabel(flat, "«ops.flat»");
187
179
 
@@ -206,6 +198,7 @@ addOpLabel(greaterThanOrEqual, "«ops.greaterThanOrEqual»");
206
198
  */
207
199
  export async function homeDirectory(...keys) {
208
200
  const tree = new OrigamiFileMap(os.homedir());
201
+ await tree.initializeGlobals();
209
202
  return keys.length > 0 ? Tree.traverse(tree, ...keys) : tree;
210
203
  }
211
204
  addOpLabel(homeDirectory, "«ops.homeDirectory»");
@@ -488,7 +481,8 @@ export async function property(object, key) {
488
481
 
489
482
  if (isUnpackable(object)) {
490
483
  object = await object.unpack();
491
- } else if (typeof object === "string") {
484
+ }
485
+ if (typeof object === "string") {
492
486
  object = new String(object);
493
487
  } else if (typeof object === "number") {
494
488
  object = new Number(object);
@@ -529,17 +523,18 @@ addOpLabel(rootDirectory, "«ops.rootDirectory»");
529
523
  /**
530
524
  * Return the scope of the current tree
531
525
  *
532
- * @param {SyncOrAsyncMap} parent
526
+ * @param {RuntimeState} state
533
527
  */
534
- export async function scope(parent) {
528
+ export async function scope(state = {}) {
529
+ const { parent } = state;
535
530
  if (!parent) {
536
- throw new ReferenceError(
537
- "Tried to find a value in scope, but no container was provided as the parent.",
538
- );
531
+ return new SyncMap(); // empty scope if there's no parent
539
532
  }
540
- return Tree.scope(parent);
533
+ const scopeMap = new ScopeMap(parent);
534
+ return scopeMap;
541
535
  }
542
536
  addOpLabel(scope, "«ops.scope»");
537
+ scope.needsState = true;
543
538
 
544
539
  export function shiftLeft(a, b) {
545
540
  return a << b;
@@ -1,4 +1,7 @@
1
+ export const cachePathSymbol = Symbol("cachePath");
1
2
  export const codeSymbol = Symbol("code");
2
3
  export const configSymbol = Symbol("config");
4
+ export const noCacheSymbol = Symbol("noCache");
3
5
  export const scopeSymbol = Symbol("scope");
4
6
  export const sourceSymbol = Symbol("source");
7
+ export const volatileSymbol = Symbol("volatile");
@@ -0,0 +1,3 @@
1
+ import SystemCacheMap from "./SystemCacheMap.js";
2
+
3
+ export default new SystemCacheMap();
@@ -0,0 +1,14 @@
1
+ import { box } from "@weborigami/async-tree";
2
+ import { volatileSymbol } from "./symbols.js";
3
+
4
+ /**
5
+ * Mark the indicated value as volatile so it won't be cached.
6
+ */
7
+ export default function volatile(value) {
8
+ const boxed = box(value);
9
+ Object.defineProperty(boxed, volatileSymbol, {
10
+ value: true,
11
+ enumerable: false,
12
+ });
13
+ return boxed;
14
+ }
@@ -23,11 +23,12 @@ export function assertCodeLocations(code) {
23
23
  */
24
24
  export function createCode(array) {
25
25
  const code = array.map((item) =>
26
- item instanceof Array ? createCode(item) : item
26
+ item instanceof Array ? createCode(item) : item,
27
27
  );
28
28
  /** @type {any} */ (code).location = {
29
29
  end: 0,
30
30
  source: {
31
+ relativePath: "test.ori",
31
32
  text: "",
32
33
  },
33
34
  start: 0,
@@ -46,15 +46,7 @@ describe("optimize", () => {
46
46
  });
47
47
 
48
48
  test("resolve deeper context references", () => {
49
- // Compilation of `{ a: 1, more: { a } }`
50
- const code = createCode([
51
- ops.object,
52
- ["a", [ops.literal, 1]],
53
- [
54
- "more",
55
- [ops.object, ["a", [markers.traverse, [markers.reference, "a"]]]],
56
- ],
57
- ]);
49
+ const expression = `{ a: 1, more: { a } }`;
58
50
  const expected = [
59
51
  ops.object,
60
52
  ["a", 1],
@@ -72,7 +64,7 @@ describe("optimize", () => {
72
64
  ],
73
65
  ],
74
66
  ];
75
- assertCodeEqual(optimize(code), expected);
67
+ assertCompile(expression, expected);
76
68
  });
77
69
 
78
70
  test("when defining a property, avoid recursive references", () => {
@@ -103,7 +95,7 @@ describe("optimize", () => {
103
95
  assertCompile(expression, expected, "shell");
104
96
  });
105
97
 
106
- describe("resolve reference", () => {
98
+ describe.only("resolve reference", () => {
107
99
  test("external reference", () => {
108
100
  // Compilation of `folder` where folder isn't a variable
109
101
  const code = createCode([
@@ -113,35 +105,36 @@ describe("optimize", () => {
113
105
  const parent = {};
114
106
  const expected = [
115
107
  ops.cache,
116
- {},
117
- "folder",
118
- [
119
- [ops.scope, parent],
120
- [ops.literal, "folder"],
121
- ],
108
+ "test.ori/_refs/folder",
109
+ [[ops.scope], [ops.literal, "folder"]],
122
110
  ];
123
111
  const globals = {};
124
- assertCodeEqual(optimize(code, { globals, parent }), expected);
112
+ assertCodeEqual(
113
+ optimize(code, { cachePath: "test.ori", globals, parent }),
114
+ expected,
115
+ );
125
116
  });
126
117
 
127
- test("external reference", () => {
128
- // Compilation of `index.html` where `index` isn't a variable
118
+ test("external reference with implied unpack", () => {
119
+ // Compilation of `greet.ori("world")`
129
120
  const code = createCode([
130
- markers.traverse,
131
- [markers.reference, "index.html"],
121
+ [markers.traverse, [markers.reference, "greet.ori"]],
122
+ [ops.literal, "world"],
132
123
  ]);
133
124
  const parent = {};
134
125
  const expected = [
135
- ops.cache,
136
- {},
137
- "index.html",
138
126
  [
139
- [ops.scope, parent],
140
- [ops.literal, "index.html"],
127
+ ops.cache,
128
+ "test.ori/_refs/greet.ori/",
129
+ [ops.unpack, [[ops.scope], [ops.literal, "greet.ori"]]],
141
130
  ],
131
+ "world",
142
132
  ];
143
133
  const globals = {};
144
- assertCodeEqual(optimize(code, { globals, parent }), expected);
134
+ assertCodeEqual(
135
+ optimize(code, { cachePath: "test.ori", globals, parent }),
136
+ expected,
137
+ );
145
138
  });
146
139
 
147
140
  test("external reference inside object with matching key", () => {
@@ -162,18 +155,17 @@ describe("optimize", () => {
162
155
  ops.getter,
163
156
  [
164
157
  ops.cache,
165
- {},
166
- "posts.txt",
167
- [
168
- [ops.scope, parent],
169
- [ops.literal, "posts.txt"],
170
- ],
158
+ "test.ori/_refs/posts.txt",
159
+ [[ops.scope], [ops.literal, "posts.txt"]],
171
160
  ],
172
161
  ],
173
162
  ],
174
163
  ];
175
164
  const globals = {};
176
- assertCodeEqual(optimize(code, { globals, parent }), expected);
165
+ assertCodeEqual(
166
+ optimize(code, { cachePath: "test.ori", globals, parent }),
167
+ expected,
168
+ );
177
169
  });
178
170
 
179
171
  test("global reference", () => {
@@ -233,17 +225,26 @@ describe("optimize", () => {
233
225
  });
234
226
 
235
227
  test("root directory", () => {
236
- // Compilation of `</>`
237
- const code = createCode([markers.traverse, [markers.external, "/"]]);
238
- const expected = [ops.cache, {}, "/", [ops.rootDirectory]];
239
- assertCodeEqual(optimize(code), expected);
228
+ // Compilation of `</etc/passwd>`
229
+ const code = createCode([
230
+ markers.traverse,
231
+ [markers.external, "/"],
232
+ [ops.literal, "etc/"],
233
+ [ops.literal, "passwd"],
234
+ ]);
235
+ const expected = [
236
+ ops.cache,
237
+ "test.ori/_refs//etc/passwd",
238
+ [[ops.rootDirectory], [ops.literal, "etc/"], [ops.literal, "passwd"]],
239
+ ];
240
+ assertCodeEqual(optimize(code, { cachePath: "test.ori" }), expected);
240
241
  });
241
242
 
242
243
  test("home directory", () => {
243
244
  // Compilation of `<~>`
244
245
  const code = createCode([markers.traverse, [ops.homeDirectory]]);
245
- const expected = [ops.cache, {}, "~", [ops.homeDirectory]];
246
- assertCodeEqual(optimize(code), expected);
246
+ const expected = [ops.cache, "test.ori/_refs/~", [ops.homeDirectory]];
247
+ assertCodeEqual(optimize(code, { cachePath: "test.ori" }), expected);
247
248
  });
248
249
  });
249
250
 
@@ -259,16 +260,18 @@ describe("optimize", () => {
259
260
  const parent = {};
260
261
  const expected = [
261
262
  ops.cache,
262
- {},
263
- "path/to/file",
263
+ "test.ori/_refs/path/to/file",
264
264
  [
265
- [ops.scope, parent],
265
+ [ops.scope],
266
266
  [ops.literal, "path/"],
267
267
  [ops.literal, "to/"],
268
268
  [ops.literal, "file"],
269
269
  ],
270
270
  ];
271
- assertCodeEqual(optimize(code, { parent }), expected);
271
+ assertCodeEqual(
272
+ optimize(code, { cachePath: "test.ori", parent }),
273
+ expected,
274
+ );
272
275
  });
273
276
 
274
277
  test("implicit external path", () => {
@@ -282,15 +285,13 @@ describe("optimize", () => {
282
285
  const parent = {};
283
286
  const expected = [
284
287
  ops.cache,
285
- {},
286
- "package.json/name",
287
- [
288
- [ops.scope, parent],
289
- [ops.literal, "package.json/"],
290
- [ops.literal, "name"],
291
- ],
288
+ "test.ori/_refs/package.json/name",
289
+ [[ops.scope], [ops.literal, "package.json/"], [ops.literal, "name"]],
292
290
  ];
293
- assertCodeEqual(optimize(code, { globals, parent }), expected);
291
+ assertCodeEqual(
292
+ optimize(code, { cachePath: "test.ori", globals, parent }),
293
+ expected,
294
+ );
294
295
  });
295
296
 
296
297
  test("local path", () => {
@@ -314,9 +315,16 @@ describe("optimize", () => {
314
315
  });
315
316
 
316
317
  function assertCompile(expression, expected, mode = "shell") {
318
+ const source =
319
+ typeof expression !== "string"
320
+ ? expression
321
+ : {
322
+ text: expression,
323
+ relativePath: "test.ori",
324
+ };
317
325
  const parent = new SyncMap();
318
326
  const globals = {};
319
- const fn = compile.expression(expression, { globals, mode, parent });
327
+ const fn = compile.expression(source, { globals, mode, parent });
320
328
  const actual = fn.code;
321
329
  assertCodeLocations(actual);
322
330
  assertCodeEqual(actual, expected);
@@ -0,0 +1,27 @@
1
+ import Zip from "adm-zip";
2
+ import assert from "node:assert";
3
+ import { describe, test } from "node:test";
4
+ import epub_handler from "../../src/handlers/epub_handler.js";
5
+
6
+ describe("EPUB handler", () => {
7
+ test("ensures mimetype file comes first", async () => {
8
+ const tree = {
9
+ EPUB: {
10
+ "index.xhtml": "This is where the book content goes",
11
+ },
12
+ "META-INF": {
13
+ "container.xml": "This is where the metadata goes",
14
+ },
15
+ mimetype: "application/epub+zip",
16
+ };
17
+ const buffer = await epub_handler.pack(tree);
18
+ const unzipped = new Zip(buffer);
19
+ const entries = unzipped.getEntries();
20
+ const entryNames = entries.map((entry) => entry.entryName);
21
+ assert.deepEqual(entryNames, [
22
+ "mimetype",
23
+ "EPUB/index.xhtml",
24
+ "META-INF/container.xml",
25
+ ]);
26
+ });
27
+ });
Binary file
@@ -3,9 +3,11 @@ import assert from "node:assert";
3
3
  import { describe, test } from "node:test";
4
4
  import ori_handler from "../../src/handlers/ori_handler.js";
5
5
  import OrigamiFileMap from "../../src/runtime/OrigamiFileMap.js";
6
+ import { cachePathSymbol } from "../../src/runtime/symbols.js";
6
7
 
7
8
  const fixturesUrl = new URL("fixtures", import.meta.url);
8
9
  const fixtures = new OrigamiFileMap(fixturesUrl);
10
+ await fixtures.initializeGlobals();
9
11
 
10
12
  describe(".ori handler", async () => {
11
13
  test("loads a string expression", async () => {
@@ -38,16 +40,18 @@ describe(".ori handler", async () => {
38
40
  const tree = await ori_handler.unpack(source);
39
41
  assert.deepEqual(
40
42
  await Tree.traverse(tree, "public", "message"),
41
- "Hello, world!"
43
+ "Hello, world!",
42
44
  );
43
45
  });
44
46
 
45
47
  test("loads an object containing an object shorthand", async () => {
46
- const assets = new ObjectMap({});
48
+ const assets = new ObjectMap({ a: 1, b: 2 });
47
49
  const parent = new ObjectMap({ assets });
48
50
  const source = `{ assets }`;
49
51
  const object = await ori_handler.unpack(source, { parent });
50
- assert.equal(object.assets, assets);
52
+ const expectedValues = Array.from(assets.values());
53
+ const actualValues = Array.from(object.assets.values());
54
+ assert.deepEqual(actualValues, expectedValues);
51
55
  });
52
56
 
53
57
  test("loads a template literal", async () => {
@@ -84,4 +88,19 @@ describe(".ori handler", async () => {
84
88
  const indexHtml = await tree["index.html"];
85
89
  assert.equal(indexHtml, "Hello, world!");
86
90
  });
91
+
92
+ test("enables caching for top-level object", async () => {
93
+ const parent = new ObjectMap({});
94
+ const source = `{
95
+ message = "Hello"
96
+ }`;
97
+ const object = await ori_handler.unpack(source, {
98
+ key: "test.ori",
99
+ parent,
100
+ });
101
+ assert.equal(object[cachePathSymbol], "test.ori/");
102
+ assert.deepEqual(await Tree.plain(object), {
103
+ message: "Hello",
104
+ });
105
+ });
87
106
  });
@@ -22,7 +22,7 @@ describe("Origami document handler", () => {
22
22
 
23
23
  test("Argument to template document available as underscore", async () => {
24
24
  const text = "<h1>${ _ }</h1>";
25
- const fn = await oridocument_handler.unpack(text);
25
+ const fn = await oridocument_handler.unpack(text, { key: "test.ori.html" });
26
26
  const result = await fn("Home");
27
27
  assert.equal(result, "<h1>Home</h1>");
28
28
  });
@@ -0,0 +1,45 @@
1
+ import { Tree } from "@weborigami/async-tree";
2
+ import Zip from "adm-zip";
3
+ import assert from "node:assert";
4
+ import fs from "node:fs/promises";
5
+ import { describe, test } from "node:test";
6
+ import zip_handler from "../../src/handlers/zip_handler.js";
7
+
8
+ describe("ZIP handler", () => {
9
+ test("creates a ZIP file as Buffer", async () => {
10
+ const tree = {
11
+ "ReadMe.md": "This is a ReadMe file.",
12
+ sub: {
13
+ "file.txt": "This is a text file in a subfolder.",
14
+ },
15
+ };
16
+ const buffer = await zip_handler.pack(tree);
17
+ const unzipped = new Zip(buffer);
18
+ const entries = unzipped.getEntries();
19
+ assert.equal(entries.length, 2);
20
+ assert.equal(entries[0].entryName, "ReadMe.md");
21
+ assert.equal(
22
+ entries[0].getData().toString("utf8"),
23
+ "This is a ReadMe file.",
24
+ );
25
+ assert.equal(entries[1].entryName, "sub/file.txt");
26
+ assert.equal(
27
+ entries[1].getData().toString("utf8"),
28
+ "This is a text file in a subfolder.",
29
+ );
30
+ });
31
+
32
+ test("reads a ZIP file", async () => {
33
+ const fixturePath = new URL("fixtures/test.zip", import.meta.url);
34
+ const buffer = await fs.readFile(fixturePath);
35
+ const tree = await zip_handler.unpack(buffer);
36
+ const plain = await Tree.plain(tree);
37
+ assert.deepEqual(Object.keys(plain), ["ReadMe.md", "sub"]);
38
+ assert.deepEqual(Object.keys(plain.sub), ["file.txt"]);
39
+ assert.equal(String(plain["ReadMe.md"]), "This is a ReadMe file.\n");
40
+ assert.equal(
41
+ String(plain.sub["file.txt"]),
42
+ "This is a text file in a subfolder.\n",
43
+ );
44
+ });
45
+ });
@@ -0,0 +1,19 @@
1
+ import assert from "node:assert";
2
+ import { beforeEach, describe, test } from "node:test";
3
+ import https from "../../src/protocols/https.js";
4
+ import systemCache from "../../src/runtime/systemCache.js";
5
+
6
+ describe("https", () => {
7
+ beforeEach(() => {
8
+ systemCache.clear();
9
+ });
10
+
11
+ test("caches fetched resources", async () => {
12
+ const result1 = await https("example.com", {});
13
+ assert(systemCache.has("https://example.com"));
14
+ const text = new TextDecoder().decode(result1);
15
+ assert(text.includes("Example Domain"));
16
+ const result2 = await https("example.com", {});
17
+ assert.strictEqual(result1, result2);
18
+ });
19
+ });
@@ -1,16 +1,21 @@
1
1
  import assert from "node:assert";
2
2
  import process from "node:process";
3
3
  import { describe, test } from "node:test";
4
+ import coreGlobals from "../../src/project/coreGlobals.js";
4
5
  import projectRootFromPath from "../../src/project/projectRootFromPath.js";
5
6
  import packageProtocol from "../../src/protocols/package.js";
6
7
 
7
8
  describe("package: protocol", () => {
8
9
  test("returns a package's main export(s)", async () => {
9
10
  // Reproduce the type of evaluation context object the runtime would create
10
- const projectRoot = await projectRootFromPath(process.cwd());
11
- const state = { parent: projectRoot };
11
+ const parent = await projectRootFromPath(process.cwd());
12
+ const globals = await coreGlobals();
13
+ /** @type {any} */ (parent).globals = globals;
14
+ const state = { globals, parent };
12
15
 
13
16
  const result = await packageProtocol("@weborigami", "async-tree", state);
17
+
18
+ // Try a method from the package
14
19
  const { toString } = result;
15
20
  assert.equal(toString(123), "123");
16
21
  });
@@ -0,0 +1,91 @@
1
+ import assert from "node:assert";
2
+ import { beforeEach, describe, test } from "node:test";
3
+ import systemCache from "../../src/runtime/systemCache.js";
4
+
5
+ // We test AsyncCacheTransform via asyncCalcs since it's a fairly small
6
+ // application of the transform.
7
+ import { Tree } from "@weborigami/async-tree";
8
+ import asyncCalcs from "./asyncCalcs.js";
9
+
10
+ describe("AsyncCacheTransform", () => {
11
+ beforeEach(() => {
12
+ systemCache.clear();
13
+ });
14
+
15
+ test("caches values and records dependencies", async () => {
16
+ // { a = 2 * b, b = c + 1, c = 3 }
17
+ let log = [];
18
+ const { calcs, data } = asyncCalcs([
19
+ [
20
+ "a",
21
+ async () => {
22
+ log.push("a");
23
+ const b = await calcs.get("b");
24
+ return 2 * b;
25
+ },
26
+ ],
27
+ [
28
+ "b",
29
+ async () => {
30
+ log.push("b");
31
+ const c = await calcs.get("c");
32
+ return c + 1;
33
+ },
34
+ ],
35
+ [
36
+ "c",
37
+ async () => {
38
+ log.push("c");
39
+ return 3;
40
+ },
41
+ ],
42
+ ]);
43
+
44
+ assert.deepEqual(await Tree.entries(calcs), [
45
+ ["a", 8],
46
+ ["b", 4],
47
+ ["c", 3],
48
+ ]);
49
+ assert.deepEqual(log, ["a", "b", "c"]);
50
+
51
+ log = [];
52
+ const a1 = await calcs.get("a");
53
+ assert.strictEqual(a1, 8);
54
+ assert.deepEqual(log, []); // a is cached, no new calcs
55
+
56
+ // Replace formula for a
57
+ // { a = 3 * b, b = c + 1, c = 3 }
58
+ data.set("a", async () => {
59
+ log.push("a");
60
+ const b = await calcs.get("b");
61
+ return 3 * b;
62
+ });
63
+ log = [];
64
+ const a2 = await calcs.get("a");
65
+ assert.strictEqual(a2, 12);
66
+ assert.deepEqual(log, ["a"]); // recalc only a
67
+
68
+ // Replace formula for b
69
+ // { a = 3 * b, b = c + 10, c = 3 }
70
+ data.set("b", async () => {
71
+ log.push("b");
72
+ const c = await calcs.get("c");
73
+ return c + 10;
74
+ });
75
+ log = [];
76
+ const a3 = await calcs.get("a");
77
+ assert.strictEqual(a3, 39);
78
+ assert.deepEqual(log, ["a", "b"]); // recalc a and b
79
+
80
+ // Replace value of c
81
+ // { a = 3 * b, b = c + 10, c = 100 }
82
+ data.set("c", async () => {
83
+ log.push("c");
84
+ return 100;
85
+ });
86
+ log = [];
87
+ const a4 = await calcs.get("a");
88
+ assert.strictEqual(a4, 330);
89
+ assert.deepEqual(log, ["a", "b", "c"]); // recalc all
90
+ });
91
+ });