@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.
- package/index.ts +1 -0
- package/main.js +7 -1
- package/package.json +7 -6
- package/src/compiler/compile.js +10 -3
- package/src/compiler/optimize.js +71 -40
- package/src/compiler/parse.js +1 -1
- package/src/compiler/parserHelpers.js +5 -3
- package/src/handlers/addExtensionKeyFn.js +18 -0
- package/src/handlers/epub_handler.js +54 -0
- package/src/handlers/getSource.js +11 -0
- package/src/handlers/handlers.js +2 -0
- package/src/handlers/htm_handler.js +1 -1
- package/src/handlers/js_handler.js +13 -4
- package/src/handlers/mediaTypeExtensions.json +15 -0
- package/src/handlers/ori_handler.js +8 -7
- package/src/handlers/oridocument_handler.js +19 -28
- package/src/handlers/processOriExport.js +17 -0
- package/src/handlers/tsv_handler.js +1 -1
- package/src/handlers/txt_handler.js +4 -2
- package/src/handlers/xhtml_handler.js +1 -1
- package/src/handlers/yaml_handler.js +6 -3
- package/src/handlers/zip_handler.js +112 -0
- package/src/project/activeProjectRoot.js +9 -0
- package/src/project/getGlobalsForTree.js +5 -0
- package/src/project/{projectGlobals.js → initializeGlobalsForTree.js} +8 -13
- package/src/project/jsGlobals.js +1 -0
- package/src/project/projectConfig.js +2 -2
- package/src/project/projectRootFromPath.js +2 -0
- package/src/protocols/constructHref.js +3 -3
- package/src/protocols/constructSiteTree.js +11 -2
- package/src/protocols/explore.js +1 -1
- package/src/protocols/explorehttp.js +1 -1
- package/src/protocols/fetchAndHandleExtension.js +23 -11
- package/src/protocols/files.js +1 -0
- package/src/protocols/http.js +4 -1
- package/src/protocols/https.js +4 -1
- package/src/protocols/httpstree.js +1 -1
- package/src/protocols/httptree.js +1 -1
- package/src/protocols/package.js +15 -3
- package/src/runtime/AsyncCacheTransform.d.ts +5 -0
- package/src/runtime/AsyncCacheTransform.js +134 -0
- package/src/runtime/HandleExtensionsTransform.d.ts +3 -1
- package/src/runtime/HandleExtensionsTransform.js +18 -2
- package/src/runtime/OrigamiFileMap.d.ts +5 -2
- package/src/runtime/OrigamiFileMap.js +27 -4
- package/src/runtime/ScopeMap.js +72 -0
- package/src/runtime/SyncCacheTransform.d.ts +8 -0
- package/src/runtime/SyncCacheTransform.js +133 -0
- package/src/runtime/SystemCacheMap.js +259 -0
- package/src/runtime/WatchFilesMixin.js +52 -19
- package/src/runtime/enableValueCaching.js +192 -0
- package/src/runtime/execute.js +2 -2
- package/src/runtime/executionContext.js +7 -0
- package/src/runtime/explainReferenceError.js +7 -2
- package/src/runtime/expressionObject.js +54 -46
- package/src/runtime/handleExtension.js +65 -34
- package/src/runtime/interop.js +2 -2
- package/src/runtime/mergeTrees.js +1 -1
- package/src/runtime/ops.js +28 -33
- package/src/runtime/symbols.js +3 -0
- package/src/runtime/systemCache.js +3 -0
- package/src/runtime/volatile.js +14 -0
- package/test/compiler/codeHelpers.js +2 -1
- package/test/compiler/optimize.test.js +62 -54
- package/test/handlers/epub_handler.test.js +27 -0
- package/test/handlers/fixtures/test.zip +0 -0
- package/test/handlers/ori_handler.test.js +22 -3
- package/test/handlers/oridocument_handler.test.js +1 -1
- package/test/handlers/zip_handler.test.js +45 -0
- package/test/protocols/https.test.js +19 -0
- package/test/protocols/package.test.js +7 -2
- package/test/runtime/AsyncCacheTransform.test.js +91 -0
- package/test/runtime/OrigamiFileMap.test.js +26 -23
- package/test/runtime/ScopeMap.test.js +49 -0
- package/test/runtime/SyncCacheTransform.test.js +93 -0
- package/test/runtime/SystemCacheMap.test.js +239 -0
- package/test/runtime/asyncCalcs.js +28 -0
- package/test/runtime/enableValueCaching.test.js +55 -0
- package/test/runtime/errors.test.js +53 -30
- package/test/runtime/evaluate.test.js +9 -4
- package/test/runtime/execute.test.js +6 -1
- package/test/runtime/expressionObject.test.js +55 -15
- package/test/runtime/fetchAndHandleExtension.test.js +24 -0
- package/test/runtime/fixtures/unpack/hello.json +1 -0
- package/test/runtime/handleExtension.test.js +12 -1
- package/test/runtime/ops.test.js +70 -65
- package/test/runtime/syncCalcs.js +27 -0
- package/test/runtime/systemCache.test.js +66 -0
- package/src/runtime/assignPropertyDescriptors.js +0 -23
- package/src/runtime/asyncStorage.js +0 -7
|
@@ -1,40 +1,43 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { describe, test } from "node:test";
|
|
4
|
+
import { afterEach, beforeEach, describe, test } from "node:test";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import OrigamiFileMap from "../../src/runtime/OrigamiFileMap.js";
|
|
7
7
|
|
|
8
8
|
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
const tempDirectory = path.join(dirname, "fixtures/temp
|
|
9
|
+
const tempDirectory = path.join(dirname, "fixtures/temp");
|
|
10
10
|
|
|
11
11
|
describe("OrigamiFileMap", () => {
|
|
12
|
-
|
|
13
|
-
createTempDirectory();
|
|
12
|
+
let tempFiles;
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
// Remove any existing files or directories inside the temp directory so
|
|
16
|
+
// tests start from a clean slate. Use force so this is safe if the
|
|
17
|
+
// directory doesn't exist.
|
|
18
|
+
fs.rmSync(tempDirectory, { force: true, recursive: true });
|
|
19
|
+
fs.mkdirSync(tempDirectory, { recursive: true });
|
|
20
|
+
tempFiles = new OrigamiFileMap(tempDirectory);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
// HACK: should removeEventListener, which should stop watching
|
|
25
|
+
tempFiles.unwatch();
|
|
26
|
+
fs.rmSync(tempDirectory, { force: true, recursive: true });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("can watch its folder for changes", { timeout: 2000 }, async () => {
|
|
30
|
+
const changedFilePath = await new Promise(async (resolve) => {
|
|
17
31
|
tempFiles.addEventListener("change", (event) => {
|
|
18
|
-
resolve(/** @type {any} */ (event).options.
|
|
32
|
+
resolve(/** @type {any} */ (event).options.filePath);
|
|
19
33
|
});
|
|
34
|
+
await tempFiles.watch();
|
|
20
35
|
tempFiles.set(
|
|
21
|
-
"
|
|
22
|
-
"This file is left over from testing and can be removed."
|
|
36
|
+
"temp.txt",
|
|
37
|
+
"This file is left over from testing and can be removed.",
|
|
23
38
|
);
|
|
24
39
|
});
|
|
25
|
-
|
|
26
|
-
assert.equal(
|
|
40
|
+
|
|
41
|
+
assert.equal(path.basename(changedFilePath), "temp.txt");
|
|
27
42
|
});
|
|
28
43
|
});
|
|
29
|
-
|
|
30
|
-
function createTempDirectory() {
|
|
31
|
-
// Remove any existing files or directories inside the temp directory so
|
|
32
|
-
// tests start from a clean slate. Use force so this is safe if the
|
|
33
|
-
// directory doesn't exist.
|
|
34
|
-
fs.rmSync(tempDirectory, { force: true, recursive: true });
|
|
35
|
-
fs.mkdirSync(tempDirectory, { recursive: true });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function removeTempDirectory() {
|
|
39
|
-
fs.rmSync(tempDirectory, { force: true, recursive: true });
|
|
40
|
-
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ObjectMap } from "@weborigami/async-tree";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { describe, test } from "node:test";
|
|
4
|
+
import ScopeMap from "../../src/runtime/ScopeMap.js";
|
|
5
|
+
import { cachePathSymbol } from "../../src/runtime/symbols.js";
|
|
6
|
+
import SyncCacheTransform from "../../src/runtime/SyncCacheTransform.js";
|
|
7
|
+
import systemCache from "../../src/runtime/systemCache.js";
|
|
8
|
+
|
|
9
|
+
describe("scope", () => {
|
|
10
|
+
test("gets the first defined value from the scope trees", async () => {
|
|
11
|
+
class SyncCacheObjectMap extends SyncCacheTransform(ObjectMap) {}
|
|
12
|
+
|
|
13
|
+
/** @type {any} */
|
|
14
|
+
const outer = new SyncCacheObjectMap({
|
|
15
|
+
a: 1,
|
|
16
|
+
b: 2,
|
|
17
|
+
});
|
|
18
|
+
outer[cachePathSymbol] = "outer";
|
|
19
|
+
|
|
20
|
+
/** @type {any} */
|
|
21
|
+
const inner = new SyncCacheObjectMap({
|
|
22
|
+
a: 3,
|
|
23
|
+
});
|
|
24
|
+
inner[cachePathSymbol] = "outer/inner";
|
|
25
|
+
|
|
26
|
+
inner.parent = outer;
|
|
27
|
+
const innerScope = new ScopeMap(inner);
|
|
28
|
+
|
|
29
|
+
// Inner tree has precedence
|
|
30
|
+
const a = systemCache.getOrInsertComputed("test/a", () =>
|
|
31
|
+
innerScope.get("a"),
|
|
32
|
+
);
|
|
33
|
+
assert.equal(a, 3);
|
|
34
|
+
|
|
35
|
+
// If tree doesn't have value, finds value from parent
|
|
36
|
+
const b = systemCache.getOrInsertComputed("test/b", () =>
|
|
37
|
+
innerScope.get("b"),
|
|
38
|
+
);
|
|
39
|
+
assert.equal(b, 2);
|
|
40
|
+
|
|
41
|
+
const c = systemCache.getOrInsertComputed("test/c", () =>
|
|
42
|
+
innerScope.get("c"),
|
|
43
|
+
);
|
|
44
|
+
assert.equal(c, undefined);
|
|
45
|
+
|
|
46
|
+
assert.deepEqual([...innerScope.keys()], ["a", "b"]);
|
|
47
|
+
assert.deepEqual(/** @type {any} */ (innerScope).trees, [inner, outer]);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
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 SyncCacheTransform via syncCalcs since it's a fairly small
|
|
6
|
+
// application of the transform.
|
|
7
|
+
import syncCalcs from "./syncCalcs.js";
|
|
8
|
+
|
|
9
|
+
describe("SyncCacheTransform", () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
systemCache.clear();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("caches values and records dependencies", () => {
|
|
15
|
+
// { a = 2 * b, b = c + 1, c = 3 }
|
|
16
|
+
let log = [];
|
|
17
|
+
const { calcs, data } = syncCalcs([
|
|
18
|
+
[
|
|
19
|
+
"a",
|
|
20
|
+
() => {
|
|
21
|
+
log.push("a");
|
|
22
|
+
const b = calcs.get("b");
|
|
23
|
+
return 2 * b;
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
[
|
|
27
|
+
"b",
|
|
28
|
+
() => {
|
|
29
|
+
log.push("b");
|
|
30
|
+
const c = calcs.get("c");
|
|
31
|
+
return c + 1;
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
[
|
|
35
|
+
"c",
|
|
36
|
+
() => {
|
|
37
|
+
log.push("c");
|
|
38
|
+
return 3;
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
assert.deepEqual(
|
|
44
|
+
[...calcs.entries()],
|
|
45
|
+
[
|
|
46
|
+
["a", 8],
|
|
47
|
+
["b", 4],
|
|
48
|
+
["c", 3],
|
|
49
|
+
],
|
|
50
|
+
);
|
|
51
|
+
assert.deepEqual(log, ["a", "b", "c"]);
|
|
52
|
+
|
|
53
|
+
log = [];
|
|
54
|
+
const a1 = calcs.get("a");
|
|
55
|
+
assert.strictEqual(a1, 8);
|
|
56
|
+
assert.deepEqual(log, []); // a is cached, no new calcs
|
|
57
|
+
|
|
58
|
+
// Replace formula for a
|
|
59
|
+
// { a = 3 * b, b = c + 1, c = 3 }
|
|
60
|
+
data.set("a", () => {
|
|
61
|
+
log.push("a");
|
|
62
|
+
const b = calcs.get("b");
|
|
63
|
+
return 3 * b;
|
|
64
|
+
});
|
|
65
|
+
log = [];
|
|
66
|
+
const a2 = calcs.get("a");
|
|
67
|
+
assert.strictEqual(a2, 12);
|
|
68
|
+
assert.deepEqual(log, ["a"]); // recalc only a
|
|
69
|
+
|
|
70
|
+
// Replace formula for b
|
|
71
|
+
// { a = 3 * b, b = c + 10, c = 3 }
|
|
72
|
+
data.set("b", () => {
|
|
73
|
+
log.push("b");
|
|
74
|
+
const c = calcs.get("c");
|
|
75
|
+
return c + 10;
|
|
76
|
+
});
|
|
77
|
+
log = [];
|
|
78
|
+
const a3 = calcs.get("a");
|
|
79
|
+
assert.strictEqual(a3, 39);
|
|
80
|
+
assert.deepEqual(log, ["a", "b"]); // recalc a and b
|
|
81
|
+
|
|
82
|
+
// Replace value of c
|
|
83
|
+
// { a = 3 * b, b = c + 10, c = 100 }
|
|
84
|
+
data.set("c", () => {
|
|
85
|
+
log.push("c");
|
|
86
|
+
return 100;
|
|
87
|
+
});
|
|
88
|
+
log = [];
|
|
89
|
+
const a4 = calcs.get("a");
|
|
90
|
+
assert.strictEqual(a4, 330);
|
|
91
|
+
assert.deepEqual(log, ["a", "b", "c"]); // recalc all
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import SystemCacheMap from "../../src/runtime/SystemCacheMap.js";
|
|
4
|
+
import volatile from "../../src/runtime/volatile.js";
|
|
5
|
+
|
|
6
|
+
describe("SystemCacheMap", () => {
|
|
7
|
+
test("getOrInsertComputed tracks sync dependencies", async () => {
|
|
8
|
+
const cache = new SystemCacheMap();
|
|
9
|
+
let log;
|
|
10
|
+
|
|
11
|
+
// Create cache entries for
|
|
12
|
+
// { a = b + 1, b = 2 }
|
|
13
|
+
let getB = () =>
|
|
14
|
+
cache.getOrInsertComputed("b", () => {
|
|
15
|
+
log.push("b");
|
|
16
|
+
return 2;
|
|
17
|
+
});
|
|
18
|
+
let getA = () =>
|
|
19
|
+
cache.getOrInsertComputed("a", () => {
|
|
20
|
+
log.push("a");
|
|
21
|
+
const b = getB();
|
|
22
|
+
return b + 1;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
log = [];
|
|
26
|
+
const a1 = getA();
|
|
27
|
+
assert.strictEqual(a1, 3);
|
|
28
|
+
assert.deepEqual(log, ["a", "b"]);
|
|
29
|
+
assert.deepEqual(cacheEntries(cache), [
|
|
30
|
+
["a", { value: 3, upstreams: ["b"] }],
|
|
31
|
+
["b", { value: 2, downstreams: ["a"] }],
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
log = [];
|
|
35
|
+
const a2 = getA();
|
|
36
|
+
assert.strictEqual(a2, 3);
|
|
37
|
+
assert.deepEqual(log, []); // no recalculation
|
|
38
|
+
|
|
39
|
+
// Delete a
|
|
40
|
+
cache.delete("a");
|
|
41
|
+
assert.deepEqual(cacheEntries(cache), [["b", { value: 2 }]]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("getOrInsertComputedAsync tracks async dependencies", async () => {
|
|
45
|
+
const cache = new SystemCacheMap();
|
|
46
|
+
let log;
|
|
47
|
+
|
|
48
|
+
// Create cache entries for
|
|
49
|
+
// { a = 2 * b, b = c + 1, c = 3 }
|
|
50
|
+
let getC = async () =>
|
|
51
|
+
await cache.getOrInsertComputedAsync("c", async () => {
|
|
52
|
+
log.push("c");
|
|
53
|
+
return 3;
|
|
54
|
+
});
|
|
55
|
+
let getB = async () =>
|
|
56
|
+
await cache.getOrInsertComputedAsync("b", async () => {
|
|
57
|
+
log.push("b");
|
|
58
|
+
const c = await getC();
|
|
59
|
+
return c + 1;
|
|
60
|
+
});
|
|
61
|
+
let getA = async () =>
|
|
62
|
+
await cache.getOrInsertComputedAsync("a", async () => {
|
|
63
|
+
log.push("a");
|
|
64
|
+
const b = await getB();
|
|
65
|
+
return 2 * b;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Initial read of `a` populates cache
|
|
69
|
+
log = [];
|
|
70
|
+
const a1 = await getA();
|
|
71
|
+
assert.strictEqual(a1, 8);
|
|
72
|
+
assert.deepEqual(log, ["a", "b", "c"]);
|
|
73
|
+
assert.deepEqual(cacheEntries(cache), [
|
|
74
|
+
["a", { value: 8, upstreams: ["b"] }],
|
|
75
|
+
["b", { value: 4, downstreams: ["a"], upstreams: ["c"] }],
|
|
76
|
+
["c", { value: 3, downstreams: ["b"] }],
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
// Subsequent read of `a` hits cache
|
|
80
|
+
log = [];
|
|
81
|
+
const a2 = await getA();
|
|
82
|
+
assert.strictEqual(a2, 8);
|
|
83
|
+
assert.deepEqual(log, []); // no recalculation
|
|
84
|
+
|
|
85
|
+
// Delete a
|
|
86
|
+
cache.delete("a");
|
|
87
|
+
assert.deepEqual(cacheEntries(cache), [
|
|
88
|
+
["b", { value: 4, upstreams: ["c"] }],
|
|
89
|
+
["c", { value: 3, downstreams: ["b"] }],
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
// Replace formula for a
|
|
93
|
+
// { a = 3 * b, b = c + 1, c = 3 }
|
|
94
|
+
getA = async () =>
|
|
95
|
+
await cache.getOrInsertComputedAsync("a", async () => {
|
|
96
|
+
log.push("a");
|
|
97
|
+
const b = await getB();
|
|
98
|
+
return 3 * b;
|
|
99
|
+
});
|
|
100
|
+
log = [];
|
|
101
|
+
const a3 = await getA();
|
|
102
|
+
assert.strictEqual(a3, 12);
|
|
103
|
+
assert.deepEqual(log, ["a"]); // recalc only a
|
|
104
|
+
|
|
105
|
+
// Delete b, confirm it's removed as both upstream and downstream
|
|
106
|
+
cache.delete("b");
|
|
107
|
+
assert.deepEqual(cacheEntries(cache), [["c", { value: 3 }]]);
|
|
108
|
+
|
|
109
|
+
// Replace formula for b
|
|
110
|
+
// { a = 3 * b, b = c + 10, c = 3 }
|
|
111
|
+
getB = async () =>
|
|
112
|
+
await cache.getOrInsertComputedAsync("b", async () => {
|
|
113
|
+
log.push("b");
|
|
114
|
+
const c = await getC();
|
|
115
|
+
return c + 10;
|
|
116
|
+
});
|
|
117
|
+
log = [];
|
|
118
|
+
const a4 = await getA();
|
|
119
|
+
assert.strictEqual(a4, 39);
|
|
120
|
+
assert.deepEqual(log, ["a", "b"]); // recalc a and b
|
|
121
|
+
|
|
122
|
+
// Replace formula for c
|
|
123
|
+
// { a = 3 * b, b = c + 10, c = 100 }
|
|
124
|
+
getC = async () =>
|
|
125
|
+
await cache.getOrInsertComputedAsync("c", async () => {
|
|
126
|
+
log.push("c");
|
|
127
|
+
return 100;
|
|
128
|
+
});
|
|
129
|
+
cache.delete("c");
|
|
130
|
+
log = [];
|
|
131
|
+
const a5 = await getA();
|
|
132
|
+
assert.strictEqual(a5, 330);
|
|
133
|
+
assert.deepEqual(log, ["a", "b", "c"]); // recalc all
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("async function can track sync dependency", async () => {
|
|
137
|
+
const cache = new SystemCacheMap();
|
|
138
|
+
|
|
139
|
+
// Create cache entries for
|
|
140
|
+
// { a = b + 1, b = 2 }
|
|
141
|
+
const getB = () =>
|
|
142
|
+
cache.getOrInsertComputed("b", () => {
|
|
143
|
+
return 2;
|
|
144
|
+
});
|
|
145
|
+
const getA = async () =>
|
|
146
|
+
cache.getOrInsertComputedAsync("a", async () => {
|
|
147
|
+
const b = getB();
|
|
148
|
+
return b + 1;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const a1 = await getA();
|
|
152
|
+
assert.strictEqual(a1, 3);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("sync function can't have async dependency", async () => {
|
|
156
|
+
const cache = new SystemCacheMap();
|
|
157
|
+
|
|
158
|
+
// Create cache entries for
|
|
159
|
+
// { a = b, b = 1 }
|
|
160
|
+
const getB = async () =>
|
|
161
|
+
await cache.getOrInsertComputedAsync("b", async () => {
|
|
162
|
+
return 1;
|
|
163
|
+
});
|
|
164
|
+
const getA = () => cache.getOrInsertComputed("a", () => getB());
|
|
165
|
+
|
|
166
|
+
// a is sync but b is async
|
|
167
|
+
await assert.rejects(getA);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("implicit dependency of child path on parent path", async () => {
|
|
171
|
+
const cache = new SystemCacheMap();
|
|
172
|
+
|
|
173
|
+
// Create cache entries
|
|
174
|
+
// parent = 1, parent/child = 2, other = 3
|
|
175
|
+
const getParent = () => cache.getOrInsertComputed("parent", () => 1);
|
|
176
|
+
cache.getOrInsertComputed("parent/child", () => {
|
|
177
|
+
// Force access of parent, no explicit dependency should be recorded
|
|
178
|
+
getParent();
|
|
179
|
+
return 2;
|
|
180
|
+
});
|
|
181
|
+
cache.getOrInsertComputed("other", () => 3);
|
|
182
|
+
|
|
183
|
+
assert.deepEqual(cacheEntries(cache), [
|
|
184
|
+
["parent/child", { value: 2 }],
|
|
185
|
+
["parent", { value: 1 }],
|
|
186
|
+
["other", { value: 3 }],
|
|
187
|
+
]);
|
|
188
|
+
|
|
189
|
+
// Deleting parent implicitly deletes all children
|
|
190
|
+
cache.delete("parent");
|
|
191
|
+
assert.deepEqual(cacheEntries(cache), [["other", { value: 3 }]]);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("sync volatile values are not cached", async () => {
|
|
195
|
+
const cache = new SystemCacheMap();
|
|
196
|
+
let count = 0;
|
|
197
|
+
const getCount = () =>
|
|
198
|
+
cache.getOrInsertComputed("count", () => volatile(count++));
|
|
199
|
+
|
|
200
|
+
const first = getCount();
|
|
201
|
+
const second = getCount();
|
|
202
|
+
assert.strictEqual(Number(first), 0);
|
|
203
|
+
assert.strictEqual(Number(second), 1);
|
|
204
|
+
assert.strictEqual(count, 2); // computeFn ran twice, value not cached
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("async volatile values are not cached", async () => {
|
|
208
|
+
const cache = new SystemCacheMap();
|
|
209
|
+
let count = 0;
|
|
210
|
+
const getCount = () =>
|
|
211
|
+
cache.getOrInsertComputedAsync("count", async () => volatile(count++));
|
|
212
|
+
|
|
213
|
+
const first = await getCount();
|
|
214
|
+
const second = await getCount();
|
|
215
|
+
assert.strictEqual(Number(first), 0);
|
|
216
|
+
assert.strictEqual(Number(second), 1);
|
|
217
|
+
assert.strictEqual(count, 2); // computeFn ran twice, value not cached
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
export function cacheEntries(cache) {
|
|
222
|
+
const entries = [...cache.entries()];
|
|
223
|
+
return entries.map(([key, entry]) => [key, cacheEntry(entry)]);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function cacheEntry(entry) {
|
|
227
|
+
const { downstreams, value } = entry;
|
|
228
|
+
|
|
229
|
+
const result = { value };
|
|
230
|
+
if (downstreams?.size > 0) {
|
|
231
|
+
result.downstreams = [...downstreams];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (entry.upstreams?.size > 0) {
|
|
235
|
+
result.upstreams = [...entry.upstreams];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AsyncMap, SyncMap } from "@weborigami/async-tree";
|
|
2
|
+
import AsyncCacheTransform from "../../src/runtime/AsyncCacheTransform.js";
|
|
3
|
+
import SyncCacheTransform from "../../src/runtime/SyncCacheTransform.js";
|
|
4
|
+
|
|
5
|
+
export default function asyncCalcs(iterable) {
|
|
6
|
+
const data = new (SyncCacheTransform(SyncMap))(iterable);
|
|
7
|
+
const calcs = new (AsyncCacheTransform(AsyncResultsMap))(data);
|
|
8
|
+
return { calcs, data };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class AsyncResultsMap extends AsyncMap {
|
|
12
|
+
constructor(source) {
|
|
13
|
+
super();
|
|
14
|
+
this.source = source;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async get(key) {
|
|
18
|
+
let value = this.source.get(key);
|
|
19
|
+
if (typeof value === "function") {
|
|
20
|
+
value = await value();
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async *keys() {
|
|
26
|
+
yield* this.source.keys();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { beforeEach, describe, test } from "node:test";
|
|
3
|
+
import enableValueCaching from "../../src/runtime/enableValueCaching.js";
|
|
4
|
+
import { cachePathSymbol } from "../../src/runtime/symbols.js";
|
|
5
|
+
import systemCache from "../../src/runtime/systemCache.js";
|
|
6
|
+
|
|
7
|
+
describe("enableValueCaching", () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
systemCache.clear();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("leaves primitive values as is", () => {
|
|
13
|
+
const value1 = enableValueCaching(1, "foo.ori/");
|
|
14
|
+
assert.equal(value1, 1);
|
|
15
|
+
|
|
16
|
+
const value2 = enableValueCaching("hello", "foo.ori/");
|
|
17
|
+
assert.equal(value2, "hello");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("adds cachePath to plain object", () => {
|
|
21
|
+
const obj = { a: 1, b: 2 };
|
|
22
|
+
const value = enableValueCaching(obj, "foo.ori/");
|
|
23
|
+
assert.equal(value, obj);
|
|
24
|
+
assert.equal(value[cachePathSymbol], "foo.ori/");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("adds cachePath to array", () => {
|
|
28
|
+
const arr = [1, 2, 3];
|
|
29
|
+
const value = enableValueCaching(arr, "foo.ori/");
|
|
30
|
+
assert.equal(value, arr);
|
|
31
|
+
assert.equal(value[cachePathSymbol], "foo.ori/");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("applies cache transform to Map", () => {
|
|
35
|
+
const map = new Map([
|
|
36
|
+
["a", 1],
|
|
37
|
+
["b", 2],
|
|
38
|
+
]);
|
|
39
|
+
const value = enableValueCaching(map, "foo.ori/");
|
|
40
|
+
assert(value instanceof Map);
|
|
41
|
+
assert.equal(value[cachePathSymbol], "foo.ori/");
|
|
42
|
+
assert.equal(value.get("a"), 1);
|
|
43
|
+
assert(systemCache.has("foo.ori/a"));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("applies cache to function with string arguments", () => {
|
|
47
|
+
function fn(x, y) {
|
|
48
|
+
return `${x} ${y}`;
|
|
49
|
+
}
|
|
50
|
+
const value = enableValueCaching(fn, "foo.ori/");
|
|
51
|
+
assert(typeof value === "function");
|
|
52
|
+
assert.equal(value("Hello", "world"), "Hello world");
|
|
53
|
+
assert.equal(value[cachePathSymbol], "foo.ori/");
|
|
54
|
+
});
|
|
55
|
+
});
|