@weborigami/origami 0.2.3 → 0.2.5

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/main.js CHANGED
@@ -7,6 +7,7 @@ export * from "./src/handlers/handlers.js";
7
7
  export * from "./src/image/image.js";
8
8
  export { builtinsTree } from "./src/internal.js";
9
9
  export * from "./src/origami/origami.js";
10
+ export * from "./src/server/server.js";
10
11
  export * from "./src/site/site.js";
11
12
  export * from "./src/text/text.js";
12
13
  export * from "./src/tree/tree.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/origami",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Web Origami language, CLI, framework, and server",
5
5
  "type": "module",
6
6
  "repository": {
@@ -17,9 +17,9 @@
17
17
  "typescript": "5.7.2"
18
18
  },
19
19
  "dependencies": {
20
- "@weborigami/async-tree": "0.2.3",
21
- "@weborigami/language": "0.2.3",
22
- "@weborigami/types": "0.2.3",
20
+ "@weborigami/async-tree": "0.2.5",
21
+ "@weborigami/language": "0.2.5",
22
+ "@weborigami/types": "0.2.5",
23
23
  "exif-parser": "0.1.12",
24
24
  "graphviz-wasm": "3.0.2",
25
25
  "highlight.js": "11.11.0",
package/src/dev/watch.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Tree } from "@weborigami/async-tree";
2
- import { formatError } from "@weborigami/language";
2
+ import { formatError, moduleCache } from "@weborigami/language";
3
3
  import ConstantTree from "../common/ConstantTree.js";
4
4
  import getTreeArgument from "../common/getTreeArgument.js";
5
5
 
