@weborigami/language 0.6.17 → 0.7.0-beta.1
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 +6 -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/getSource.js +11 -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 +17 -10
- 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/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/ori_handler.test.js +22 -3
- package/test/handlers/oridocument_handler.test.js +1 -1
- 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
|
@@ -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
|
+
});
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
import { ObjectMap } from "@weborigami/async-tree";
|
|
1
2
|
import assert from "node:assert";
|
|
2
|
-
import { describe, test } from "node:test";
|
|
3
|
+
import { beforeEach, describe, test } from "node:test";
|
|
3
4
|
import coreGlobals from "../../src/project/coreGlobals.js";
|
|
4
5
|
import { formatError } from "../../src/runtime/errors.js";
|
|
5
6
|
import evaluate from "../../src/runtime/evaluate.js";
|
|
7
|
+
import systemCache from "../../src/runtime/systemCache.js";
|
|
6
8
|
|
|
7
9
|
const globals = await coreGlobals();
|
|
8
10
|
|
|
9
11
|
describe("formatError", () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
systemCache.clear();
|
|
14
|
+
});
|
|
15
|
+
|
|
10
16
|
describe("ReferenceError", () => {
|
|
11
17
|
test("identifies an undefined function", async () => {
|
|
12
18
|
await assertError(
|
|
@@ -17,7 +23,7 @@ evaluating: \x1b[31mdoesntExist\x1b[0m`,
|
|
|
17
23
|
);
|
|
18
24
|
});
|
|
19
25
|
|
|
20
|
-
test("identifies the argument that produced an error", async () => {
|
|
26
|
+
test.skip("identifies the argument that produced an error", async () => {
|
|
21
27
|
await assertError(
|
|
22
28
|
`Tree.map(foo, (_) => _)`,
|
|
23
29
|
`ReferenceError: Tree.map: The map argument wasn't defined.
|
|
@@ -73,9 +79,9 @@ evaluating: \x1B[31mdatu.toString\x1B[0m
|
|
|
73
79
|
});
|
|
74
80
|
|
|
75
81
|
test("proposes typos using scope keys", async () => {
|
|
76
|
-
const parent = {
|
|
82
|
+
const parent = new ObjectMap({
|
|
77
83
|
"index.ori": "Index page",
|
|
78
|
-
};
|
|
84
|
+
});
|
|
79
85
|
await assertError(
|
|
80
86
|
`index.orj(1, 2, 3)`,
|
|
81
87
|
`ReferenceError: Couldn't find the function or map to execute.
|
|
@@ -170,11 +176,14 @@ evaluating: \x1B[31mposts.md\x1B[0m
|
|
|
170
176
|
});
|
|
171
177
|
|
|
172
178
|
test("handles a traversal failure inside a reference error", async () => {
|
|
173
|
-
const parent =
|
|
174
|
-
|
|
175
|
-
|
|
179
|
+
const parent = new ObjectMap(
|
|
180
|
+
{
|
|
181
|
+
post1: {
|
|
182
|
+
title: "First post",
|
|
183
|
+
},
|
|
176
184
|
},
|
|
177
|
-
|
|
185
|
+
{ deep: true },
|
|
186
|
+
);
|
|
178
187
|
await assertError(
|
|
179
188
|
`(post1/totle).toUpperCase()`,
|
|
180
189
|
`ReferenceError: Tried to get a property of something that doesn't exist.
|
|
@@ -197,16 +206,19 @@ evaluating: \x1B[31mrepeat\x1B[0m`,
|
|
|
197
206
|
|
|
198
207
|
describe("TraverseError", () => {
|
|
199
208
|
test("suggests typos for failed path", async () => {
|
|
200
|
-
const parent =
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
209
|
+
const parent = new ObjectMap(
|
|
210
|
+
{
|
|
211
|
+
a: {
|
|
212
|
+
b: {
|
|
213
|
+
sub: {
|
|
214
|
+
// need 2+ characters for typos
|
|
215
|
+
c: 1,
|
|
216
|
+
},
|
|
206
217
|
},
|
|
207
218
|
},
|
|
208
219
|
},
|
|
209
|
-
|
|
220
|
+
{ deep: true },
|
|
221
|
+
);
|
|
210
222
|
await assertError(
|
|
211
223
|
`a/b/sup/c`,
|
|
212
224
|
`TraverseError: A path hit a null or undefined value.
|
|
@@ -219,9 +231,7 @@ evaluating: \x1B[31msup/\x1B[0m`,
|
|
|
219
231
|
});
|
|
220
232
|
|
|
221
233
|
test("identifies when a numeric key failed", async () => {
|
|
222
|
-
const parent =
|
|
223
|
-
map: new Map([[1, new Map([["a", true]])]]),
|
|
224
|
-
};
|
|
234
|
+
const parent = new Map([["map", new Map([[1, new Map([["a", true]])]])]]);
|
|
225
235
|
await assertError(
|
|
226
236
|
`map/1/a`,
|
|
227
237
|
`TraverseError: A path hit a null or undefined value.
|
|
@@ -235,12 +245,12 @@ evaluating: \x1B[31m1/\x1B[0m`,
|
|
|
235
245
|
});
|
|
236
246
|
|
|
237
247
|
test("identifies when a value couldn't be unpacked due to a missing extension handler", async () => {
|
|
238
|
-
const parent = {
|
|
248
|
+
const parent = new ObjectMap({
|
|
239
249
|
"file.foo": Uint8Array.from("hello"),
|
|
240
|
-
};
|
|
250
|
+
});
|
|
241
251
|
await assertError(
|
|
242
252
|
`file.foo/bar`,
|
|
243
|
-
`TraverseError: A path hit binary
|
|
253
|
+
`TraverseError: A path hit binary data that can't be unpacked.
|
|
244
254
|
Tried to traverse path: file.foo/bar
|
|
245
255
|
Stopped unexpectedly at: file.foo/
|
|
246
256
|
The value couldn't be unpacked because no file extension handler is registered for ".foo".
|
|
@@ -250,11 +260,14 @@ evaluating: \x1B[31mfile.foo/\x1B[0m`,
|
|
|
250
260
|
});
|
|
251
261
|
|
|
252
262
|
test("identifies when data was already unpacked", async () => {
|
|
253
|
-
const parent =
|
|
254
|
-
|
|
255
|
-
|
|
263
|
+
const parent = new ObjectMap(
|
|
264
|
+
{
|
|
265
|
+
a: {
|
|
266
|
+
"b.json": 1,
|
|
267
|
+
},
|
|
256
268
|
},
|
|
257
|
-
|
|
269
|
+
{ deep: true },
|
|
270
|
+
);
|
|
258
271
|
await assertError(
|
|
259
272
|
`a/b.json/`,
|
|
260
273
|
`TraverseError: A path tried to unpack data that's already unpacked.
|
|
@@ -267,9 +280,12 @@ evaluating: \x1B[31mb.json/\x1B[0m`,
|
|
|
267
280
|
});
|
|
268
281
|
|
|
269
282
|
test("identifies when a value didn't exist to be unpacked", async () => {
|
|
270
|
-
const parent =
|
|
271
|
-
|
|
272
|
-
|
|
283
|
+
const parent = new ObjectMap(
|
|
284
|
+
{
|
|
285
|
+
a: {},
|
|
286
|
+
},
|
|
287
|
+
{ deep: true },
|
|
288
|
+
);
|
|
273
289
|
await assertError(
|
|
274
290
|
`a/b/`,
|
|
275
291
|
`TraverseError: A path tried to unpack a value that doesn't exist.
|
|
@@ -282,12 +298,19 @@ evaluating: \x1B[31mb/\x1B[0m`,
|
|
|
282
298
|
});
|
|
283
299
|
});
|
|
284
300
|
|
|
285
|
-
async function assertError(
|
|
301
|
+
async function assertError(expression, expectedMessage, options) {
|
|
286
302
|
try {
|
|
303
|
+
const source =
|
|
304
|
+
typeof expression !== "string"
|
|
305
|
+
? expression
|
|
306
|
+
: {
|
|
307
|
+
text: expression,
|
|
308
|
+
relativePath: "test.ori",
|
|
309
|
+
};
|
|
287
310
|
await evaluate(source, {
|
|
288
311
|
globals,
|
|
289
312
|
object: null,
|
|
290
|
-
parent:
|
|
313
|
+
parent: null,
|
|
291
314
|
stack: [],
|
|
292
315
|
...options,
|
|
293
316
|
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Tree } from "@weborigami/async-tree";
|
|
1
|
+
import { ObjectMap, Tree } from "@weborigami/async-tree";
|
|
2
2
|
import assert from "node:assert";
|
|
3
|
-
import { describe, test } from "node:test";
|
|
3
|
+
import { beforeEach, describe, test } from "node:test";
|
|
4
4
|
import evaluate from "../../src/runtime/evaluate.js";
|
|
5
|
+
import systemCache from "../../src/runtime/systemCache.js";
|
|
5
6
|
|
|
6
7
|
const globals = {
|
|
7
8
|
concat: (...args) => args.join(""),
|
|
@@ -10,6 +11,10 @@ const globals = {
|
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
describe("evaluate", () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
systemCache.clear();
|
|
16
|
+
});
|
|
17
|
+
|
|
13
18
|
test("array", async () => {
|
|
14
19
|
await assertEvaluation("[]", []);
|
|
15
20
|
await assertEvaluation("[ 1, 2, 3, ]", [1, 2, 3]);
|
|
@@ -31,9 +36,9 @@ describe("evaluate", () => {
|
|
|
31
36
|
|
|
32
37
|
test("angle bracket path", async () => {
|
|
33
38
|
await assertEvaluation("<data>", "Bob", {
|
|
34
|
-
parent: {
|
|
39
|
+
parent: new ObjectMap({
|
|
35
40
|
data: "Bob",
|
|
36
|
-
},
|
|
41
|
+
}),
|
|
37
42
|
});
|
|
38
43
|
});
|
|
39
44
|
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
|
-
import { describe, test } from "node:test";
|
|
2
|
+
import { beforeEach, describe, test } from "node:test";
|
|
3
3
|
|
|
4
4
|
import { SyncMap } from "@weborigami/async-tree";
|
|
5
5
|
import execute from "../../src/runtime/execute.js";
|
|
6
|
+
import systemCache from "../../src/runtime/systemCache.js";
|
|
6
7
|
import { createCode } from "../compiler/codeHelpers.js";
|
|
7
8
|
|
|
8
9
|
describe("execute", () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
systemCache.clear();
|
|
12
|
+
});
|
|
13
|
+
|
|
9
14
|
test("if object in function position isn't a function, can unpack it", async () => {
|
|
10
15
|
const fn = (...args) => args.join(",");
|
|
11
16
|
const packed = new String();
|
|
@@ -1,35 +1,59 @@
|
|
|
1
1
|
import { ObjectMap, symbols, SyncMap, Tree } from "@weborigami/async-tree";
|
|
2
2
|
import assert from "node:assert";
|
|
3
|
-
import { describe, test } from "node:test";
|
|
3
|
+
import { beforeEach, describe, test } from "node:test";
|
|
4
4
|
|
|
5
5
|
import expressionObject from "../../src/runtime/expressionObject.js";
|
|
6
6
|
import { ops } from "../../src/runtime/internal.js";
|
|
7
|
+
import { cachePathSymbol } from "../../src/runtime/symbols.js";
|
|
8
|
+
import systemCache from "../../src/runtime/systemCache.js";
|
|
7
9
|
|
|
8
10
|
describe("expressionObject", () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
systemCache.clear();
|
|
13
|
+
});
|
|
14
|
+
|
|
9
15
|
test("can instantiate an object", async () => {
|
|
10
|
-
const
|
|
16
|
+
const parent = new ObjectMap({
|
|
11
17
|
upper: (s) => s.toUpperCase(),
|
|
12
18
|
});
|
|
13
19
|
|
|
14
20
|
const entries = [
|
|
15
|
-
["hello", [[[ops.scope
|
|
16
|
-
["world", [[[ops.scope
|
|
21
|
+
["hello", [[[ops.scope], "upper"], "hello"]],
|
|
22
|
+
["world", [[[ops.scope], "upper"], "world"]],
|
|
17
23
|
];
|
|
18
24
|
const context = new SyncMap();
|
|
19
25
|
|
|
20
|
-
const object = await expressionObject(entries, {
|
|
26
|
+
const object = await expressionObject(entries, {
|
|
27
|
+
object: context,
|
|
28
|
+
parent,
|
|
29
|
+
});
|
|
21
30
|
assert.equal(await object.hello, "HELLO");
|
|
22
31
|
assert.equal(await object.world, "WORLD");
|
|
23
32
|
assert.equal(object[symbols.parent], context);
|
|
24
33
|
});
|
|
25
34
|
|
|
26
|
-
test("
|
|
35
|
+
test("static property", async () => {
|
|
36
|
+
let count = 0;
|
|
37
|
+
const increment = () => count++;
|
|
38
|
+
const entries = [["count", [increment]]];
|
|
39
|
+
const object = await expressionObject(entries);
|
|
40
|
+
const propertyDescriptor = Object.getOwnPropertyDescriptor(object, "count");
|
|
41
|
+
assert.equal(propertyDescriptor?.value, 0);
|
|
42
|
+
assert.equal(object.count, 0);
|
|
43
|
+
assert.equal(count, 1); // getter should have been called
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("with caching on, property getter uses system cache", async () => {
|
|
27
47
|
let count = 0;
|
|
28
48
|
const increment = () => count++;
|
|
29
49
|
const entries = [["count", [ops.getter, [increment]]]];
|
|
30
50
|
const object = await expressionObject(entries);
|
|
51
|
+
object[cachePathSymbol] = "foo.ori/"; // enable caching on this object tree
|
|
31
52
|
assert.equal(await object.count, 0);
|
|
32
|
-
|
|
53
|
+
const propertyDescriptor = Object.getOwnPropertyDescriptor(object, "count");
|
|
54
|
+
assert(propertyDescriptor?.get); // should still be a getter
|
|
55
|
+
assert.equal(count, 1); // getter should have been called
|
|
56
|
+
assert.equal(await object.count, 0); // getter result should be cached
|
|
33
57
|
});
|
|
34
58
|
|
|
35
59
|
test("treats a getter for a primitive value as a regular property", async () => {
|
|
@@ -75,10 +99,16 @@ describe("expressionObject", () => {
|
|
|
75
99
|
});
|
|
76
100
|
});
|
|
77
101
|
|
|
78
|
-
test
|
|
102
|
+
test("returned object values can be unpacked", async () => {
|
|
79
103
|
const entries = [["data.json", `{ "a": 1 }`]];
|
|
80
104
|
const context = new SyncMap();
|
|
81
|
-
const
|
|
105
|
+
const globals = {
|
|
106
|
+
json_handler: { unpack: (data) => JSON.parse(data) },
|
|
107
|
+
};
|
|
108
|
+
const result = await expressionObject(entries, {
|
|
109
|
+
object: context,
|
|
110
|
+
globals,
|
|
111
|
+
});
|
|
82
112
|
const dataJson = await result["data.json"];
|
|
83
113
|
const json = await dataJson.unpack();
|
|
84
114
|
assert.deepEqual(json, { a: 1 });
|
|
@@ -115,11 +145,21 @@ describe("expressionObject", () => {
|
|
|
115
145
|
]);
|
|
116
146
|
});
|
|
117
147
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
// assert.equal(noGetter[symbols.async], undefined);
|
|
148
|
+
test("tracks dependencies on upstream values", async () => {
|
|
149
|
+
systemCache.clear();
|
|
121
150
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
151
|
+
// The `number` property gets a value from the cache
|
|
152
|
+
const getNumber = () =>
|
|
153
|
+
systemCache.getOrInsertComputed("dependency", () => 1);
|
|
154
|
+
const entries = [["number", [ops.getter, [getNumber]]]];
|
|
155
|
+
const object = await expressionObject(entries);
|
|
156
|
+
object[cachePathSymbol] = "src/test.ori/"; // enable caching on this object tree
|
|
157
|
+
const number = await object.number;
|
|
158
|
+
assert.equal(number, 1);
|
|
159
|
+
|
|
160
|
+
const dependencyEntry = systemCache.get("dependency");
|
|
161
|
+
const objectEntry = systemCache.get("src/test.ori/number");
|
|
162
|
+
assert(dependencyEntry.downstreams.has("src/test.ori/number"));
|
|
163
|
+
assert(objectEntry.upstreams.has("dependency"));
|
|
164
|
+
});
|
|
125
165
|
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import json_handler from "../../src/handlers/json_handler.js";
|
|
4
|
+
import fetchAndHandleExtension from "../../src/protocols/fetchAndHandleExtension.js";
|
|
5
|
+
|
|
6
|
+
describe("fetchAndHandleExtension", () => {
|
|
7
|
+
test("can unpack based on MIME content type", async () => {
|
|
8
|
+
/** @type {any} */
|
|
9
|
+
const parent = new Map();
|
|
10
|
+
parent.globals = {
|
|
11
|
+
json_handler,
|
|
12
|
+
};
|
|
13
|
+
const buffer = await fetchAndHandleExtension(
|
|
14
|
+
"https://weborigami.org/samples/help/pet.json",
|
|
15
|
+
null,
|
|
16
|
+
{
|
|
17
|
+
parent,
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
const data = await buffer.unpack();
|
|
22
|
+
assert.strictEqual(data.name, "Fluffy");
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"Hello"
|