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/EXPRESS_LICENSE +26 -0
- package/LICENSE +202 -0
- package/NOTICE +38 -0
- package/README.md +469 -0
- package/package.json +165 -0
- package/src/application.js +561 -0
- package/src/cli.js +369 -0
- package/src/declarative.js +768 -0
- package/src/index.js +71 -0
- package/src/middlewares.js +636 -0
- package/src/node-shim.js +400 -0
- package/src/request.js +807 -0
- package/src/response.js +1360 -0
- package/src/router.js +1240 -0
- package/src/types.d.ts +62 -0
- package/src/utils.js +993 -0
- package/src/view.js +172 -0
- package/src/worker.js +38 -0
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
|
+
});
|