@weborigami/origami 0.0.46 → 0.0.47
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/exports/buildExports.js +2 -2
- package/exports/exports.js +16 -16
- package/package.json +11 -13
- package/src/builtins/@config.js +12 -5
- package/src/builtins/@crawl.js +3 -3
- package/src/builtins/@debug.js +9 -2
- package/src/builtins/@document.js +3 -5
- package/src/builtins/@fnTree.js +5 -2
- package/src/builtins/@inline.js +18 -25
- package/src/builtins/@inners.js +1 -3
- package/src/builtins/@invoke.js +2 -1
- package/src/builtins/@json.js +0 -3
- package/src/builtins/@keysJson.js +1 -4
- package/src/builtins/@mdHtml.js +8 -8
- package/src/builtins/@project.js +22 -14
- package/src/builtins/@shuffle.js +1 -6
- package/src/builtins/@sitemap.js +2 -2
- package/src/builtins/@sort.js +1 -4
- package/src/builtins/@static.js +1 -3
- package/src/builtins/@tree.js +1 -4
- package/src/builtins/@yaml.js +0 -3
- package/src/builtins/css_handler.js +7 -0
- package/src/builtins/htm_handler.js +2 -0
- package/src/builtins/html_handler.js +7 -0
- package/src/builtins/jpeg_handler.js +58 -0
- package/src/builtins/jpg_handler.js +2 -0
- package/src/builtins/js_handler.js +20 -0
- package/src/builtins/json_handler.js +19 -0
- package/src/builtins/md_handler.js +7 -0
- package/src/builtins/mjs_handler.js +2 -0
- package/src/builtins/ori_handler.js +48 -0
- package/src/builtins/txt_handler.js +78 -0
- package/src/builtins/wasm_handler.js +17 -0
- package/src/builtins/xhtml_handler.js +2 -0
- package/src/builtins/yaml_handler.js +29 -0
- package/src/builtins/yml_handler.js +2 -0
- package/src/common/ExplorableSiteTransform.js +5 -1
- package/src/common/addValueKeyToScope.js +3 -2
- package/src/common/documentObject.js +33 -0
- package/src/common/serialize.d.ts +1 -0
- package/src/common/serialize.js +68 -27
- package/src/common/utilities.js +9 -7
- package/src/misc/getTreeArgument.js +12 -2
- package/src/server/constructResponse.js +18 -5
- package/src/server/server.js +5 -4
- package/src/builtins/@loaders/css.js +0 -2
- package/src/builtins/@loaders/htm.js +0 -2
- package/src/builtins/@loaders/html.js +0 -2
- package/src/builtins/@loaders/jpeg.js +0 -55
- package/src/builtins/@loaders/jpg.js +0 -2
- package/src/builtins/@loaders/js.js +0 -14
- package/src/builtins/@loaders/json.js +0 -14
- package/src/builtins/@loaders/md.js +0 -2
- package/src/builtins/@loaders/mjs.js +0 -2
- package/src/builtins/@loaders/ori.js +0 -45
- package/src/builtins/@loaders/txt.js +0 -37
- package/src/builtins/@loaders/wasm.js +0 -11
- package/src/builtins/@loaders/xhtml.js +0 -2
- package/src/builtins/@loaders/yaml.js +0 -23
- package/src/builtins/@loaders/yml.js +0 -2
- package/src/common/TextDocument.js +0 -58
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import exifParser from "exif-parser";
|
|
2
|
+
|
|
3
|
+
const exifDateTags = [
|
|
4
|
+
"ModifyDate",
|
|
5
|
+
"MDPrepDate",
|
|
6
|
+
"DateTimeOriginal",
|
|
7
|
+
"CreateDate",
|
|
8
|
+
"PreviewDateTime",
|
|
9
|
+
"GPSDateStamp",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A JPEG file with possible Exif metadata
|
|
14
|
+
*/
|
|
15
|
+
export default {
|
|
16
|
+
mediaType: "image/jpeg",
|
|
17
|
+
|
|
18
|
+
/** @type {import("@weborigami/language").UnpackFunction} */
|
|
19
|
+
async unpack(packed, options) {
|
|
20
|
+
const parser = exifParser.create(packed);
|
|
21
|
+
parser.enableTagNames(true);
|
|
22
|
+
parser.enableSimpleValues(true);
|
|
23
|
+
const parsed = await parser.parse();
|
|
24
|
+
|
|
25
|
+
// The exif-parser `enableSimpleValues` option should convert dates to
|
|
26
|
+
// JavaScript Date objects, but that doesn't seem to work. Ensure dates are
|
|
27
|
+
// Date objects.
|
|
28
|
+
const exif = parsed.tags;
|
|
29
|
+
for (const tag of exifDateTags) {
|
|
30
|
+
if (typeof exif[tag] === "number") {
|
|
31
|
+
exif[tag] = new Date(exif[tag] * 1000);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const result = {
|
|
36
|
+
height: parsed.imageSize.height,
|
|
37
|
+
width: parsed.imageSize.width,
|
|
38
|
+
exif,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Promote some Exif properties to the top level.
|
|
42
|
+
const tagsToPromote = {
|
|
43
|
+
ImageDescription: "caption",
|
|
44
|
+
ModifyDate: "modified",
|
|
45
|
+
Orientation: "orientation",
|
|
46
|
+
};
|
|
47
|
+
for (const [tag, key] of Object.entries(tagsToPromote)) {
|
|
48
|
+
if (exif[tag] !== undefined) {
|
|
49
|
+
result[key] = exif[tag];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Add aspect ratio for use with `aspect-ratio` CSS.
|
|
54
|
+
result.aspectRatio = result.width / result.height;
|
|
55
|
+
|
|
56
|
+
return result;
|
|
57
|
+
},
|
|
58
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import processUnpackedContent from "../common/processUnpackedContent.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A JavaScript file
|
|
5
|
+
*
|
|
6
|
+
* Unpacking a JavaScript file returns its default export, or its set of exports
|
|
7
|
+
* if there is more than one.
|
|
8
|
+
*/
|
|
9
|
+
export default {
|
|
10
|
+
mediaType: "application/javascript",
|
|
11
|
+
|
|
12
|
+
/** @type {import("@weborigami/language").UnpackFunction} */
|
|
13
|
+
async unpack(packed, options = {}) {
|
|
14
|
+
const { key, parent } = options;
|
|
15
|
+
if (parent && "import" in parent) {
|
|
16
|
+
const content = await /** @type {any} */ (parent).import?.(key);
|
|
17
|
+
return processUnpackedContent(content, parent);
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as utilities from "../common/utilities.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A JSON file
|
|
5
|
+
*
|
|
6
|
+
* Unpacking a JSON file returns the parsed data.
|
|
7
|
+
*/
|
|
8
|
+
export default {
|
|
9
|
+
mediaType: "application/json",
|
|
10
|
+
|
|
11
|
+
/** @type {import("@weborigami/language").UnpackFunction} */
|
|
12
|
+
unpack(packed) {
|
|
13
|
+
const json = utilities.toString(packed);
|
|
14
|
+
if (!json) {
|
|
15
|
+
throw new Error("Tried to parse something as JSON but it wasn't text.");
|
|
16
|
+
}
|
|
17
|
+
return JSON.parse(json);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { symbols } from "@weborigami/async-tree";
|
|
2
|
+
import { Scope, compile } from "@weborigami/language";
|
|
3
|
+
import processUnpackedContent from "../common/processUnpackedContent.js";
|
|
4
|
+
import * as utilities from "../common/utilities.js";
|
|
5
|
+
import builtins from "./@builtins.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* An Origami expression file
|
|
9
|
+
*
|
|
10
|
+
* Unpacking an Origami file returns the result of evaluating the expression.
|
|
11
|
+
*/
|
|
12
|
+
export default {
|
|
13
|
+
mediaType: "text/plain",
|
|
14
|
+
|
|
15
|
+
/** @type {import("@weborigami/language").UnpackFunction} */
|
|
16
|
+
async unpack(packed, options = {}) {
|
|
17
|
+
const attachedData = options.attachedData;
|
|
18
|
+
const parent =
|
|
19
|
+
options.parent ??
|
|
20
|
+
/** @type {any} */ (packed).parent ??
|
|
21
|
+
/** @type {any} */ (packed)[symbols.parent];
|
|
22
|
+
|
|
23
|
+
// Construct an object to represent the source code.
|
|
24
|
+
const sourceName = options.key;
|
|
25
|
+
let url;
|
|
26
|
+
if (sourceName && parent?.url) {
|
|
27
|
+
let parentHref = parent.url.href;
|
|
28
|
+
if (!parentHref.endsWith("/")) {
|
|
29
|
+
parentHref += "/";
|
|
30
|
+
}
|
|
31
|
+
url = new URL(sourceName, parentHref);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const source = {
|
|
35
|
+
text: utilities.toString(packed),
|
|
36
|
+
name: options.key,
|
|
37
|
+
url,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Compile the source code as an Origami expression and evaluate it.
|
|
41
|
+
const compiler = options.compiler ?? compile.expression;
|
|
42
|
+
const fn = compiler(source);
|
|
43
|
+
const parentScope = parent ? Scope.getScope(parent) : builtins;
|
|
44
|
+
let content = await fn.call(parentScope);
|
|
45
|
+
|
|
46
|
+
return processUnpackedContent(content, parent, attachedData);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { isPacked, symbols } from "@weborigami/async-tree";
|
|
2
|
+
import { evaluateYaml, toYaml } from "../common/serialize.js";
|
|
3
|
+
import * as utilities from "../common/utilities.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A text file with possible front matter
|
|
7
|
+
*
|
|
8
|
+
* The unpacking process will parse out any YAML or JSON front matter and attach
|
|
9
|
+
* it to the document as data. The first line of the text must be "---",
|
|
10
|
+
* followed by a block of JSON or YAML, followed by another line of "---". Any
|
|
11
|
+
* lines following will be treated as the document text.
|
|
12
|
+
*
|
|
13
|
+
* If there is no front matter, the document will be treated as plain text and
|
|
14
|
+
* returned as a String object.
|
|
15
|
+
*
|
|
16
|
+
* If there is front matter, any Origami expressions in the front matter will be
|
|
17
|
+
* evaluated. The result will be a plain JavaScript object with the evaluated
|
|
18
|
+
* data and a `@text` property containing the document text.
|
|
19
|
+
*/
|
|
20
|
+
export default {
|
|
21
|
+
mediaType: "text/plain",
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* If the input is already in some packed format, it will be returned as is.
|
|
25
|
+
*
|
|
26
|
+
* Otherwise, the properties of the object will be formatted as YAML. If the
|
|
27
|
+
* object has a `@text` property, that will be used as the body of the text
|
|
28
|
+
* document; otherwise, an empty string will be used.
|
|
29
|
+
*
|
|
30
|
+
* @param {any} object
|
|
31
|
+
* @returns {Promise<import("@weborigami/async-tree").Packed>}
|
|
32
|
+
*/
|
|
33
|
+
async pack(object) {
|
|
34
|
+
if (isPacked(object)) {
|
|
35
|
+
return object;
|
|
36
|
+
} else if (!object || typeof object !== "object") {
|
|
37
|
+
throw new TypeError("The input to pack must be a JavaScript object.");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const text = object["@text"] ?? "";
|
|
41
|
+
|
|
42
|
+
/** @type {any} */
|
|
43
|
+
const dataWithoutText = Object.assign({}, object);
|
|
44
|
+
delete dataWithoutText["@text"];
|
|
45
|
+
if (Object.keys(dataWithoutText).length > 0) {
|
|
46
|
+
const frontMatter = (await toYaml(dataWithoutText)).trimEnd();
|
|
47
|
+
return `---\n${frontMatter}\n---\n${text}`;
|
|
48
|
+
} else {
|
|
49
|
+
return text;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
/** @type {import("@weborigami/language").UnpackFunction} */
|
|
54
|
+
async unpack(packed, options = {}) {
|
|
55
|
+
const parent = options.parent ?? null;
|
|
56
|
+
const text = utilities.toString(packed);
|
|
57
|
+
if (!text) {
|
|
58
|
+
throw new Error("Tried to treat something as text but it wasn't text.");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const regex =
|
|
62
|
+
/^(---\r?\n(?<frontText>[\s\S]*?\r?\n?)---\r?\n)(?<body>[\s\S]*$)/;
|
|
63
|
+
const match = regex.exec(text);
|
|
64
|
+
let unpacked;
|
|
65
|
+
if (match) {
|
|
66
|
+
// Document object with front matter
|
|
67
|
+
const { body, frontText } = /** @type {any} */ (match.groups);
|
|
68
|
+
const frontData = await evaluateYaml(frontText, parent);
|
|
69
|
+
unpacked = Object.assign({}, frontData, { "@text": body });
|
|
70
|
+
} else {
|
|
71
|
+
// Plain text
|
|
72
|
+
unpacked = new String(text);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
unpacked[symbols.parent] = parent;
|
|
76
|
+
return unpacked;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import processUnpackedContent from "../common/processUnpackedContent.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A WebAssembly module
|
|
5
|
+
*
|
|
6
|
+
* Unpacking a WebAssembly module returns its exports.
|
|
7
|
+
*/
|
|
8
|
+
export default {
|
|
9
|
+
mediaType: "application/wasm",
|
|
10
|
+
|
|
11
|
+
/** @type {import("@weborigami/language").UnpackFunction} */
|
|
12
|
+
async unpack(packed, options = {}) {
|
|
13
|
+
const wasmModule = await WebAssembly.instantiate(packed);
|
|
14
|
+
// @ts-ignore TypeScript thinks wasmModule is already an Instance.
|
|
15
|
+
return processUnpackedContent(wasmModule.instance.exports, options.parent);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as YAMLModule from "yaml";
|
|
2
|
+
import processUnpackedContent from "../common/processUnpackedContent.js";
|
|
3
|
+
import { evaluateYaml } from "../common/serialize.js";
|
|
4
|
+
import * as utilities from "../common/utilities.js";
|
|
5
|
+
|
|
6
|
+
// See notes at serialize.js
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
const YAML = YAMLModule.default ?? YAMLModule.YAML;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A YAML file
|
|
12
|
+
*
|
|
13
|
+
* Unpacking a YAML file returns the parsed data.
|
|
14
|
+
*
|
|
15
|
+
*/
|
|
16
|
+
export default {
|
|
17
|
+
mediaType: "application/yaml",
|
|
18
|
+
|
|
19
|
+
/** @type {import("@weborigami/language").UnpackFunction} */
|
|
20
|
+
async unpack(packed, options = {}) {
|
|
21
|
+
const parent = options.parent ?? null;
|
|
22
|
+
const yaml = utilities.toString(packed);
|
|
23
|
+
if (!yaml) {
|
|
24
|
+
throw new Error("Tried to parse something as YAML but it wasn't text.");
|
|
25
|
+
}
|
|
26
|
+
const data = await evaluateYaml(yaml, options.parent);
|
|
27
|
+
return processUnpackedContent(data, parent);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -59,11 +59,15 @@ export default function ExplorableSiteTransform(Base) {
|
|
|
59
59
|
const original = value.unpack.bind(value);
|
|
60
60
|
value.unpack = async () => {
|
|
61
61
|
const content = await original();
|
|
62
|
-
if (!Tree.
|
|
62
|
+
if (!Tree.isTraversable(content)) {
|
|
63
63
|
return content;
|
|
64
64
|
}
|
|
65
65
|
/** @type {any} */
|
|
66
66
|
let tree = Tree.from(content);
|
|
67
|
+
if (!tree.parent && !tree.scope) {
|
|
68
|
+
const scope = Scope.getScope(this);
|
|
69
|
+
tree = Scope.treeWithScope(tree, scope);
|
|
70
|
+
}
|
|
67
71
|
tree = transformObject(ExplorableSiteTransform, tree);
|
|
68
72
|
return tree;
|
|
69
73
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ObjectTree } from "@weborigami/async-tree";
|
|
1
2
|
import { Scope } from "@weborigami/language";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -14,9 +15,9 @@ import { Scope } from "@weborigami/language";
|
|
|
14
15
|
*/
|
|
15
16
|
export default function addValueKeyToScope(scope, value, key) {
|
|
16
17
|
// Add the key and value to the scope as ambients.
|
|
17
|
-
const ambients = {
|
|
18
|
+
const ambients = new ObjectTree({
|
|
18
19
|
"@key": key,
|
|
19
20
|
_: value,
|
|
20
|
-
};
|
|
21
|
+
});
|
|
21
22
|
return new Scope(ambients, scope);
|
|
22
23
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { isPlainObject } from "@weborigami/async-tree";
|
|
2
|
+
import txtHandler from "../builtins/txt_handler.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* In Origami, a text document object is any object with a `@text` property and
|
|
6
|
+
* a pack() method that formats that object as text with YAML front matter. This
|
|
7
|
+
* function is a helper for constructing such text document objects.
|
|
8
|
+
*
|
|
9
|
+
* @typedef {import("@weborigami/async-tree").StringLike} StringLike
|
|
10
|
+
* @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
|
|
11
|
+
*
|
|
12
|
+
* @param {StringLike|PlainObject} input
|
|
13
|
+
* @param {any} [data]
|
|
14
|
+
*/
|
|
15
|
+
export default function documentObject(input, data) {
|
|
16
|
+
let text;
|
|
17
|
+
let inputData;
|
|
18
|
+
if (isPlainObject(input)) {
|
|
19
|
+
text = input["@text"];
|
|
20
|
+
inputData = input;
|
|
21
|
+
} else {
|
|
22
|
+
text = String(input);
|
|
23
|
+
inputData = null;
|
|
24
|
+
}
|
|
25
|
+
const base = {
|
|
26
|
+
pack() {
|
|
27
|
+
return txtHandler.pack(this);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
const result = Object.create(base);
|
|
31
|
+
Object.assign(result, inputData, data, { "@text": text });
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
@@ -5,4 +5,5 @@ export function evaluateYaml(text: string, parent?: AsyncTree|null): Promise<Jso
|
|
|
5
5
|
export function parseYaml(text: string): JsonValue|AsyncTree;
|
|
6
6
|
export function toJson(obj: JsonValue | AsyncTree): Promise<string>;
|
|
7
7
|
export function toJsonValue(obj: any): Promise<JsonValue>;
|
|
8
|
+
export function toValue(obj: any, jsonValuesOnly?: boolean): Promise<JsonValue>;
|
|
8
9
|
export function toYaml(obj: JsonValue | AsyncTree): Promise<string>;
|
package/src/common/serialize.js
CHANGED
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
Tree,
|
|
10
|
+
isPlainObject,
|
|
11
|
+
isStringLike,
|
|
12
|
+
isUnpackable,
|
|
13
|
+
} from "@weborigami/async-tree";
|
|
9
14
|
import { OrigamiTree } from "@weborigami/language";
|
|
10
15
|
import * as YAMLModule from "yaml";
|
|
11
16
|
import yamlOrigamiTag from "../misc/yamlOrigamiTag.js";
|
|
@@ -90,40 +95,76 @@ export async function toJson(obj) {
|
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
/**
|
|
93
|
-
* Convert the given object to a corresponding JSON value that can be
|
|
94
|
-
* as JSON or YAML.
|
|
98
|
+
* Convert the given object to a corresponding JSON value that can be
|
|
99
|
+
* represented as JSON or YAML.
|
|
95
100
|
*
|
|
96
|
-
*
|
|
101
|
+
* @param {any} object
|
|
102
|
+
* @returns {Promise<JsonValue>}
|
|
103
|
+
*/
|
|
104
|
+
export async function toJsonValue(object) {
|
|
105
|
+
return toValue(object, true);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Convert the given input to the plainest possible JavaScript value. This
|
|
110
|
+
* helper is intended for functions that want to accept an argument from the ori
|
|
111
|
+
* CLI, which could a string, a file buffer, an ArrayBuffer from a URL, or some
|
|
112
|
+
* other kind of JavaScript object.
|
|
97
113
|
*
|
|
98
|
-
* If the
|
|
99
|
-
*
|
|
114
|
+
* If the input implements the `unpack()` method, the input will be unpacked and
|
|
115
|
+
* before processing.
|
|
100
116
|
*
|
|
101
|
-
* If the
|
|
102
|
-
*
|
|
103
|
-
* types.
|
|
117
|
+
* If the input is treelike, it will be converted to a plain JavaScript object,
|
|
118
|
+
* recursively traversing the tree and converting all values to plain types.
|
|
104
119
|
*
|
|
105
|
-
* If the
|
|
106
|
-
* will be returned.
|
|
120
|
+
* If the input is stringlike, its text will be returned.
|
|
107
121
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
122
|
+
* If the input is a Buffer or ArrayBuffer, it will be interpreted as UTF-8
|
|
123
|
+
* text.
|
|
124
|
+
*
|
|
125
|
+
* If the input has a custom class instance, its public properties will be
|
|
126
|
+
* returned as a plain object.
|
|
127
|
+
*
|
|
128
|
+
* The `jsonValuesOnly` parameter can be set to `true` to ensure that the
|
|
129
|
+
* returned value can be represented as JSON. If the input can't be represented
|
|
130
|
+
* as JSON, an error is thrown.
|
|
131
|
+
*
|
|
132
|
+
* @param {any} input
|
|
133
|
+
* @param {boolean} [jsonValuesOnly]
|
|
134
|
+
* @returns {Promise<any>}
|
|
110
135
|
*/
|
|
111
|
-
export async function
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
136
|
+
export async function toValue(input, jsonValuesOnly = false) {
|
|
137
|
+
if (input instanceof Promise) {
|
|
138
|
+
// Resolve promise before processing.
|
|
139
|
+
return toValue(await input, jsonValuesOnly);
|
|
140
|
+
} else if (isJsonValue(input)) {
|
|
141
|
+
return input;
|
|
142
|
+
} else if (typeof input !== "object") {
|
|
143
|
+
if (jsonValuesOnly) {
|
|
144
|
+
throw new TypeError(`Couldn't serialize value to JSON: ${input}`);
|
|
145
|
+
} else {
|
|
146
|
+
return input;
|
|
147
|
+
}
|
|
148
|
+
} else if (isUnpackable(input)) {
|
|
149
|
+
// Unpack first, then convert to JSON value.
|
|
150
|
+
const unpacked = await input.unpack();
|
|
151
|
+
return toValue(unpacked);
|
|
152
|
+
} else if (isStringLike(input) && !(input instanceof Array)) {
|
|
153
|
+
return String(input);
|
|
154
|
+
} else if (Tree.isTreelike(input)) {
|
|
155
|
+
const mapped = await Tree.map(input, (value) => toValue(value));
|
|
120
156
|
return Tree.plain(mapped);
|
|
121
|
-
} else if (
|
|
122
|
-
//
|
|
123
|
-
return textDecoder.decode(
|
|
157
|
+
} else if (input instanceof ArrayBuffer || input instanceof TypedArray) {
|
|
158
|
+
// Interpret input as UTF-8 text.
|
|
159
|
+
return textDecoder.decode(input);
|
|
160
|
+
} else {
|
|
161
|
+
// Some other kind of class instance; return its public properties.
|
|
162
|
+
const plain = {};
|
|
163
|
+
for (const [key, value] of Object.entries(input)) {
|
|
164
|
+
plain[key] = await toValue(value);
|
|
165
|
+
}
|
|
166
|
+
return plain;
|
|
124
167
|
}
|
|
125
|
-
|
|
126
|
-
throw new TypeError("Couldn't serialize object");
|
|
127
168
|
}
|
|
128
169
|
|
|
129
170
|
/**
|
package/src/common/utilities.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Tree,
|
|
3
|
+
isPlainObject,
|
|
4
|
+
isStringLike,
|
|
5
|
+
isUnpackable,
|
|
6
|
+
} from "@weborigami/async-tree";
|
|
2
7
|
|
|
3
8
|
const textDecoder = new TextDecoder();
|
|
4
9
|
const TypedArray = Object.getPrototypeOf(Uint8Array);
|
|
@@ -57,16 +62,13 @@ export function toFunction(obj) {
|
|
|
57
62
|
if (typeof obj === "function") {
|
|
58
63
|
// Return a function as is.
|
|
59
64
|
return obj;
|
|
60
|
-
} else if (
|
|
61
|
-
typeof obj === "object" &&
|
|
62
|
-
typeof (/** @type {any} */ (obj)?.unpack) === "function"
|
|
63
|
-
) {
|
|
65
|
+
} else if (isUnpackable(obj)) {
|
|
64
66
|
// Extract the contents of the object and convert that to a function.
|
|
65
67
|
let fn;
|
|
66
68
|
/** @this {any} */
|
|
67
69
|
return async function (...args) {
|
|
68
70
|
if (!fn) {
|
|
69
|
-
const content = await
|
|
71
|
+
const content = await obj.unpack();
|
|
70
72
|
fn = toFunction(content);
|
|
71
73
|
}
|
|
72
74
|
return fn.call(this, ...args);
|
|
@@ -100,7 +102,7 @@ export function toString(object) {
|
|
|
100
102
|
if (isPlainObject(object) && "@text" in object) {
|
|
101
103
|
return object["@text"];
|
|
102
104
|
} else if (object instanceof ArrayBuffer || object instanceof TypedArray) {
|
|
103
|
-
//
|
|
105
|
+
// Treat the buffer as UTF-8 text.
|
|
104
106
|
const decoded = textDecoder.decode(object);
|
|
105
107
|
// If the result has non-printable characters, it's probably not a string.
|
|
106
108
|
return hasNonPrintableCharacters(decoded) ? null : decoded;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Tree } from "@weborigami/async-tree";
|
|
1
|
+
import { Tree, isUnpackable } from "@weborigami/async-tree";
|
|
2
2
|
import { isTreelike } from "@weborigami/async-tree/src/Tree.js";
|
|
3
|
+
import { Scope } from "@weborigami/language";
|
|
3
4
|
import assertScopeIsDefined from "./assertScopeIsDefined.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -29,8 +30,17 @@ export default async function getTreeArgument(
|
|
|
29
30
|
assertScopeIsDefined(scope);
|
|
30
31
|
|
|
31
32
|
if (treelike !== undefined) {
|
|
33
|
+
if (isUnpackable(treelike)) {
|
|
34
|
+
treelike = await treelike.unpack();
|
|
35
|
+
}
|
|
32
36
|
if (isTreelike(treelike)) {
|
|
33
|
-
|
|
37
|
+
let tree = Tree.from(treelike);
|
|
38
|
+
// If the tree was created from a treelike object and does not yet have a
|
|
39
|
+
// parent or scope, put it in the current scope.
|
|
40
|
+
if (!tree.parent && !(/** @type {any} */ (tree).scope)) {
|
|
41
|
+
tree = Scope.treeWithScope(tree, scope);
|
|
42
|
+
}
|
|
43
|
+
return tree;
|
|
34
44
|
}
|
|
35
45
|
throw new Error(
|
|
36
46
|
`${methodName}: The first argument must be a tree, like an array, object, or files.`
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SiteTree,
|
|
3
3
|
Tree,
|
|
4
|
+
isPacked,
|
|
4
5
|
isPlainObject,
|
|
5
6
|
isStringLike,
|
|
6
7
|
} from "@weborigami/async-tree";
|
|
@@ -30,8 +31,16 @@ export default async function constructResponse(request, resource) {
|
|
|
30
31
|
|
|
31
32
|
// Determine media type, what data we'll send, and encoding.
|
|
32
33
|
const url = new URL(request.url ?? "", `https://${request.headers.host}`);
|
|
33
|
-
|
|
34
|
-
let mediaType
|
|
34
|
+
|
|
35
|
+
let mediaType;
|
|
36
|
+
if (resource.mediaType) {
|
|
37
|
+
// Resource indicates its own media type.
|
|
38
|
+
mediaType = resource.mediaType;
|
|
39
|
+
} else {
|
|
40
|
+
// Infer expected media type from file extension on request URL.
|
|
41
|
+
const extension = extname(url.pathname).toLowerCase();
|
|
42
|
+
mediaType = extension ? mediaTypeForExtension[extension] : undefined;
|
|
43
|
+
}
|
|
35
44
|
|
|
36
45
|
if (
|
|
37
46
|
mediaType === undefined &&
|
|
@@ -50,12 +59,16 @@ export default async function constructResponse(request, resource) {
|
|
|
50
59
|
});
|
|
51
60
|
}
|
|
52
61
|
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
if (!isPacked(resource) && typeof resource.pack === "function") {
|
|
63
|
+
resource = await resource.pack();
|
|
64
|
+
}
|
|
65
|
+
|
|
55
66
|
if (
|
|
56
67
|
(mediaType === "application/json" || mediaType === "text/yaml") &&
|
|
57
68
|
!isStringLike(resource)
|
|
58
69
|
) {
|
|
70
|
+
// The request is for a JSON or YAML result, and the resource we got isn't
|
|
71
|
+
// yet a string or Buffer: convert the resource to JSON or YAML now.
|
|
59
72
|
const tree = Tree.from(resource);
|
|
60
73
|
resource =
|
|
61
74
|
mediaType === "text/yaml"
|
|
@@ -92,7 +105,7 @@ export default async function constructResponse(request, resource) {
|
|
|
92
105
|
|
|
93
106
|
// If we didn't get back some kind of data that response.write() accepts,
|
|
94
107
|
// assume it was an error.
|
|
95
|
-
const validResponse =
|
|
108
|
+
const validResponse = isPacked(body);
|
|
96
109
|
if (!validResponse) {
|
|
97
110
|
const typeName = body?.constructor?.name ?? typeof body;
|
|
98
111
|
console.error(
|
package/src/server/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DeepObjectTree, Tree, keysFromPath } from "@weborigami/async-tree";
|
|
2
2
|
import { Scope, formatError } from "@weborigami/language";
|
|
3
3
|
import { ServerResponse } from "node:http";
|
|
4
4
|
import constructResponse from "./constructResponse.js";
|
|
@@ -50,15 +50,16 @@ function extendTreeScopeWithParams(tree, url) {
|
|
|
50
50
|
return tree;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const paramTree = new
|
|
53
|
+
const paramTree = new DeepObjectTree({
|
|
54
54
|
"@params": params,
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
// Create a new scope that includes search parameter tree.
|
|
58
|
-
const
|
|
58
|
+
const scope = Scope.getScope(tree);
|
|
59
|
+
const extendedScope = new Scope(paramTree, scope);
|
|
59
60
|
|
|
60
61
|
// Create a new tree that extends the prototype chain of the supplied tree.
|
|
61
|
-
const extendedTree = Scope.treeWithScope(tree,
|
|
62
|
+
const extendedTree = Scope.treeWithScope(tree, extendedScope);
|
|
62
63
|
|
|
63
64
|
return extendedTree;
|
|
64
65
|
}
|