@weborigami/origami 0.0.40 → 0.0.42

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.
Files changed (90) hide show
  1. package/exports/exports.js +33 -34
  2. package/package.json +4 -4
  3. package/src/builtins/@arrows.js +2 -11
  4. package/src/builtins/@builtins.js +2 -5
  5. package/src/builtins/@cache.js +1 -1
  6. package/src/builtins/{@tree/concat.js → @concat.js} +3 -3
  7. package/src/builtins/@copy.js +2 -2
  8. package/src/builtins/{@tree/count.js → @count.js} +3 -9
  9. package/src/builtins/@crawl.js +1 -1
  10. package/src/builtins/@debug.js +2 -8
  11. package/src/builtins/{@tree/defineds.js → @defineds.js} +3 -7
  12. package/src/builtins/@document.js +1 -1
  13. package/src/builtins/{@tree/exceptions.js → @exceptions.js} +7 -8
  14. package/src/builtins/@explore.js +2 -5
  15. package/src/builtins/@files.js +1 -1
  16. package/src/builtins/@filter.js +1 -1
  17. package/src/builtins/{@tree/first.js → @first.js} +3 -9
  18. package/src/builtins/{@tree/fn.js → @fnTree.js} +13 -7
  19. package/src/builtins/@globs.js +1 -1
  20. package/src/builtins/{@tree/groupBy.js → @groupBy.js} +7 -9
  21. package/src/builtins/@help.js +1 -1
  22. package/src/builtins/@http.js +1 -1
  23. package/src/builtins/@https.js +1 -1
  24. package/src/builtins/@if.js +1 -1
  25. package/src/builtins/@image/format.js +36 -2
  26. package/src/builtins/@image/resize.js +30 -2
  27. package/src/builtins/@index.js +2 -8
  28. package/src/builtins/@inherited.js +1 -1
  29. package/src/builtins/@inline.js +1 -1
  30. package/src/builtins/{@tree/inners.js → @inners.js} +3 -8
  31. package/src/builtins/@invoke.js +8 -0
  32. package/src/builtins/{@tree/isAsyncTree.js → @isAsyncTree.js} +1 -1
  33. package/src/builtins/@json.js +7 -1
  34. package/src/builtins/{@tree/keys.js → @keys.js} +3 -9
  35. package/src/builtins/{@tree/keysJson.js → @keysJson.js} +3 -8
  36. package/src/builtins/@loaders/ori.js +18 -18
  37. package/src/builtins/@map.js +17 -19
  38. package/src/builtins/@match.js +1 -1
  39. package/src/builtins/{@tree/merge.js → @merge.js} +2 -2
  40. package/src/builtins/{@tree/mergeDeep.js → @mergeDeep.js} +3 -3
  41. package/src/builtins/@once.js +1 -1
  42. package/src/builtins/@ori.js +1 -1
  43. package/src/builtins/@pack.js +1 -1
  44. package/src/builtins/{@tree/parent.js → @parent.js} +3 -9
  45. package/src/builtins/{@tree/paths.js → @paths.js} +3 -8
  46. package/src/builtins/@perf.js +18 -0
  47. package/src/builtins/{@tree/plain.js → @plain.js} +4 -8
  48. package/src/builtins/@project.js +1 -1
  49. package/src/builtins/@redirect.js +8 -0
  50. package/src/builtins/{@tree/reverse.js → @reverse.js} +3 -8
  51. package/src/builtins/@rss.js +1 -1
  52. package/src/builtins/@scope/extend.js +6 -6
  53. package/src/builtins/@scope/get.js +1 -1
  54. package/src/builtins/@scope/set.js +4 -4
  55. package/src/builtins/@serve.js +1 -1
  56. package/src/builtins/{@tree/setDeep.js → @setDeep.js} +1 -1
  57. package/src/builtins/{@tree/shuffle.js → @shuffle.js} +5 -11
  58. package/src/builtins/{@tree/sitemap.js → @sitemap.js} +5 -8
  59. package/src/builtins/{@tree/sort.js → @sort.js} +5 -7
  60. package/src/builtins/{@tree/sortBy.js → @sortBy.js} +7 -9
  61. package/src/builtins/{@tree/static.js → @static.js} +5 -10
  62. package/src/builtins/@svg.js +3 -9
  63. package/src/builtins/{@tree/table.js → @table.js} +3 -8
  64. package/src/builtins/{@tree/take.js → @take.js} +3 -9
  65. package/src/builtins/@tree.js +19 -0
  66. package/src/builtins/@treeHttp.js +1 -1
  67. package/src/builtins/@treeHttps.js +1 -1
  68. package/src/builtins/@unpack.js +1 -1
  69. package/src/builtins/{@tree/values.js → @values.js} +3 -8
  70. package/src/builtins/{@tree/valuesDeep.js → @valuesDeep.js} +4 -8
  71. package/src/builtins/@watch.js +3 -8
  72. package/src/builtins/@with.js +1 -1
  73. package/src/builtins/@yaml.js +7 -1
  74. package/src/builtins/{@tree/map.d.ts → map.d.ts} +1 -3
  75. package/src/cli/cli.js +2 -15
  76. package/src/common/ExplorableSiteTransform.js +6 -4
  77. package/src/common/addValueKeyToScope.js +3 -11
  78. package/src/common/processUnpackedContent.js +2 -7
  79. package/src/misc/assertScopeIsDefined.js +2 -2
  80. package/src/misc/getTreeArgument.js +51 -0
  81. package/src/{builtins/@tree/dot.js → misc/treeDot.js} +2 -11
  82. package/src/server/constructResponse.js +126 -0
  83. package/src/server/mediaTypes.js +0 -16
  84. package/src/server/server.js +62 -131
  85. package/src/builtins/@tree/flowSvg.js +0 -55
  86. package/src/builtins/@tree/from.js +0 -27
  87. package/src/builtins/@tree/fromJson.js +0 -6
  88. package/src/builtins/@tree/fromYaml.js +0 -24
  89. package/src/builtins/@tree/nextKey.js +0 -29
  90. package/src/builtins/@tree/previousKey.js +0 -29