@@ -42,6 +42,7 @@ export default async function watch(treelike, fn) {
42
42
  // Reevaluate the function whenever the tree changes.
43
43
  container.addEventListener?.("change", async () => {
44
44
  const tree = await evaluateTree(container, fn);
45
+ moduleCache.resetTimestamp();
45
46
  updateIndirectPointer(handle, tree);
46
47
  });
47
48
 
@@ -0,0 +1,29 @@
1
+ import { symbols } from "@weborigami/async-tree";
2
+
3
+ /**
4
+ * Return a suitable parent for the packed file.
5
+ *
6
+ * @param {any} packed
7
+ * @param {any} options
8
+ * @returns {import("@weborigami/types").AsyncTree|null}
9
+ */
10
+ export default function getParent(packed, options) {
11
+ // Prefer parent set on options
12
+ if (options?.parent) {
13
+ return options.parent;
14
+ }
15
+
16
+ // If the packed object has a `parent` property, use that. Exception: Node
17
+ // Buffer objects have a `parent` property that we ignore.
18
+ if (packed.parent && !(packed instanceof Buffer)) {
19
+ return packed.parent;
20
+ }
21
+
22
+ // If the packed object has a parent symbol, use that.
23
+ if (packed[symbols.parent]) {
24
+ return packed[symbols.parent];
25
+ }
26
+
27
+ // Otherwise, return null.
28
+ return null;
29
+ }
@@ -1,7 +1,7 @@
1
- import { symbols } from "@weborigami/async-tree";
2
1
  import { compile } from "@weborigami/language";
3
2
  import * as utilities from "../common/utilities.js";
4
3
  import { builtinsTree, processUnpackedContent } from "../internal.js";
4
+ import getParent from "./getParent.js";
5
5
 
6
6
  /**
7
7
  * An Origami expression file
@@ -13,16 +13,13 @@ export default {
13
13
 
14
14
  /** @type {import("@weborigami/language").UnpackFunction} */
15
15
  async unpack(packed, options = {}) {
16
- const parent =
17
- options.parent ??
18
- /** @type {any} */ (packed).parent ??
19
- /** @type {any} */ (packed)[symbols.parent];
16
+ const parent = getParent(packed, options);
20
17
 
21
18
  // Construct an object to represent the source code.
22
19
  const sourceName = options.key;
23
20
  let url;
24
- if (sourceName && parent?.url) {
25
- let parentHref = parent.url.href;
21
+ if (sourceName && /** @type {any} */ (parent)?.url) {
22
+ let parentHref = /** @type {any} */ (parent).url.href;
26
23
  if (!parentHref.endsWith("/")) {
27
24
  parentHref += "/";
28
25
  }
@@ -8,6 +8,7 @@ import { compile } from "@weborigami/language";
8
8
  import { parseYaml } from "../common/serialize.js";
9
9
  import { toString } from "../common/utilities.js";
10
10
  import { processUnpackedContent } from "../internal.js";
11
+ import getParent from "./getParent.js";
11
12
  import parseFrontMatter from "./parseFrontMatter.js";
12
13
 
13
14
  /**
@@ -19,11 +20,7 @@ export default {
19
20
 
20
21
  /** @type {import("@weborigami/language").UnpackFunction} */
21
22
  async unpack(packed, options = {}) {
22
- const parent =
23
- options.parent ??
24
- /** @type {any} */ (packed).parent ??
25
- /** @type {any} */ (packed)[symbols.parent] ??
26
- null;
23
+ const parent = getParent(packed, options);
27
24
 
28
25
  // Unpack as a text document
29
26
  const unpacked = toString(packed);
@@ -31,8 +28,8 @@ export default {
31
28
  // See if we can construct a URL to use in error messages
32
29
  const key = options.key;
33
30
  let url;
34
- if (key && parent?.url) {
35
- let parentHref = parent.url.href;
31
+ if (key && /** @type {any} */ (parent)?.url) {
32
+ let parentHref = /** @type {any} */ (parent).url.href;
36
33
  if (!parentHref.endsWith("/")) {
37
34
  parentHref += "/";
38
35
  }
@@ -274,6 +274,9 @@ tree:
274
274
  concat:
275
275
  args: (...objs)
276
276
  description: Concatenate text and/or trees of text
277
+ constant:
278
+ args: (value)
279
+ description: Return a deep tree with a single constant value
277
280
  copy:
278
281
  args: (source, target)
279
282
  description: Copy the source tree to the target
@@ -298,6 +301,9 @@ tree:
298
301
  entries:
299
302
  args: (tree)
300
303
  description: The tree's [key, value] pairs
304
+ filter:
305
+ args: (source, filter)
306
+ description: Filter the source tree using the filter tree
301
307
  first:
302
308
  args: (tree)
303
309
  description: The first value in the tree
@@ -310,9 +316,9 @@ tree:
310
316
  fromFn:
311
317
  args: (fn, [keys])
312
318
  description: A tree defined by a value function
313
- globs:
319
+ globKeys:
314
320
  args: (patterns)
315
- description: A tree whose keys can include wildcard patterns
321
+ description: A tree whose keys can include glob wildcard patterns
316
322
  group:
317
323
  args: (tree, fn)
318
324
  description: A new tree with values grouped by the function
@@ -364,6 +370,9 @@ tree:
364
370
  plain:
365
371
  args: (tree)
366
372
  description: Render the tree as a plain JavaScript object
373
+ regExpKeys:
374
+ args: (tree)
375
+ description: A tree whose keys are regular expression strings
367
376
  remove:
368
377
  args: (tree, key)
369
378
  description: Remove the value for the key from tree
package/src/js.js CHANGED
@@ -18,6 +18,7 @@ export default {
18
18
  Number,
19
19
  Object,
20
20
  RegExp,
21
+ Response,
21
22
  Set,
22
23
  String,
23
24
  Symbol,
@@ -1,6 +1,7 @@
1
1
  import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
2
2
 
3
- const fnPromiseMap = new WeakMap();
3
+ const fnPromiseMap = new Map();
4
+ const codePromiseMap = new Map();
4
5
 
5
6
  /**
6
7
  * Evaluate the given function only once and cache the result.
@@ -11,8 +12,23 @@ const fnPromiseMap = new WeakMap();
11
12
  */
12
13
  export default async function once(fn) {
13
14
  assertTreeIsDefined(this, "origami:once");
15
+
16
+ const code = /** @type {any} */ (fn).code;
17
+ if (code) {
18
+ // Origami function, cache by code
19
+ if (!codePromiseMap.has(code)) {
20
+ // Don't wait for promise to resolve
21
+ const promise = fn.call(this);
22
+ codePromiseMap.set(code, promise);
23
+ }
24
+ return codePromiseMap.get(code);
25
+ }
26
+
27
+ // Regular function, cache by function
14
28
  if (!fnPromiseMap.has(fn)) {
15
- fnPromiseMap.set(fn, fn.call(this));
29
+ // Don't wait for promise to resolve
30
+ const promise = fn.call(this);
31
+ fnPromiseMap.set(fn, promise);
16
32
  }
17
33
  return fnPromiseMap.get(fn);
18
34
  }
@@ -46,6 +46,9 @@ export default async function constructResponse(request, resource) {
46
46
  if (typeof resource === "function") {
47
47
  resource = await resource();
48
48
  }
49
+ if (resource instanceof Response) {
50
+ return resource;
51
+ }
49
52
  }
50
53
 
51
54
  let mediaType;
@@ -193,7 +193,8 @@ ${message}
193
193
  }
194
194
 
195
195
  // Asynchronous tree router as Express middleware.
196
- export function treeRouter(tree) {
196
+ export function treeRouter(treelike) {
197
+ const tree = Tree.from(treelike, { deep: true });
197
198
  // Return a router for the tree source.
198
199
  return async function (request, response, next) {
199
200
  const handled = await handleRequest(request, response, tree);
package/src/tree/cache.js CHANGED
@@ -9,19 +9,14 @@ import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
9
9
  * @typedef {import("@weborigami/async-tree").Treelike} Treelike
10
10
  * @param {Treelike} sourceTreelike
11
11
  * @param {Treelike} [cacheTreelike]
12
- * @param {Treelike} [filterTreelike]
13
12
  * @this {AsyncTree|null}
14
13
  */
15
- export default async function cacheBuiltin(
16
- sourceTreelike,
17
- cacheTreelike,
18
- filterTreelike
19
- ) {
14
+ export default async function cacheBuiltin(sourceTreelike, cacheTreelike) {
20
15
  assertTreeIsDefined(this, "tree:cache");
21
16
  /** @type {any} */
22
17
  const cacheTree = cacheTreelike
23
18
  ? Tree.from(cacheTreelike, { parent: this })
24
19
  : undefined;
25
- const result = cache(sourceTreelike, cacheTree, filterTreelike);
20
+ const result = cache(sourceTreelike, cacheTree);
26
21
  return result;
27
22
  }
@@ -0,0 +1 @@
1
+ export { constantTree as default } from "@weborigami/async-tree";
@@ -11,16 +11,8 @@ import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
11
11
  */
12
12
  export default async function treeDeepMerge(...trees) {
13
13
  assertTreeIsDefined(this, "tree:deepMerge");
14
-
15
- // Filter out null or undefined trees.
16
- const filtered = trees.filter((tree) => tree);
17
-
18
- if (filtered.length === 1) {
19
- // Only one tree, no need to merge.
20
- return filtered[0];
21
- }
22
-
23
14
  // Merge the trees.
24
- const result = deepMerge(...filtered);
15
+ const result = deepMerge(...trees);
16
+ result.parent = this;
25
17
  return result;
26
18
  }
@@ -1,21 +1,19 @@
1
- import { Tree } from "@weborigami/async-tree";
1
+ import { filter } from "@weborigami/async-tree";
2
2
  import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
3
- import FilterTree from "./FilterTree.js";
4
3
 
5
4
  /**
6
5
  * Apply a filter to a tree.
7
6
  *
8
7
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
8
  * @typedef {import("@weborigami/async-tree").Treelike} Treelike
9
+ *
10
10
  * @this {AsyncTree|null}
11
11
  * @param {Treelike} sourceTreelike
12
12
  * @param {Treelike} filterTreelike
13
13
  */
14
- export default async function filter(sourceTreelike, filterTreelike) {
14
+ export default async function filterBuiltin(sourceTreelike, filterTreelike) {
15
15
  assertTreeIsDefined(this, "tree:filter");
16
- const sourceTree = Tree.from(sourceTreelike, { parent: this });
17
- const filterTree = Tree.from(filterTreelike, { deep: true, parent: this });
18
- const result = new FilterTree(sourceTree, filterTree);
16
+ const result = filter(sourceTreelike, filterTreelike);
19
17
  result.parent = this;
20
18
  return result;
21
19
  }
@@ -1,5 +1,6 @@
1
+ import { setParent } from "@weborigami/async-tree";
2
+ import globKeys from "@weborigami/async-tree/src/operations/globKeys.js";
1
3
  import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
2
- import GlobTree from "./GlobTree.js";
3
4
 
4
5
  /**
5
6
  * Define a tree whose keys are globs.
@@ -10,8 +11,9 @@ import GlobTree from "./GlobTree.js";
10
11
  * @param {Treelike} tree
11
12
  * @this {AsyncTree|null}
12
13
  */
13
- export default async function globs(tree) {
14
+ export default async function globKeysBuiltin(tree) {
14
15
  assertTreeIsDefined(this, "tree:globs");
15
- const result = new GlobTree(tree);
16
+ const result = globKeys(tree);
17
+ setParent(this, result);
16
18
  return result;
17
19
  }
package/src/tree/map.js CHANGED
@@ -21,16 +21,22 @@ import { toFunction } from "../common/utilities.js";
21
21
  * @param {ValueKeyFn|TreeMapOptions} operation
22
22
  */
23
23
  export default async function map(treelike, operation) {
24
- // The tree we're going to map
25
- const source = await getTreeArgument(this, arguments, treelike, "tree:map");
26
- // The tree in which the map operation happens
27
- const context = this;
28
-
29
24
  if (isUnpackable(operation)) {
30
25
  operation = await operation.unpack();
31
26
  }
27
+ // The tree in which the map operation happens
28
+ const context = this;
32
29
  const options = extendedOptions(context, operation);
33
30
 
31
+ // The tree we're going to map
32
+ const source = await getTreeArgument(
33
+ this,
34
+ arguments,
35
+ treelike,
36
+ "tree:map",
37
+ options?.deep
38
+ );
39
+
34
40
  const mapped = mapTransform(source, options);
35
41
  mapped.parent = context;
36
42
  return mapped;
@@ -0,0 +1,19 @@
1
+ import { setParent } from "@weborigami/async-tree";
2
+ import regExpKeys from "@weborigami/async-tree/src/operations/regExpKeys.js";
3
+ import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
4
+
5
+ /**
6
+ * Define a tree whose keys are regular expression strings.
7
+ *
8
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
+ * @typedef {import("@weborigami/async-tree").Treelike} Treelike
10
+ *
11
+ * @param {Treelike} tree
12
+ * @this {AsyncTree|null}
13
+ */
14
+ export default async function regExpKeysBuiltin(tree) {
15
+ assertTreeIsDefined(this, "tree:globs");
16
+ const result = regExpKeys(tree);
17
+ setParent(this, result);
18
+ return result;
19
+ }
package/src/tree/tree.js CHANGED
@@ -4,6 +4,7 @@ export { default as cache } from "./cache.js";
4
4
  export { default as calendar } from "./calendar.js";
5
5
  export { default as clear } from "./clear.js";
6
6
  export { default as concat } from "./concat.js";
7
+ export { default as constant } from "./constant.js";
7
8
  export { default as copy } from "./copy.js";
8
9
  export { default as deepMap } from "./deepMap.js";
9
10
  export { default as deepMerge } from "./deepMerge.js";
@@ -14,7 +15,7 @@ export { default as defineds } from "./defineds.js";
14
15
  export { default as filter } from "./filter.js";
15
16
  export { default as first } from "./first.js";
16
17
  export { default as fromFn } from "./fromFn.js";
17
- export { default as globs } from "./globs.js";
18
+ export { default as globKeys } from "./globKeys.js";
18
19
  export { default as group } from "./group.js";
19
20
  export { default as inners } from "./inners.js";
20
21
  export { default as keys } from "./keys.js";
@@ -25,6 +26,7 @@ export { default as merge } from "./merge.js";
25
26
  export { default as paginate } from "./paginate.js";
26
27
  export { default as parent } from "./parent.js";
27
28
  export { default as plain } from "./plain.js";
29
+ export { default as regExpKeys } from "./regExpKeys.js";
28
30
  export { default as reverse } from "./reverse.js";
29
31
  export { default as setDeep } from "./setDeep.js";
30
32
  export { default as shuffle } from "./shuffle.js";
@@ -1,61 +0,0 @@
1
- import { trailingSlash, Tree } from "@weborigami/async-tree";
2
-
3
- /**
4
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
5
- * @implements {AsyncTree}
6
- */
7
- export default class FilterTree {
8
- constructor(tree, filter) {
9
- this.tree = Tree.from(tree);
10
- this.filter = Tree.from(filter, { deep: true });
11
- this.parent = null;
12
- }
13
-
14
- async get(key) {
15
- let value = await this.tree.get(key);
16
-
17
- let filterValue = await this.filter.get(key);
18
- if (!Tree.isAsyncTree(value)) {
19
- if (filterValue === undefined) {
20
- value = undefined;
21
- } else if (Tree.isAsyncTree(filterValue)) {
22
- value = undefined;
23
- }
24
- } else if (Tree.isAsyncTree(filterValue)) {
25
- // Wrap value with corresponding filter.
26
- value = Reflect.construct(this.constructor, [value, filterValue]);
27
- }
28
-
29
- return value;
30
- }
31
-
32
- async keys() {
33
- const keys = new Set();
34
-
35
- // Enumerate all keys in the tree that can be found in the filter tree.
36
- for (const key of await this.tree.keys()) {
37
- const filterValue = await this.filter.get(key);
38
- const isFilterValueTree = Tree.isAsyncTree(filterValue);
39
- // If the filter value is a tree, the corresponding value in the tree
40
- // must be a tree too.
41
- const match =
42
- (!isFilterValueTree && filterValue) ||
43
- (isFilterValueTree && trailingSlash.has(key));
44
- if (match) {
45
- keys.add(key);
46
- }
47
- }
48
-
49
- // Also include any keys in the filter that are found in the tree. This
50
- // lets the filter "pull" values from a tree that, e.g., is defined by a
51
- // function without an explicit domain.
52
- // for (const key of await this.filter.keys()) {
53
- // const value = await this.tree.get(key);
54
- // if (value !== undefined) {
55
- // keys.add(key);
56
- // }
57
- // }
58
-
59
- return keys;
60
- }
61
- }
@@ -1,76 +0,0 @@
1
- import { ObjectTree, Tree, merge, trailingSlash } from "@weborigami/async-tree";
2
-
3
- const globstar = "**";
4
-
5
- /**
6
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
7
- * @implements {AsyncTree}
8
- */
9
- export default class GlobTree {
10
- constructor(globs) {
11
- this.globs = Tree.from(globs, { deep: true });
12
- }
13
-
14
- async get(key) {
15
- if (typeof key !== "string") {
16
- return undefined;
17
- }
18
-
19
- // Remove trailing slash if it exists
20
- key = trailingSlash.remove(key);
21
-
22
- let value = await matchGlobs(this.globs, key);
23
- if (Tree.isAsyncTree(value)) {
24
- value = Reflect.construct(this.constructor, [value]);
25
- }
26
- return value;
27
- }
28
-
29
- async keys() {
30
- return this.globs.keys();
31
- }
32
- }
33
-
34
- // Convert the glob to a regular expression
35
- function matchGlob(glob, text) {
36
- const regexText = glob
37
- // Escape special regex characters
38
- .replace(/[+?^${}()|\[\]\\]/g, "\\$&")
39
- // Replace the glob wildcards with regex wildcards
40
- .replace(/\*/g, ".+")
41
- .replace(/\?/g, ".");
42
- const regex = new RegExp(`^${regexText}$`);
43
- return regex.test(text);
44
- }
45
-
46
- async function matchGlobs(globs, text) {
47
- let value;
48
- for (let glob of await globs.keys()) {
49
- if (typeof glob !== "string") {
50
- continue;
51
- }
52
-
53
- // Remove trailing slash if it exists
54
- glob = trailingSlash.remove(glob);
55
-
56
- if (glob !== globstar && matchGlob(glob, text)) {
57
- value = await globs.get(glob);
58
- if (value !== undefined) {
59
- break;
60
- }
61
- }
62
- }
63
-
64
- const globstarGlobs = await globs.get(globstar);
65
- if (globstarGlobs) {
66
- const globstarTree = new ObjectTree({ [globstar]: globstarGlobs });
67
- if (value === undefined) {
68
- const globstarValue = await matchGlobs(globstarGlobs, text);
69
- value = globstarValue !== undefined ? globstarValue : globstarTree;
70
- } else if (Tree.isAsyncTree(value)) {
71
- value = merge(value, globstarTree);
72
- }
73
- }
74
-
75
- return value;
76
- }