@weborigami/language 0.6.16 → 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 +20 -11
- 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
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { Tree } from "@weborigami/async-tree";
|
|
2
|
-
import assignPropertyDescriptors from "../runtime/assignPropertyDescriptors.js";
|
|
1
|
+
import { Tree, assignPropertyDescriptors } from "@weborigami/async-tree";
|
|
3
2
|
import coreGlobals from "./coreGlobals.js";
|
|
4
3
|
import projectConfig from "./projectConfig.js";
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
*
|
|
6
|
+
* Make the complete set of globals available to code running in the given
|
|
8
7
|
* container. This will be the core globals plus any configuration specified in
|
|
9
8
|
* the project's config.ori file.
|
|
10
9
|
*
|
|
@@ -12,17 +11,13 @@ import projectConfig from "./projectConfig.js";
|
|
|
12
11
|
* container.
|
|
13
12
|
*
|
|
14
13
|
* @typedef {import("@weborigami/async-tree").SyncOrAsyncMap} SyncOrAsyncMap
|
|
15
|
-
* @param {SyncOrAsyncMap
|
|
14
|
+
* @param {SyncOrAsyncMap} parent
|
|
16
15
|
*/
|
|
17
|
-
export default async function
|
|
18
|
-
|
|
19
|
-
return coreGlobals();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const projectRoot = await Tree.root(parent);
|
|
16
|
+
export default async function initializeGlobalsForTree(parent) {
|
|
17
|
+
const projectRoot = Tree.root(parent);
|
|
23
18
|
if (!projectRoot.globals) {
|
|
24
|
-
// Start with core globals
|
|
25
|
-
const globals = await coreGlobals();
|
|
19
|
+
// Start with a copy of the core globals
|
|
20
|
+
const globals = { ...(await coreGlobals()) };
|
|
26
21
|
|
|
27
22
|
if (parent) {
|
|
28
23
|
// Get config for the given container and add it to the globals. During
|
|
@@ -35,7 +30,7 @@ export default async function projectGlobals(parent) {
|
|
|
35
30
|
assignPropertyDescriptors(globals, config);
|
|
36
31
|
}
|
|
37
32
|
|
|
38
|
-
//
|
|
33
|
+
// Store globals on project root
|
|
39
34
|
projectRoot.globals = globals;
|
|
40
35
|
}
|
|
41
36
|
|
package/src/project/jsGlobals.js
CHANGED
|
@@ -10,8 +10,8 @@ import coreGlobals from "./coreGlobals.js";
|
|
|
10
10
|
* @typedef {import("@weborigami/async-tree").SyncOrAsyncMap} SyncOrAsyncMap
|
|
11
11
|
* @param {SyncOrAsyncMap} parent
|
|
12
12
|
*/
|
|
13
|
-
export default async function
|
|
14
|
-
const projectRoot =
|
|
13
|
+
export default async function projectConfig(parent) {
|
|
14
|
+
const projectRoot = Tree.root(parent);
|
|
15
15
|
|
|
16
16
|
let configObject = {};
|
|
17
17
|
const configBuffer = await projectRoot.get("config.ori");
|
|
@@ -8,11 +8,11 @@ import { pathFromKeys } from "@weborigami/async-tree";
|
|
|
8
8
|
* @param {string[]} keys
|
|
9
9
|
*/
|
|
10
10
|
export default function constructHref(protocol, host, ...keys) {
|
|
11
|
+
let href = host.endsWith("/") ? host.slice(0, -1) : host;
|
|
11
12
|
const path = pathFromKeys(keys);
|
|
12
|
-
if (
|
|
13
|
-
|
|
13
|
+
if (path) {
|
|
14
|
+
href += "/" + path;
|
|
14
15
|
}
|
|
15
|
-
let href = [host, path].join("/");
|
|
16
16
|
if (!href.startsWith(protocol)) {
|
|
17
17
|
if (!href.startsWith("//")) {
|
|
18
18
|
href = `//${href}`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { trailingSlash } from "@weborigami/async-tree";
|
|
2
2
|
import HandleExtensionsTransform from "../runtime/HandleExtensionsTransform.js";
|
|
3
|
+
import systemCache from "../runtime/systemCache.js";
|
|
3
4
|
import constructHref from "./constructHref.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -12,7 +13,12 @@ import constructHref from "./constructHref.js";
|
|
|
12
13
|
* @param {string} host
|
|
13
14
|
* @param {string[]} keys
|
|
14
15
|
*/
|
|
15
|
-
export default function constructSiteTree(
|
|
16
|
+
export default async function constructSiteTree(
|
|
17
|
+
protocol,
|
|
18
|
+
mapClass,
|
|
19
|
+
host,
|
|
20
|
+
...keys
|
|
21
|
+
) {
|
|
16
22
|
// If the last key doesn't end in a slash, remove it for now.
|
|
17
23
|
let lastKey;
|
|
18
24
|
if (keys.length > 0 && keys.at(-1) && !trailingSlash.has(keys.at(-1))) {
|
|
@@ -20,7 +26,10 @@ export default function constructSiteTree(protocol, mapClass, host, ...keys) {
|
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
const href = constructHref(protocol, host, ...keys);
|
|
23
|
-
|
|
29
|
+
const result = await systemCache.getOrInsertComputedAsync(
|
|
30
|
+
href,
|
|
31
|
+
() => new (HandleExtensionsTransform(mapClass))(href),
|
|
32
|
+
);
|
|
24
33
|
|
|
25
34
|
return lastKey ? result.get(lastKey) : result;
|
|
26
35
|
}
|
package/src/protocols/explore.js
CHANGED
|
@@ -8,6 +8,6 @@ import constructSiteTree from "./constructSiteTree.js";
|
|
|
8
8
|
* @param {string} host
|
|
9
9
|
* @param {...string} keys
|
|
10
10
|
*/
|
|
11
|
-
export default function explore(host, ...keys) {
|
|
11
|
+
export default async function explore(host, ...keys) {
|
|
12
12
|
return constructSiteTree("https:", ExplorableSiteMap, host, ...keys);
|
|
13
13
|
}
|
|
@@ -8,6 +8,6 @@ import constructSiteTree from "./constructSiteTree.js";
|
|
|
8
8
|
* @param {string} host
|
|
9
9
|
* @param {...string} keys
|
|
10
10
|
*/
|
|
11
|
-
export default function explorehttp(host, ...keys) {
|
|
11
|
+
export default async function explorehttp(host, ...keys) {
|
|
12
12
|
return constructSiteTree("http:", ExplorableSiteMap, host, ...keys);
|
|
13
13
|
}
|
|
@@ -1,18 +1,26 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { args, Tree } from "@weborigami/async-tree";
|
|
2
|
+
import handleExtension from "../../src/runtime/handleExtension.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Extend the JavaScript `fetch` function to implicity return an ArrayBuffer
|
|
6
|
+
* with an unpack() method if the resource has a known file extension or MIME
|
|
7
|
+
* type.
|
|
7
8
|
*
|
|
8
9
|
* @param {string} href
|
|
9
|
-
* @param {SyncOrAsyncMap} parent
|
|
10
10
|
*/
|
|
11
|
-
export default async function fetchAndHandleExtension(href,
|
|
12
|
-
|
|
11
|
+
export default async function fetchAndHandleExtension(href, options, state) {
|
|
12
|
+
if (options && state === undefined) {
|
|
13
|
+
// Options weren't provided
|
|
14
|
+
state = options;
|
|
15
|
+
options = undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
href = args.string(href, "Origami.fetch");
|
|
19
|
+
const response = await fetch(href, options);
|
|
13
20
|
if (!response.ok) {
|
|
14
21
|
return undefined;
|
|
15
22
|
}
|
|
23
|
+
|
|
16
24
|
let buffer = await response.arrayBuffer();
|
|
17
25
|
|
|
18
26
|
const mediaType = response.headers.get("Content-Type");
|
|
@@ -20,12 +28,16 @@ export default async function fetchAndHandleExtension(href, parent) {
|
|
|
20
28
|
/** @type {any} */ (buffer).mediaType = mediaType;
|
|
21
29
|
}
|
|
22
30
|
|
|
23
|
-
// Attach any
|
|
31
|
+
// Attach any handler defined for the file type or MIME type.
|
|
32
|
+
const parent = state?.parent;
|
|
24
33
|
const url = new URL(href);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
if (parent) {
|
|
35
|
+
const root = await Tree.root(parent);
|
|
36
|
+
const globals = root.globals;
|
|
37
|
+
const filename = url.pathname.split("/").pop();
|
|
38
|
+
buffer = await handleExtension(buffer, filename, globals, parent);
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
return buffer;
|
|
31
42
|
}
|
|
43
|
+
fetchAndHandleExtension.needsState = true;
|
package/src/protocols/files.js
CHANGED
package/src/protocols/http.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import systemCache from "../runtime/systemCache.js";
|
|
1
2
|
import constructHref from "./constructHref.js";
|
|
2
3
|
import fetchAndHandleExtension from "./fetchAndHandleExtension.js";
|
|
3
4
|
|
|
@@ -11,6 +12,8 @@ import fetchAndHandleExtension from "./fetchAndHandleExtension.js";
|
|
|
11
12
|
export default async function http(host, ...keys) {
|
|
12
13
|
const state = keys.pop();
|
|
13
14
|
const href = constructHref("http:", host, ...keys);
|
|
14
|
-
return
|
|
15
|
+
return systemCache.getOrInsertComputedAsync(href, () =>
|
|
16
|
+
fetchAndHandleExtension(href, null, state),
|
|
17
|
+
);
|
|
15
18
|
}
|
|
16
19
|
http.needsState = true;
|
package/src/protocols/https.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import systemCache from "../runtime/systemCache.js";
|
|
1
2
|
import constructHref from "./constructHref.js";
|
|
2
3
|
import fetchAndHandleExtension from "./fetchAndHandleExtension.js";
|
|
3
4
|
|
|
@@ -11,6 +12,8 @@ import fetchAndHandleExtension from "./fetchAndHandleExtension.js";
|
|
|
11
12
|
export default async function https(host, ...keys) {
|
|
12
13
|
const state = keys.pop();
|
|
13
14
|
const href = constructHref("https:", host, ...keys);
|
|
14
|
-
return
|
|
15
|
+
return systemCache.getOrInsertComputedAsync(href, () =>
|
|
16
|
+
fetchAndHandleExtension(href, null, state),
|
|
17
|
+
);
|
|
15
18
|
}
|
|
16
19
|
https.needsState = true;
|
|
@@ -8,6 +8,6 @@ import constructSiteTree from "./constructSiteTree.js";
|
|
|
8
8
|
* @param {string} host
|
|
9
9
|
* @param {...string} keys
|
|
10
10
|
*/
|
|
11
|
-
export default function httpstree(host, ...keys) {
|
|
11
|
+
export default async function httpstree(host, ...keys) {
|
|
12
12
|
return constructSiteTree("https:", SiteMap, host, ...keys);
|
|
13
13
|
}
|
|
@@ -8,6 +8,6 @@ import constructSiteTree from "./constructSiteTree.js";
|
|
|
8
8
|
* @param {string} host
|
|
9
9
|
* @param {...string} keys
|
|
10
10
|
*/
|
|
11
|
-
export default function httptree(host, ...keys) {
|
|
11
|
+
export default async function httptree(host, ...keys) {
|
|
12
12
|
return constructSiteTree("http:", SiteMap, host, ...keys);
|
|
13
13
|
}
|
package/src/protocols/package.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Tree, keysFromPath, pathFromKeys } from "@weborigami/async-tree";
|
|
2
2
|
import projectRoot from "../project/projectRoot.js";
|
|
3
|
+
import systemCache from "../runtime/systemCache.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* The package: protocol handler
|
|
@@ -9,15 +10,27 @@ import projectRoot from "../project/projectRoot.js";
|
|
|
9
10
|
export default async function packageProtocol(...args) {
|
|
10
11
|
const state = args.pop(); // Remaining args are the path
|
|
11
12
|
const root = await projectRoot(state);
|
|
13
|
+
const path = pathFromKeys(args);
|
|
14
|
+
if (!path) {
|
|
15
|
+
throw new Error("package: protocol requires a package name");
|
|
16
|
+
}
|
|
17
|
+
const href = `package:${path}`;
|
|
18
|
+
const result = await systemCache.getOrInsertComputedAsync(href, () =>
|
|
19
|
+
loadPackage(root, args),
|
|
20
|
+
);
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
packageProtocol.needsState = true;
|
|
12
24
|
|
|
25
|
+
async function loadPackage(root, args) {
|
|
13
26
|
// Identify the path to the package root
|
|
14
|
-
const packageRootKeys = ["node_modules"];
|
|
27
|
+
const packageRootKeys = ["node_modules/"];
|
|
15
28
|
let name = args.shift();
|
|
16
29
|
packageRootKeys.push(name);
|
|
17
30
|
if (name.startsWith("@")) {
|
|
18
31
|
// First key is an npm organization, add next key as name
|
|
19
32
|
const nameArg = args.shift();
|
|
20
|
-
name
|
|
33
|
+
name = pathFromKeys([name, nameArg]);
|
|
21
34
|
packageRootKeys.push(nameArg);
|
|
22
35
|
}
|
|
23
36
|
const packageRootPath = pathFromKeys(packageRootKeys);
|
|
@@ -60,4 +73,3 @@ export default async function packageProtocol(...args) {
|
|
|
60
73
|
|
|
61
74
|
return result;
|
|
62
75
|
}
|
|
63
|
-
packageProtocol.needsState = true;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import enableValueCaching from "./enableValueCaching.js";
|
|
2
|
+
import { cachePathSymbol, noCacheSymbol } from "./symbols.js";
|
|
3
|
+
import systemCache from "./systemCache.js";
|
|
4
|
+
import SystemCacheMap from "./SystemCacheMap.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* General-purpose mixin for Origami maps with dependency tracking, used for:
|
|
8
|
+
* files, site resources, and scope references in Origami files
|
|
9
|
+
*
|
|
10
|
+
* This wraps a map's get() and keys() methods to add caching and dependency tracking.
|
|
11
|
+
* It tracks which cached values are downstream of other cached values so that if
|
|
12
|
+
* an upstream value changes, all dependent downstream cached values can be
|
|
13
|
+
* invalidated efficiently.
|
|
14
|
+
*
|
|
15
|
+
* Cache entries look like:
|
|
16
|
+
*
|
|
17
|
+
* key -> {
|
|
18
|
+
* downstreams: Set(path),
|
|
19
|
+
* value
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* This allows for efficiently evicting all a value and all its downstream
|
|
23
|
+
* dependent cached values.
|
|
24
|
+
*
|
|
25
|
+
* Example project:
|
|
26
|
+
*
|
|
27
|
+
* site.ori loads a.ori and b.ori
|
|
28
|
+
* a.ori loads c.ori
|
|
29
|
+
* b.ori loads c.ori
|
|
30
|
+
* c.ori doesn't load anything
|
|
31
|
+
*
|
|
32
|
+
* Resulting cache:
|
|
33
|
+
*
|
|
34
|
+
* site.ori -> { value: ... }
|
|
35
|
+
* a.ori -> { downstreams: Set(site.ori), value: ... }
|
|
36
|
+
* b.ori -> { downstreams: Set(site.ori), value: ... }
|
|
37
|
+
* c.ori -> { downstreams: Set(a.ori, b.ori), value: ... }
|
|
38
|
+
*/
|
|
39
|
+
export default function AsyncCacheTransform(Base) {
|
|
40
|
+
return class AsyncCache extends Base {
|
|
41
|
+
constructor(...args) {
|
|
42
|
+
super(...args);
|
|
43
|
+
|
|
44
|
+
// Expose cache for debugging
|
|
45
|
+
this.cache = systemCache;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get cachePath() {
|
|
49
|
+
// @ts-ignore
|
|
50
|
+
return this[cachePathSymbol];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
cachePathForKey(key) {
|
|
54
|
+
return key === "."
|
|
55
|
+
? this.cachePath
|
|
56
|
+
: SystemCacheMap.joinPath(this.cachePath, key);
|
|
57
|
+
}
|
|
58
|
+
|
|
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
|
+
}
|
|
66
|
+
|
|
67
|
+
async get(key) {
|
|
68
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
69
|
+
// Non-string keys and non-empty strings can't be cached
|
|
70
|
+
return super.get(key);
|
|
71
|
+
}
|
|
72
|
+
const cachePath = this.cachePathForKey(key);
|
|
73
|
+
const value = await systemCache.getOrInsertComputedAsync(
|
|
74
|
+
cachePath,
|
|
75
|
+
async () => {
|
|
76
|
+
let result = await super.get(key);
|
|
77
|
+
if (result !== undefined) {
|
|
78
|
+
// @ts-ignore
|
|
79
|
+
if (this[noCacheSymbol]) {
|
|
80
|
+
result[noCacheSymbol] = true;
|
|
81
|
+
} else {
|
|
82
|
+
result = enableValueCaching(result, cachePath);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
invalidateKeys() {
|
|
92
|
+
const keysPath = this.cachePathForKey("_keys");
|
|
93
|
+
systemCache.delete(keysPath);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async *keys() {
|
|
97
|
+
const keysPath = this.cachePathForKey("_keys");
|
|
98
|
+
const keys = await systemCache.getOrInsertComputedAsync(
|
|
99
|
+
keysPath,
|
|
100
|
+
async () => {
|
|
101
|
+
// We can't cache an iterator; convert to array
|
|
102
|
+
const result = [];
|
|
103
|
+
for await (const key of super.keys()) {
|
|
104
|
+
result.push(key);
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
yield* keys;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
onKeysChange(key) {
|
|
113
|
+
super.onKeysChange?.(key);
|
|
114
|
+
this.invalidateKeys();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
onValueChange(key) {
|
|
118
|
+
super.onValueChange?.(key);
|
|
119
|
+
systemCache.delete(this.cachePathForKey(key));
|
|
120
|
+
}
|
|
121
|
+
|
|
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
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import getGlobalsForTree from "../project/getGlobalsForTree.js";
|
|
2
|
+
import initializeGlobalsForTree from "../project/initializeGlobalsForTree.js";
|
|
1
3
|
import handleExtension from "./handleExtension.js";
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -13,11 +15,25 @@ export default function HandleExtensionsTransform(Base) {
|
|
|
13
15
|
return super.delete(key);
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Initialize the globals on the project root. This makes the file handlers
|
|
20
|
+
* available for use from any folder inside the tree.
|
|
21
|
+
*
|
|
22
|
+
* This is an async operation because it can load JavaScript files, so it
|
|
23
|
+
* can't be done in the constructor.
|
|
24
|
+
*/
|
|
25
|
+
async initializeGlobals() {
|
|
26
|
+
await initializeGlobalsForTree(this);
|
|
27
|
+
}
|
|
28
|
+
|
|
16
29
|
get(key) {
|
|
30
|
+
const globals = getGlobalsForTree(this);
|
|
17
31
|
const value = super.get(key);
|
|
18
32
|
return value instanceof Promise
|
|
19
|
-
? value.then((resolved) =>
|
|
20
|
-
|
|
33
|
+
? value.then((resolved) =>
|
|
34
|
+
handleExtension(resolved, key, globals, this),
|
|
35
|
+
)
|
|
36
|
+
: handleExtension(value, key, globals, this);
|
|
21
37
|
}
|
|
22
38
|
|
|
23
39
|
// See delete()
|
|
@@ -2,8 +2,11 @@ import { FileMap } from "@weborigami/async-tree";
|
|
|
2
2
|
import EventTargetMixin from "./EventTargetMixin.js";
|
|
3
3
|
import HandleExtensionsTransform from "./HandleExtensionsTransform.js";
|
|
4
4
|
import ImportModulesMixin from "./ImportModulesMixin.js";
|
|
5
|
+
import SyncCacheTransform from "./SyncCacheTransform.js";
|
|
5
6
|
import WatchFilesMixin from "./WatchFilesMixin.js";
|
|
6
7
|
|
|
7
|
-
export default class OrigamiFileMap extends HandleExtensionsTransform(
|
|
8
|
+
export default class OrigamiFileMap extends SyncCacheTransform(HandleExtensionsTransform(
|
|
8
9
|
ImportModulesMixin(WatchFilesMixin(EventTargetMixin(FileMap)))
|
|
9
|
-
) {
|
|
10
|
+
)) {
|
|
11
|
+
globals: any;
|
|
12
|
+
}
|
|
@@ -1,9 +1,32 @@
|
|
|
1
|
-
import { FileMap } from "@weborigami/async-tree";
|
|
1
|
+
import { FileMap, trailingSlash } from "@weborigami/async-tree";
|
|
2
2
|
import EventTargetMixin from "./EventTargetMixin.js";
|
|
3
3
|
import HandleExtensionsTransform from "./HandleExtensionsTransform.js";
|
|
4
4
|
import ImportModulesMixin from "./ImportModulesMixin.js";
|
|
5
|
+
import SyncCacheTransform from "./SyncCacheTransform.js";
|
|
5
6
|
import WatchFilesMixin from "./WatchFilesMixin.js";
|
|
7
|
+
import { noCacheSymbol } from "./symbols.js";
|
|
6
8
|
|
|
7
|
-
export default class OrigamiFileMap extends
|
|
8
|
-
|
|
9
|
-
)
|
|
9
|
+
export default class OrigamiFileMap extends SyncCacheTransform(
|
|
10
|
+
HandleExtensionsTransform(
|
|
11
|
+
ImportModulesMixin(WatchFilesMixin(EventTargetMixin(FileMap))),
|
|
12
|
+
),
|
|
13
|
+
) {
|
|
14
|
+
get cachePath() {
|
|
15
|
+
return super.cachePath ?? this.path;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Workaround to register file paths in the system cache without trailing
|
|
19
|
+
// slahes. This is so that if someone calls `get("site.ori/")`, the cache path
|
|
20
|
+
// will be "site.ori". It's not clear whether this is the best solution, but
|
|
21
|
+
// hopefully suffices for now.
|
|
22
|
+
cachePathForKey(key) {
|
|
23
|
+
const normalized = trailingSlash.remove(key);
|
|
24
|
+
return super.cachePathForKey(normalized);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
globals = null;
|
|
28
|
+
|
|
29
|
+
// Don't cache files, just record their dependencies. The OS already caches
|
|
30
|
+
// files, so caching them just consumes memory and may slow things down.
|
|
31
|
+
[noCacheSymbol] = true;
|
|
32
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { SyncMap } from "@weborigami/async-tree";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { cachePathSymbol } from "./symbols.js";
|
|
4
|
+
import systemCache from "./systemCache.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Return a sync map of all values in scope for the given sync source map.
|
|
8
|
+
*
|
|
9
|
+
* @param {SyncMap} source
|
|
10
|
+
*/
|
|
11
|
+
export default class ScopeMap extends SyncMap {
|
|
12
|
+
constructor(source) {
|
|
13
|
+
super();
|
|
14
|
+
this.source = source;
|
|
15
|
+
this.trailingSlashKeys = source.trailingSlashKeys;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get(key) {
|
|
19
|
+
let value;
|
|
20
|
+
|
|
21
|
+
// Starting with this map, search up its parent hierarchy
|
|
22
|
+
let current = this.source;
|
|
23
|
+
while (current) {
|
|
24
|
+
// If the keys of this folder change, the scope request for this key could
|
|
25
|
+
// return a different value, so a change in keys needs to invalidate the
|
|
26
|
+
// value. Whether or not the get() request below succeeds, track the keys
|
|
27
|
+
// of this folder as an upstream dependency of the value being requested.
|
|
28
|
+
if (current[cachePathSymbol]) {
|
|
29
|
+
const folderKeysPath = path.join(current[cachePathSymbol], "_keys");
|
|
30
|
+
systemCache.trackCurrentDependency(folderKeysPath);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
value = current.get(key);
|
|
34
|
+
if (value !== undefined) {
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
current = current.parent;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Collect all keys for this tree and all parents
|
|
44
|
+
keys() {
|
|
45
|
+
const scopeKeys = new Set();
|
|
46
|
+
let current = this.source;
|
|
47
|
+
while (current) {
|
|
48
|
+
for (const key of current.keys()) {
|
|
49
|
+
scopeKeys.add(key);
|
|
50
|
+
}
|
|
51
|
+
current = current.parent;
|
|
52
|
+
}
|
|
53
|
+
return scopeKeys[Symbol.iterator]();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Collect all keys for this tree and all parents.
|
|
57
|
+
//
|
|
58
|
+
// This method exists for debugging purposes, as it's helpful to be able to
|
|
59
|
+
// quickly flatten and view the entire scope chain.
|
|
60
|
+
get trees() {
|
|
61
|
+
const result = [];
|
|
62
|
+
|
|
63
|
+
/** @type {SyncMap|null} */
|
|
64
|
+
let current = this.source;
|
|
65
|
+
while (current) {
|
|
66
|
+
result.push(current);
|
|
67
|
+
current = "parent" in current ? current.parent : null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
}
|