@@ -0,0 +1,126 @@
1
+ import {
2
+ SiteTree,
3
+ Tree,
4
+ isPlainObject,
5
+ isStringLike,
6
+ } from "@weborigami/async-tree";
7
+ import { extname } from "@weborigami/language";
8
+ import * as serialize from "../common/serialize.js";
9
+ import { toString } from "../common/utilities.js";
10
+ import { mediaTypeForExtension } from "./mediaTypes.js";
11
+
12
+ const TypedArray = Object.getPrototypeOf(Uint8Array);
13
+
14
+ /**
15
+ * Given a resource that was returned from a route, construct an appropriate
16
+ * HTTP Response indicating what should be sent to the client. Return null
17
+ * if the resource is not a valid response.
18
+ *
19
+ * @param {import("node:http").IncomingMessage} request
20
+ * @param {any} resource
21
+ * @returns {Promise<Response|null>}
22
+ */
23
+ export default async function constructResponse(request, resource) {
24
+ if (resource instanceof Response) {
25
+ // Already a Response, return as is.
26
+ return resource;
27
+ } else if (!resource) {
28
+ return null;
29
+ }
30
+
31
+ // Determine media type, what data we'll send, and encoding.
32
+ const url = new URL(request.url ?? "", `https://${request.headers.host}`);
33
+ const extension = extname(url.pathname).toLowerCase();
34
+ let mediaType = extension ? mediaTypeForExtension[extension] : undefined;
35
+
36
+ if (
37
+ mediaType === undefined &&
38
+ !url.pathname.endsWith("/") &&
39
+ (Tree.isAsyncTree(resource) ||
40
+ isPlainObject(resource) ||
41
+ resource instanceof Array)
42
+ ) {
43
+ // Redirect to an index page for the result.
44
+ const Location = `${request.url}/`;
45
+ return new Response("ok", {
46
+ headers: {
47
+ Location,
48
+ },
49
+ status: 307,
50
+ });
51
+ }
52
+
53
+ // If the request is for a JSON or YAML result, and the resource we got
54
+ // isn't yet a string or Buffer, convert the resource to JSON or YAML now.
55
+ if (
56
+ (mediaType === "application/json" || mediaType === "text/yaml") &&
57
+ !isStringLike(resource)
58
+ ) {
59
+ const tree = Tree.from(resource);
60
+ resource =
61
+ mediaType === "text/yaml"
62
+ ? await serialize.toYaml(tree)
63
+ : await serialize.toJson(tree);
64
+ } else if (
65
+ mediaType === undefined &&
66
+ (isPlainObject(resource) || resource instanceof Array)
67
+ ) {
68
+ // The resource is data, try showing it as YAML.
69
+ const tree = Tree.from(resource);
70
+ resource = await serialize.toYaml(tree);
71
+ mediaType = "text/yaml";
72
+ }
73
+
74
+ let body;
75
+ if (mediaType) {
76
+ body = SiteTree.mediaTypeIsText(mediaType) ? toString(resource) : resource;
77
+ } else {
78
+ body = textOrObject(resource);
79
+ // Infer media type.
80
+ mediaType =
81
+ typeof body !== "string"
82
+ ? "application/octet-stream"
83
+ : body.trimStart().startsWith("<")
84
+ ? "text/html"
85
+ : "text/plain";
86
+ }
87
+
88
+ // Assume text is encoded in UTF-8.
89
+ if (SiteTree.mediaTypeIsText(mediaType)) {
90
+ mediaType += "; charset=utf-8";
91
+ }
92
+
93
+ // If we didn't get back some kind of data that response.write() accepts,
94
+ // assume it was an error.
95
+ const validResponse = typeof body === "string" || body instanceof TypedArray;
96
+ if (!validResponse) {
97
+ const typeName = body?.constructor?.name ?? typeof body;
98
+ console.error(
99
+ `A served tree must return a string or a TypedArray (such as a Buffer) but returned an instance of ${typeName}.`
100
+ );
101
+ return null;
102
+ }
103
+
104
+ return new Response(body, {
105
+ headers: {
106
+ "Content-Type": mediaType,
107
+ },
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Convert to a string if we can, but leave objects that convert to something
113
+ * like "[object Object]" alone.
114
+ *
115
+ * @param {any} object
116
+ */
117
+ function textOrObject(object) {
118
+ if (object instanceof ArrayBuffer) {
119
+ // Convert to Buffer.
120
+ return Buffer.from(object);
121
+ } else if (object instanceof TypedArray) {
122
+ // Return typed arrays as is.
123
+ return object;
124
+ }
125
+ return toString(object);
126
+ }
@@ -79,19 +79,3 @@ export const mediaTypeForExtension = {
79
79
  ".yaml": "text/yaml", // Not official
80
80
  ".zip": "application/zip",
81
81
  };
82
-
83
- export const mediaTypeIsText = {
84
- "application/json": true,
85
- "application/ld+json": true,
86
- "application/vnd.oasis.opendocument.text": true,
87
- "application/x-httpd-php": true,
88
- "application/x-sh": true,
89
- "application/xhtml+xml": true,
90
- "text/css": true,
91
- "text/csv": true,
92
- "text/html": true,
93
- "text/javascript": true,
94
- "text/plain": true,
95
- "text/yaml": true,
96
- "text/yml": true,
97
- };
@@ -1,16 +1,41 @@
1
- import {
2
- ObjectTree,
3
- Tree,
4
- isPlainObject,
5
- isStringLike,
6
- keysFromPath,
7
- } from "@weborigami/async-tree";
8
- import { Scope, extname } from "@weborigami/language";
9
- import * as serialize from "../common/serialize.js";
10
- import { toString } from "../common/utilities.js";
11
- import { mediaTypeForExtension, mediaTypeIsText } from "./mediaTypes.js";
1
+ import { ObjectTree, Tree, keysFromPath } from "@weborigami/async-tree";
2
+ import { Scope, formatError } from "@weborigami/language";
3
+ import { ServerResponse } from "node:http";
4
+ import constructResponse from "./constructResponse.js";
12
5
 
13
- const TypedArray = Object.getPrototypeOf(Uint8Array);
6
+ /**
7
+ * Copy a constructed response to a ServerResponse. Return true if the response
8
+ * was successfully copied, and false if there was a problem.
9
+ *
10
+ * @param {Response} constructed
11
+ * @param {ServerResponse} response
12
+ */
13
+ async function copyResponse(constructed, response) {
14
+ response.statusCode = constructed.status;
15
+ response.statusMessage = constructed.statusText;
16
+
17
+ for (const [key, value] of constructed.headers) {
18
+ response.setHeader(key, value);
19
+ }
20
+
21
+ if (constructed.body) {
22
+ try {
23
+ // Write the response body to the ServerResponse.
24
+ const reader = constructed.body.getReader();
25
+ let { done, value } = await reader.read();
26
+ while (!done) {
27
+ response.write(value);
28
+ ({ done, value } = await reader.read());
29
+ }
30
+ response.end();
31
+ } catch (/** @type {any} */ error) {
32
+ console.error(error.message);
33
+ return false;
34
+ }
35
+ }
36
+
37
+ return true;
38
+ }
14
39
 
15
40
  // Extend the tree's scope with the URL's search parameters.
16
41
  function extendTreeScopeWithParams(tree, url) {
@@ -38,21 +63,16 @@ function extendTreeScopeWithParams(tree, url) {
38
63
  return extendedTree;
39
64
  }
40
65
 
41
- // Asynchronous tree router as Express middleware.
42
- export function treeRouter(tree) {
43
- // Return a router for the tree source.
44
- return async function (request, response, next) {
45
- const handled = await handleRequest(request, response, tree);
46
- if (!handled) {
47
- // Module not found, let next middleware function try.
48
- next();
49
- }
50
- };
51
- }
52
-
66
+ /**
67
+ * Handle a client request.
68
+ *
69
+ * @param {import("node:http").IncomingMessage} request
70
+ * @param {ServerResponse} response
71
+ * @param {import("@weborigami/types").AsyncTree} tree
72
+ */
53
73
  export async function handleRequest(request, response, tree) {
54
74
  // For parsing purposes, we assume HTTPS -- it doesn't affect parsing.
55
- const url = new URL(request.url, `https://${request.headers.host}`);
75
+ const url = new URL(request.url ?? "", `https://${request.headers.host}`);
56
76
  const keys = keysFromUrl(url);
57
77
 
58
78
  const extendedTree =
@@ -73,93 +93,15 @@ export async function handleRequest(request, response, tree) {
73
93
  return true;
74
94
  }
75
95
 
76
- let mediaType;
77
-
78
- if (!resource) {
79
- return false;
80
- }
81
-
82
- // Determine media type, what data we'll send, and encoding.
83
- const extension = extname(url.pathname).toLowerCase();
84
- mediaType = extension ? mediaTypeForExtension[extension] : undefined;
85
-
86
- if (
87
- mediaType === undefined &&
88
- !request.url.endsWith("/") &&
89
- (Tree.isAsyncTree(resource) ||
90
- isPlainObject(resource) ||
91
- resource instanceof Array)
92
- ) {
93
- // Redirect to an index page for the result.
94
- // Redirect to the root of the tree.
95
- const Location = `${request.url}/`;
96
- response.writeHead(307, { Location });
97
- response.end("ok");
98
- return true;
99
- }
100
-
101
- // If the request is for a JSON or YAML result, and the resource we got
102
- // isn't yet a string or Buffer, convert the resource to JSON or YAML now.
103
- if (
104
- (mediaType === "application/json" || mediaType === "text/yaml") &&
105
- !isStringLike(resource)
106
- ) {
107
- const tree = Tree.from(resource);
108
- resource =
109
- mediaType === "text/yaml"
110
- ? await serialize.toYaml(tree)
111
- : await serialize.toJson(tree);
112
- } else if (
113
- mediaType === undefined &&
114
- (isPlainObject(resource) || resource instanceof Array)
115
- ) {
116
- // The resource is data, try showing it as YAML.
117
- const tree = Tree.from(resource);
118
- resource = await serialize.toYaml(tree);
119
- mediaType = "text/yaml";
120
- }
121
-
122
- let data;
123
- if (mediaType) {
124
- data = mediaTypeIsText[mediaType] ? toString(resource) : resource;
125
- } else {
126
- data = textOrObject(resource);
127
- }
128
-
129
- if (!mediaType) {
130
- // Can't identify media type; infer default type.
131
- mediaType =
132
- typeof data !== "string"
133
- ? "application/octet-stream"
134
- : data.trimStart().startsWith("<")
135
- ? "text/html"
136
- : "text/plain";
137
- }
138
- const encoding = mediaTypeIsText[mediaType] ? "utf-8" : undefined;
139
-
140
- // If we didn't get back some kind of data that response.write() accepts,
141
- // assume it was an error.
142
- const validResponse = typeof data === "string" || data instanceof TypedArray;
143
-
144
- if (!validResponse) {
145
- const typeName = data?.constructor?.name ?? typeof data;
146
- console.error(
147
- `A served tree must return a string or a TypedArray (such as a Buffer) but returned an instance of ${typeName}.`
148
- );
149
- return false;
150
- }
151
-
152
- response.writeHead(200, {
153
- "Content-Type": mediaType,
154
- });
155
- try {
156
- response.end(data, encoding);
157
- } catch (/** @type {any} */ error) {
158
- console.error(error.message);
96
+ // Construct the response.
97
+ const constructed = await constructResponse(request, resource);
98
+ if (!constructed) {
159
99
  return false;
160
100
  }
161
101
 
162
- return true;
102
+ // Copy the construct response to the ServerResponse and return true if
103
+ // the response was valid.
104
+ return copyResponse(constructed, response);
163
105
  }
164
106
 
165
107
  function keysFromUrl(url) {
@@ -211,16 +153,7 @@ export function requestListener(treelike) {
211
153
  * the console.
212
154
  */
213
155
  function respondWithError(response, error) {
214
- let message = "";
215
- // Work up to the root cause, displaying intermediate messages as we go up.
216
- while (error.cause) {
217
- message += error.message + `\n`;
218
- error = error.cause;
219
- }
220
- if (error.name) {
221
- message += `${error.name}: `;
222
- }
223
- message += error.message;
156
+ let message = formatError(error);
224
157
  // Prevent HTML in the error message from being interpreted as HTML.
225
158
  message = message.replace(/</g, "&lt;").replace(/>/g, "&gt;");
226
159
  const html = `<!DOCTYPE html>
@@ -241,16 +174,14 @@ ${message}
241
174
  console.error(message);
242
175
  }
243
176
 
244
- /**
245
- * Convert to a string if we can, but leave objects that convert to something
246
- * like "[object Object]" alone.
247
- *
248
- * @param {any} object
249
- */
250
- function textOrObject(object) {
251
- // Return buffers and typed arrays as is.
252
- if (object instanceof ArrayBuffer || object instanceof TypedArray) {
253
- return object;
254
- }
255
- return toString(object);
177
+ // Asynchronous tree router as Express middleware.
178
+ export function treeRouter(tree) {
179
+ // Return a router for the tree source.
180
+ return async function (request, response, next) {
181
+ const handled = await handleRequest(request, response, tree);
182
+ if (!handled) {
183
+ // Module not found, let next middleware function try.
184
+ next();
185
+ }
186
+ };
256
187
  }
@@ -1,55 +0,0 @@
1
- import graphviz from "graphviz-wasm";
2
-
3
- let graphvizLoaded = false;
4
-
5
- export default async function flowSvg(flow) {
6
- if (!graphvizLoaded) {
7
- await graphviz.loadWASM();
8
- graphvizLoaded = true;
9
- }
10
- const dot = flowDot(flow);
11
- const svg = await graphviz.layout(dot, "svg");
12
- return svg;
13
- }
14
-
15
- function flowDot(flow) {
16
- const nodes = [];
17
- const edges = [];
18
- for (const [key, record] of Object.entries(flow)) {
19
- const dependencies = record.dependencies ?? [];
20
- let label = record.label ?? key;
21
- if (record.undefined) {
22
- label += " (?)";
23
- }
24
- const virtualNode = dependencies.length > 0;
25
- const url = record.url ?? key;
26
- const nodeLabel = `label="${label}"`;
27
- const nodeUrl = `URL=".scope/${url}"`;
28
- const nodeShape = record.undefined ? `shape="none"` : "";
29
- const nodeStyle = virtualNode ? `style="dashed"` : null;
30
- const attributes = [nodeLabel, nodeShape, nodeStyle, nodeUrl].filter(
31
- (attribute) => attribute
32
- );
33
- const nodeDot = ` "${key}" [${attributes.join("; ")}];`;
34
- nodes.push(nodeDot);
35
-
36
- for (const dependency of dependencies) {
37
- edges.push(` "${dependency}" -> "${key}";`);
38
- }
39
- }
40
-
41
- return `digraph dataflow {
42
- nodesep=1;
43
- rankdir=LR;
44
- ranksep=1.5;
45
- node [color="gray70"; fillcolor="white"; fontname="Helvetica"; fontsize="10"; nojustify="true"; style="filled"; shape="box"];
46
- edge [arrowhead="onormal"; arrowsize="0.75"; color="gray60"; fontname="Helvetica"; fontsize="10"; labeldistance="5"];
47
-
48
- ${nodes.join("\n")}
49
-
50
- ${edges.join("\n")}
51
- }`;
52
- }
53
-
54
- // flowSvg.usage = `flowSvg <dataflow>\tRenders the output of dataflow() as an SVG`;
55
- // flowSvg.documentation = "https://weborigami.org/cli/builtins.html#flowSvg";
@@ -1,27 +0,0 @@
1
- import { Tree } from "@weborigami/async-tree";
2
- import { Scope } from "@weborigami/language";
3
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
4
-
5
- /**
6
- * Cast the indicated treelike to a tree.
7
- *
8
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
- * @typedef {import("@weborigami/async-tree").Treelike} Treelike
10
- * @this {AsyncTree|null}
11
- * @param {Treelike} [treelike]
12
- */
13
- export default async function tree(treelike) {
14
- assertScopeIsDefined(this);
15
- treelike = treelike ?? (await this?.get("@current"));
16
- if (treelike === undefined) {
17
- return undefined;
18
- }
19
-
20
- /** @type {AsyncTree} */
21
- let result = Tree.from(treelike);
22
- result = Scope.treeWithScope(result, this);
23
- return result;
24
- }
25
-
26
- tree.usage = `from <treelike>\tConvert JSON, YAML, function, or plain object to a tree`;
27
- tree.documentation = "https://weborigami.org/cli/builtins.html#tree";
@@ -1,6 +0,0 @@
1
- export default async function fromJson(text) {
2
- return text ? JSON.parse(text) : undefined;
3
- }
4
-
5
- fromJson.usage = `fromJson <text>\tParse text as JSON`;
6
- fromJson.documentation = "https://weborigami.org/cli/builtins.html#fromJson";
@@ -1,24 +0,0 @@
1
- import { Tree } from "@weborigami/async-tree";
2
- import { Scope } from "@weborigami/language";
3
- import * as serialize from "../../common/serialize.js";
4
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
5
-
6
- /**
7
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
- * @typedef {import("@weborigami/async-tree").Treelike} Treelike
9
- * @typedef {import("@weborigami/async-tree").StringLike} StringLike
10
- *
11
- * @param {StringLike} text
12
- * @this {AsyncTree|null}
13
- */
14
- export default async function fromYaml(text) {
15
- assertScopeIsDefined(this);
16
- let result = text ? serialize.parseYaml(String(text)) : undefined;
17
- if (this && Tree.isAsyncTree(result)) {
18
- result = Scope.treeWithScope(result, this);
19
- }
20
- return result;
21
- }
22
-
23
- fromYaml.usage = `fromYaml <text>\tParse text as YAML`;
24
- fromYaml.documentation = "https://weborigami.org/cli/builtins.html#fromYaml";
@@ -1,29 +0,0 @@
1
- import { Tree } from "@weborigami/async-tree";
2
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
3
-
4
- /**
5
- * Returns the key after the indicated key.
6
- *
7
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
- * @typedef {import("@weborigami/async-tree").Treelike} Treelike
9
- * @this {AsyncTree|null}
10
- * @param {Treelike} treelike
11
- * @param {any} key
12
- */
13
- export default async function nextKey(treelike, key) {
14
- assertScopeIsDefined(this);
15
- const tree = Tree.from(treelike);
16
- let returnNextKey = false;
17
- for (const treeKey of await tree.keys()) {
18
- if (returnNextKey) {
19
- return treeKey;
20
- }
21
- if (treeKey === key) {
22
- returnNextKey = true;
23
- }
24
- }
25
- return undefined;
26
- }
27
-
28
- nextKey.usage = `nextKey <tree>, <key>\tReturns the key after the indicated key`;
29
- nextKey.documentation = "https://weborigami.org/cli/builtins.html#nextKey";
@@ -1,29 +0,0 @@
1
- import { Tree } from "@weborigami/async-tree";
2
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
3
-
4
- /**
5
- * Returns the key before the indicated key.
6
- *
7
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
- * @typedef {import("@weborigami/async-tree").Treelike} Treelike
9
- *
10
- * @param {Treelike} treelike
11
- * @param {any} key
12
- * @this {AsyncTree|null}
13
- */
14
- export default async function previousKey(treelike, key) {
15
- assertScopeIsDefined(this);
16
- const tree = Tree.from(treelike);
17
- let previousKey = undefined;
18
- for (const treeKey of await tree.keys()) {
19
- if (treeKey === key) {
20
- return previousKey;
21
- }
22
- previousKey = treeKey;
23
- }
24
- return undefined;
25
- }
26
-
27
- previousKey.usage = `previousKey <tree>, <key>\tReturns the key before the indicated key`;
28
- previousKey.documentation =
29
- "https://weborigami.org/cli/builtins.html#previousKey";