@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
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
} from "@weborigami/async-tree";
|
|
7
7
|
import * as YAMLModule from "yaml";
|
|
8
8
|
import * as compile from "../compiler/compile.js";
|
|
9
|
-
import
|
|
9
|
+
import coreGlobals from "../project/coreGlobals.js";
|
|
10
|
+
import getGlobalsForTree from "../project/getGlobalsForTree.js";
|
|
10
11
|
import parseFrontMatter from "./parseFrontMatter.js";
|
|
11
12
|
|
|
12
13
|
// The "yaml" package doesn't seem to provide a default export that the browser can
|
|
@@ -80,7 +81,8 @@ export default {
|
|
|
80
81
|
const { body, frontText, isOrigami } = parsed;
|
|
81
82
|
let frontData;
|
|
82
83
|
if (isOrigami) {
|
|
83
|
-
const globals =
|
|
84
|
+
const globals =
|
|
85
|
+
options.globals ?? getGlobalsForTree(parent) ?? (await coreGlobals());
|
|
84
86
|
const compiled = compile.expression(frontText.trim(), {
|
|
85
87
|
globals,
|
|
86
88
|
parent,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// .xhtml is a synonynm for .html
|
|
2
|
-
export {
|
|
2
|
+
export { default } from "./html_handler.js";
|
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
} from "@weborigami/async-tree";
|
|
9
9
|
import * as YAMLModule from "yaml";
|
|
10
10
|
import * as compile from "../compiler/compile.js";
|
|
11
|
-
import
|
|
11
|
+
import coreGlobals from "../project/coreGlobals.js";
|
|
12
|
+
import getGlobalsForTree from "../project/getGlobalsForTree.js";
|
|
12
13
|
import getSource from "./getSource.js";
|
|
13
14
|
|
|
14
15
|
// The "yaml" package doesn't seem to provide a default export that the browser can
|
|
@@ -104,7 +105,8 @@ export default {
|
|
|
104
105
|
};
|
|
105
106
|
|
|
106
107
|
async function oriCallTagForParent(parent, options, yaml) {
|
|
107
|
-
const globals =
|
|
108
|
+
const globals =
|
|
109
|
+
options.globals ?? getGlobalsForTree(parent) ?? (await coreGlobals());
|
|
108
110
|
return {
|
|
109
111
|
collection: "seq",
|
|
110
112
|
|
|
@@ -168,7 +170,8 @@ async function oriCallTagForParent(parent, options, yaml) {
|
|
|
168
170
|
// Define the !ori tag for YAML parsing. This will run in the context of the
|
|
169
171
|
// supplied parent.
|
|
170
172
|
async function oriTagForParent(parent, options, yaml) {
|
|
171
|
-
const globals =
|
|
173
|
+
const globals =
|
|
174
|
+
options.globals ?? getGlobalsForTree(parent) ?? (await coreGlobals());
|
|
172
175
|
return {
|
|
173
176
|
resolve(text) {
|
|
174
177
|
hasOriTags = true;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { isUnpackable, SyncMap, Tree } from "@weborigami/async-tree";
|
|
2
|
+
import {
|
|
3
|
+
getGlobalsForTree,
|
|
4
|
+
HandleExtensionsTransform,
|
|
5
|
+
} from "@weborigami/language";
|
|
6
|
+
import Zip from "adm-zip";
|
|
7
|
+
import addExtensionKeyFn from "./addExtensionKeyFn.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Handler for ZIP files
|
|
11
|
+
*/
|
|
12
|
+
const zip_handler = {
|
|
13
|
+
mediaType: "application/zip",
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Pack a tree of files as a ZIP file in Buffer form.
|
|
17
|
+
*
|
|
18
|
+
* @param {import("@weborigami/async-tree").Maplike} maplike
|
|
19
|
+
*/
|
|
20
|
+
async pack(maplike) {
|
|
21
|
+
// The ZIP file should leave the files in tree order.
|
|
22
|
+
const zip = new Zip({ noSort: true });
|
|
23
|
+
|
|
24
|
+
if (isUnpackable(maplike)) {
|
|
25
|
+
maplike = await maplike.unpack();
|
|
26
|
+
}
|
|
27
|
+
const tree = Tree.from(maplike, { deep: true });
|
|
28
|
+
const deflated = await Tree.deflatePaths(tree);
|
|
29
|
+
for (let [path, value] of deflated) {
|
|
30
|
+
if (typeof value === "function") {
|
|
31
|
+
value = value();
|
|
32
|
+
}
|
|
33
|
+
if (value instanceof Promise) {
|
|
34
|
+
value = await value;
|
|
35
|
+
} else if (value instanceof String) {
|
|
36
|
+
value = value.toString(); // adm-zip wants simple strings
|
|
37
|
+
}
|
|
38
|
+
zip.addFile(path, value);
|
|
39
|
+
|
|
40
|
+
// Special case for EPUB files, where `mimetype` must be uncompressed.
|
|
41
|
+
if (path === "mimetype") {
|
|
42
|
+
const entry = zip.getEntry(path);
|
|
43
|
+
entry.header.method = 0; // STORE (not DEFLATE)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const buffer = zip.toBuffer();
|
|
47
|
+
return buffer;
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Unpack a ZIP file
|
|
52
|
+
*/
|
|
53
|
+
async unpack(buffer, options) {
|
|
54
|
+
// Origami generally prefers keeping things as an Uint8Array or ArrayBuffer,
|
|
55
|
+
// but adm-zip only accepts a Buffer.
|
|
56
|
+
if (buffer instanceof Uint8Array || buffer instanceof ArrayBuffer) {
|
|
57
|
+
// @ts-ignore
|
|
58
|
+
buffer = Buffer.from(buffer);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const zip = new Zip(buffer);
|
|
62
|
+
|
|
63
|
+
const entries = zip.getEntries();
|
|
64
|
+
const filtered = entries.filter(
|
|
65
|
+
(entry) =>
|
|
66
|
+
!entry.entryName.startsWith("__MACOSX/") &&
|
|
67
|
+
!entry.entryName.endsWith("/"),
|
|
68
|
+
);
|
|
69
|
+
const deflated = Object.fromEntries(
|
|
70
|
+
filtered.map((entry) => [entry.entryName, () => entry.getData()]),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// The final tree will include extension handlers and have functions invoked
|
|
74
|
+
// to retrieve data from the ZIP file. While the base map is a SyncMap, the
|
|
75
|
+
// final tree will be async.
|
|
76
|
+
const classFn = HandleExtensionsTransform(
|
|
77
|
+
InvokeFunctionsTransform(SyncMap),
|
|
78
|
+
);
|
|
79
|
+
const result = await Tree.inflatePaths(deflated, { classFn });
|
|
80
|
+
|
|
81
|
+
const parent = options?.parent;
|
|
82
|
+
const globals = parent ? getGlobalsForTree(parent) : null;
|
|
83
|
+
if (globals) {
|
|
84
|
+
result.globals = globals;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return result;
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** @type {any} */ (zip_handler.pack).key = addExtensionKeyFn(".zip");
|
|
92
|
+
|
|
93
|
+
export default zip_handler;
|
|
94
|
+
|
|
95
|
+
function InvokeFunctionsTransform(Base) {
|
|
96
|
+
return class InvokeFunctions extends Base {
|
|
97
|
+
delete(key) {
|
|
98
|
+
return super.delete(key);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get(key) {
|
|
102
|
+
const value = super.get(key);
|
|
103
|
+
return typeof value === "function" ? value() : value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
set(key, value) {
|
|
107
|
+
return super.set(key, value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
trailingSlashKeys = true;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -1,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()
|