fulmine.js 5.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/view.js ADDED
@@ -0,0 +1,172 @@
1
+ /*
2
+ Copyright 2024 dimden.dev
3
+ Copyright 2026 Nigro Simone
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ */
17
+
18
+ const path = require("path");
19
+ const fs = require("fs");
20
+ const { NullObject } = require("./utils.js");
21
+
22
+ module.exports = class View {
23
+ /**
24
+ * Resolves a template name to a file, so that render() has something to hand the engine. An
25
+ * absolute name is taken as it stands; anything else is looked for under each configured root.
26
+ *
27
+ * @param {string} name template name, with or without its extension
28
+ * @param {any} options defaultEngine, root and engines. Loosely typed on purpose: the
29
+ * constructor throws when what it needs is missing rather than checking each use
30
+ */
31
+ constructor(name, options) {
32
+ this.name = name;
33
+ this.options = options ? Object.assign({}, options) : new NullObject();
34
+ this.defaultEngine = options.defaultEngine;
35
+ this.ext = path.extname(name);
36
+ this.root = options.root;
37
+
38
+ if (!this.ext && !this.defaultEngine) {
39
+ throw new Error("No default engine was specified and no extension was provided.");
40
+ }
41
+
42
+ let fileName = name;
43
+ if (!this.ext) {
44
+ this.ext = this.defaultEngine[0] !== "." ? "." + this.defaultEngine : this.defaultEngine;
45
+
46
+ fileName += this.ext;
47
+ }
48
+
49
+ if (!this.options.engines[this.ext]) {
50
+ const mod = this.ext.slice(1);
51
+
52
+ // default engine export
53
+ const fn = require(mod).__express;
54
+
55
+ if (typeof fn !== "function") {
56
+ throw new Error('Module "' + mod + '" does not provide a view engine.');
57
+ }
58
+
59
+ this.options.engines[this.ext] = fn;
60
+ }
61
+
62
+ this.engine = this.options.engines[this.ext];
63
+ if (path.isAbsolute(name)) {
64
+ this.path = name;
65
+ if (path.extname(name) === "") {
66
+ this.path += this.ext;
67
+ }
68
+ } else {
69
+ this.path = this.lookup(fileName);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * The first of the configured roots that actually holds this template, or undefined when none
75
+ * of them does. `views` may be a single directory or a list, and the list is searched in order,
76
+ * so an application can put its own templates in front of a package's.
77
+ *
78
+ * @param {string} name template file name, relative to a root
79
+ * @returns {string|undefined} absolute path to the file that exists
80
+ */
81
+ lookup(name) {
82
+ let _path;
83
+ const roots = [].concat(this.root);
84
+ for (let i = 0; i < roots.length && !_path; i++) {
85
+ const root = roots[i];
86
+
87
+ // resolve the path
88
+ const loc = path.resolve(root, name);
89
+ const dir = path.dirname(loc);
90
+ const file = path.basename(loc);
91
+
92
+ // resolve the file
93
+ _path = this.resolve(dir, file);
94
+ }
95
+ return _path;
96
+ }
97
+
98
+ /**
99
+ * Renders the template through its engine.
100
+ *
101
+ * The callback is always delivered asynchronously, even when the engine answers on the spot.
102
+ * `sync` is still true only if the engine called back before this function returned, and in
103
+ * that case the callback is pushed to the next tick, so a caller never has to handle both
104
+ * orders. Express normalises it the same way.
105
+ *
106
+ * @param {Record<string, any>} options locals and engine options, passed through untouched
107
+ * @param {Function} callback called with whatever the engine passed, which is normally
108
+ * (err, html) but is forwarded as it came rather than reshaped
109
+ */
110
+ render(options, callback) {
111
+ let sync = true;
112
+ // `this` is whatever the engine called onRender with, and it is forwarded untouched
113
+ this.engine(
114
+ this.path,
115
+ options,
116
+ /** @this {any} */ function onRender() {
117
+ if (!sync) {
118
+ return callback.apply(this, arguments);
119
+ }
120
+
121
+ return process.nextTick(() => {
122
+ return callback.apply(this, arguments);
123
+ });
124
+ }
125
+ );
126
+
127
+ sync = false;
128
+ }
129
+
130
+ /**
131
+ * The file a template name resolves to inside one directory, trying `<name>.<ext>` first and
132
+ * then `<name>/index.<ext>`, which is how a template can be a directory with an index in it.
133
+ *
134
+ * @param {string} dir directory to look in
135
+ * @param {string} file file name, with or without the extension
136
+ * @returns {string|undefined} the path that exists, or undefined when neither does
137
+ */
138
+ resolve(dir, file) {
139
+ const ext = this.ext;
140
+
141
+ // <path>.<ext>
142
+ let _path = path.join(dir, file);
143
+ let stat = tryStat(_path);
144
+
145
+ if (stat && stat.isFile()) {
146
+ return _path;
147
+ }
148
+
149
+ // <path>/index.<ext>
150
+ _path = path.join(dir, path.basename(file, ext), "index" + ext);
151
+ stat = tryStat(_path);
152
+
153
+ if (stat && stat.isFile()) {
154
+ return _path;
155
+ }
156
+ }
157
+ };
158
+
159
+ /**
160
+ * fs.statSync that answers undefined instead of throwing, since a template that is not there is
161
+ * the ordinary case while looking through several roots.
162
+ *
163
+ * @param {string} path
164
+ * @returns {import("fs").Stats|undefined}
165
+ */
166
+ function tryStat(path) {
167
+ try {
168
+ return fs.statSync(path);
169
+ } catch (e) {
170
+ return undefined;
171
+ }
172
+ }
package/src/worker.js ADDED
@@ -0,0 +1,38 @@
1
+ /*
2
+ Copyright 2024 dimden.dev
3
+ Copyright 2026 Nigro Simone
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ */
17
+
18
+ const fs = require("fs");
19
+ const { parentPort } = require("worker_threads");
20
+
21
+ // this file is only ever loaded as a worker, where parentPort is always present. Reading it in the
22
+ // main thread would give null, so say so once here rather than at each use
23
+ if (!parentPort) {
24
+ throw new Error("worker.js must be loaded as a worker thread");
25
+ }
26
+ const port = parentPort;
27
+
28
+ port.on("message", (message) => {
29
+ if (message.type === "readFile") {
30
+ try {
31
+ const data = fs.readFileSync(message.path);
32
+ const ab = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
33
+ port.postMessage({ key: message.key, data: ab }, [ab]);
34
+ } catch (err) {
35
+ port.postMessage({ key: message.key, err: String(err) });
36
+ }
37
+ }
38
+ });