@faasjs/node-utils 8.0.0-beta.7
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/LICENSE +21 -0
- package/README.md +31 -0
- package/dist/index.cjs +221 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.mjs +212 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019--present, Zhu Feng
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @faasjs/node-utils
|
|
2
|
+
|
|
3
|
+
FaasJS Node.js utility toolkit.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/faasjs/faasjs/blob/main/packages/node-utils/LICENSE)
|
|
6
|
+
[](https://www.npmjs.com/package/@faasjs/node-utils)
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install @faasjs/node-utils
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Functions
|
|
15
|
+
|
|
16
|
+
- [deepMerge](functions/deepMerge.md)
|
|
17
|
+
- [detectNodeRuntime](functions/detectNodeRuntime.md)
|
|
18
|
+
- [loadConfig](functions/loadConfig.md)
|
|
19
|
+
- [loadFunc](functions/loadFunc.md)
|
|
20
|
+
- [loadPackage](functions/loadPackage.md)
|
|
21
|
+
- [resetRuntime](functions/resetRuntime.md)
|
|
22
|
+
- [streamToObject](functions/streamToObject.md)
|
|
23
|
+
- [streamToText](functions/streamToText.md)
|
|
24
|
+
|
|
25
|
+
## Type Aliases
|
|
26
|
+
|
|
27
|
+
- [NodeRuntime](type-aliases/NodeRuntime.md)
|
|
28
|
+
|
|
29
|
+
## Variables
|
|
30
|
+
|
|
31
|
+
- [streamToString](variables/streamToString.md)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
let node_fs = require("node:fs");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
let _faasjs_logger = require("@faasjs/logger");
|
|
5
|
+
let js_yaml = require("js-yaml");
|
|
6
|
+
|
|
7
|
+
//#region src/deep_merge.ts
|
|
8
|
+
const shouldMerge = (item) => {
|
|
9
|
+
const type = Object.prototype.toString.call(item);
|
|
10
|
+
return type === "[object Object]" || type === "[object Array]";
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Deep merge two objects or arrays.
|
|
14
|
+
*
|
|
15
|
+
* Features:
|
|
16
|
+
* * All objects will be cloned before merging.
|
|
17
|
+
* * Merging order is from right to left.
|
|
18
|
+
* * If an array include same objects, it will be unique to one.
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* deepMerge({ a: 1 }, { a: 2 }) // { a: 2 }
|
|
22
|
+
* deepMerge([1, 2], [2, 3]) // [1, 2, 3]
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
function deepMerge(...sources) {
|
|
26
|
+
let acc = Object.create(null);
|
|
27
|
+
for (const source of sources) if (Array.isArray(source)) {
|
|
28
|
+
if (!Array.isArray(acc)) acc = [];
|
|
29
|
+
acc = [...new Set(source.concat(...acc))];
|
|
30
|
+
} else if (shouldMerge(source)) for (const [key, value] of Object.entries(source)) {
|
|
31
|
+
let val;
|
|
32
|
+
if (shouldMerge(value)) val = deepMerge(acc[key], value);
|
|
33
|
+
else val = value;
|
|
34
|
+
acc = {
|
|
35
|
+
...acc,
|
|
36
|
+
[key]: val
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return acc;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/load_config.ts
|
|
44
|
+
function isObject(value) {
|
|
45
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
46
|
+
}
|
|
47
|
+
function createConfigError(filePath, keyPath, reason) {
|
|
48
|
+
return Error(`[loadConfig] Invalid faas.yaml ${filePath} at "${keyPath}": ${reason}`);
|
|
49
|
+
}
|
|
50
|
+
function validateServerConfig(filePath, staging, server) {
|
|
51
|
+
if (!isObject(server)) throw createConfigError(filePath, `${staging}.server`, "must be an object");
|
|
52
|
+
if (typeof server.root !== "undefined" && typeof server.root !== "string") throw createConfigError(filePath, `${staging}.server.root`, "must be a string");
|
|
53
|
+
if (typeof server.base !== "undefined" && typeof server.base !== "string") throw createConfigError(filePath, `${staging}.server.base`, "must be a string");
|
|
54
|
+
}
|
|
55
|
+
function validateFaasYaml(filePath, config) {
|
|
56
|
+
if (typeof config === "undefined" || config === null) return Object.create(null);
|
|
57
|
+
if (!isObject(config)) throw createConfigError(filePath, "<root>", "must be an object");
|
|
58
|
+
for (const staging in config) {
|
|
59
|
+
if (staging === "types") throw createConfigError(filePath, "types", "has been removed, move related settings out of faas.yaml");
|
|
60
|
+
const stageConfig = config[staging];
|
|
61
|
+
if (typeof stageConfig === "undefined" || stageConfig === null) continue;
|
|
62
|
+
if (!isObject(stageConfig)) throw createConfigError(filePath, staging, "must be an object");
|
|
63
|
+
if (Object.hasOwn(stageConfig, "types")) throw createConfigError(filePath, `${staging}.types`, "has been removed, move related settings out of faas.yaml");
|
|
64
|
+
if (Object.hasOwn(stageConfig, "server")) validateServerConfig(filePath, staging, stageConfig.server);
|
|
65
|
+
}
|
|
66
|
+
return config;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Load configuration from faas.yaml
|
|
70
|
+
*/
|
|
71
|
+
var Config = class {
|
|
72
|
+
root;
|
|
73
|
+
filename;
|
|
74
|
+
origin;
|
|
75
|
+
defaults;
|
|
76
|
+
logger;
|
|
77
|
+
constructor(root, filename, logger) {
|
|
78
|
+
this.logger = new _faasjs_logger.Logger(logger?.label ? `${logger.label}] [config` : "config");
|
|
79
|
+
this.root = root;
|
|
80
|
+
if (!this.root.endsWith(node_path.sep)) this.root += node_path.sep;
|
|
81
|
+
this.filename = filename;
|
|
82
|
+
this.logger.debug("load %s in %s", filename, root);
|
|
83
|
+
const configs = [];
|
|
84
|
+
[this.root, "."].concat((0, node_path.dirname)(filename.replace(root, "")).split(node_path.sep)).reduce((base, path) => {
|
|
85
|
+
const root = (0, node_path.join)(base, path);
|
|
86
|
+
if (root === base) return base;
|
|
87
|
+
const faas = (0, node_path.join)(root, "faas.yaml");
|
|
88
|
+
if ((0, node_fs.existsSync)(faas)) configs.push(validateFaasYaml(faas, (0, js_yaml.load)((0, node_fs.readFileSync)(faas).toString())));
|
|
89
|
+
return root;
|
|
90
|
+
});
|
|
91
|
+
this.origin = deepMerge(...configs);
|
|
92
|
+
this.defaults = deepMerge(this.origin.defaults || {});
|
|
93
|
+
for (const key in this.origin) {
|
|
94
|
+
if (key !== "defaults") this[key] = deepMerge(this.defaults, this.origin[key]);
|
|
95
|
+
const data = this[key];
|
|
96
|
+
if (data.plugins) for (const pluginKey in data.plugins) {
|
|
97
|
+
const plugin = data.plugins[pluginKey];
|
|
98
|
+
plugin.name = pluginKey;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
get(key) {
|
|
103
|
+
return this[key] || this.defaults || Object.create(null);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Load configuration from faas.yaml
|
|
108
|
+
*/
|
|
109
|
+
function loadConfig(root, filename, staging, logger) {
|
|
110
|
+
return new Config(root, filename, logger).get(staging);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/load_package.ts
|
|
115
|
+
let _runtime = null;
|
|
116
|
+
function resetRuntime() {
|
|
117
|
+
_runtime = null;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Detect current JavaScript runtime environment.
|
|
121
|
+
*
|
|
122
|
+
* This function checks for presence of `require` first, then falls back to
|
|
123
|
+
* Node.js ESM detection via `process.versions.node`.
|
|
124
|
+
*
|
|
125
|
+
* @returns {NodeRuntime} Returns `module` for ESM and `commonjs` for CJS.
|
|
126
|
+
* @throws {Error} Throws an error if runtime cannot be determined.
|
|
127
|
+
*/
|
|
128
|
+
function detectNodeRuntime() {
|
|
129
|
+
if (_runtime) return _runtime;
|
|
130
|
+
if (typeof globalThis.require === "function" && typeof module !== "undefined") return _runtime = "commonjs";
|
|
131
|
+
if (typeof process !== "undefined" && process.versions?.node) return _runtime = "module";
|
|
132
|
+
throw Error("Unknown runtime");
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Asynchronously loads a package by its name, supporting both ESM and CJS.
|
|
136
|
+
*
|
|
137
|
+
* @template T The type of module to be loaded.
|
|
138
|
+
* @param name The package name to load.
|
|
139
|
+
* @param defaultNames Preferred export keys used to resolve default values.
|
|
140
|
+
* @returns Loaded module or resolved default export.
|
|
141
|
+
*/
|
|
142
|
+
async function loadPackage(name, defaultNames = "default") {
|
|
143
|
+
const runtime = detectNodeRuntime();
|
|
144
|
+
let module;
|
|
145
|
+
if (runtime === "module") {
|
|
146
|
+
module = await import(name);
|
|
147
|
+
if (typeof defaultNames === "string") return defaultNames in module ? module[defaultNames] : module;
|
|
148
|
+
for (const key of defaultNames) if (key in module) return module[key];
|
|
149
|
+
return module;
|
|
150
|
+
}
|
|
151
|
+
if (runtime === "commonjs") {
|
|
152
|
+
module = globalThis.require(name);
|
|
153
|
+
if (typeof defaultNames === "string") return defaultNames in module ? module[defaultNames] : module;
|
|
154
|
+
for (const key of defaultNames) if (key in module) return module[key];
|
|
155
|
+
return module;
|
|
156
|
+
}
|
|
157
|
+
throw Error("Unknown runtime");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/load_func.ts
|
|
162
|
+
/**
|
|
163
|
+
* Load a FaasJS function and its configuration, returning the handler.
|
|
164
|
+
*
|
|
165
|
+
* @param root Project root directory used to resolve configuration.
|
|
166
|
+
* @param filename Path to the packaged FaasJS function file to load.
|
|
167
|
+
* @param staging Staging directory name (used when locating config).
|
|
168
|
+
* @returns A promise that resolves to the function handler.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* import { loadFunc } from '@faasjs/node-utils'
|
|
173
|
+
*
|
|
174
|
+
* const handler = await loadFunc(
|
|
175
|
+
* process.cwd(),
|
|
176
|
+
* __dirname + '/example.func.ts',
|
|
177
|
+
* 'development'
|
|
178
|
+
* )
|
|
179
|
+
*
|
|
180
|
+
* const result = await handler(event, context)
|
|
181
|
+
* console.log(result)
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
async function loadFunc(root, filename, staging) {
|
|
185
|
+
const func = await loadPackage(filename);
|
|
186
|
+
func.config = await loadConfig(root, filename, staging);
|
|
187
|
+
return func.export().handler;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region src/stream.ts
|
|
192
|
+
/**
|
|
193
|
+
* Convert ReadableStream to text.
|
|
194
|
+
*
|
|
195
|
+
* @throws {TypeError} If stream is not a ReadableStream instance.
|
|
196
|
+
*/
|
|
197
|
+
async function streamToText(stream) {
|
|
198
|
+
if (!(stream instanceof ReadableStream)) throw new TypeError("stream must be a ReadableStream instance");
|
|
199
|
+
return new Response(stream).text();
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Convert ReadableStream to object.
|
|
203
|
+
*
|
|
204
|
+
* @throws {TypeError} If stream is not a ReadableStream instance.
|
|
205
|
+
*/
|
|
206
|
+
async function streamToObject(stream) {
|
|
207
|
+
if (!(stream instanceof ReadableStream)) throw new TypeError("stream must be a ReadableStream instance");
|
|
208
|
+
return new Response(stream).json();
|
|
209
|
+
}
|
|
210
|
+
const streamToString = streamToText;
|
|
211
|
+
|
|
212
|
+
//#endregion
|
|
213
|
+
exports.deepMerge = deepMerge;
|
|
214
|
+
exports.detectNodeRuntime = detectNodeRuntime;
|
|
215
|
+
exports.loadConfig = loadConfig;
|
|
216
|
+
exports.loadFunc = loadFunc;
|
|
217
|
+
exports.loadPackage = loadPackage;
|
|
218
|
+
exports.resetRuntime = resetRuntime;
|
|
219
|
+
exports.streamToObject = streamToObject;
|
|
220
|
+
exports.streamToString = streamToString;
|
|
221
|
+
exports.streamToText = streamToText;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Config, ExportedHandler } from "@faasjs/func";
|
|
2
|
+
import { Logger } from "@faasjs/logger";
|
|
3
|
+
|
|
4
|
+
//#region src/deep_merge.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Deep merge two objects or arrays.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* * All objects will be cloned before merging.
|
|
10
|
+
* * Merging order is from right to left.
|
|
11
|
+
* * If an array include same objects, it will be unique to one.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* deepMerge({ a: 1 }, { a: 2 }) // { a: 2 }
|
|
15
|
+
* deepMerge([1, 2], [2, 3]) // [1, 2, 3]
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
declare function deepMerge(...sources: any[]): any;
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/load_config.d.ts
|
|
21
|
+
/**
|
|
22
|
+
* Load configuration from faas.yaml
|
|
23
|
+
*/
|
|
24
|
+
declare function loadConfig(root: string, filename: string, staging: string, logger?: Logger): Config;
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/load_func.d.ts
|
|
27
|
+
/**
|
|
28
|
+
* Load a FaasJS function and its configuration, returning the handler.
|
|
29
|
+
*
|
|
30
|
+
* @param root Project root directory used to resolve configuration.
|
|
31
|
+
* @param filename Path to the packaged FaasJS function file to load.
|
|
32
|
+
* @param staging Staging directory name (used when locating config).
|
|
33
|
+
* @returns A promise that resolves to the function handler.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { loadFunc } from '@faasjs/node-utils'
|
|
38
|
+
*
|
|
39
|
+
* const handler = await loadFunc(
|
|
40
|
+
* process.cwd(),
|
|
41
|
+
* __dirname + '/example.func.ts',
|
|
42
|
+
* 'development'
|
|
43
|
+
* )
|
|
44
|
+
*
|
|
45
|
+
* const result = await handler(event, context)
|
|
46
|
+
* console.log(result)
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
declare function loadFunc<TEvent = any, TContext = any, TResult = any>(root: string, filename: string, staging: string): Promise<ExportedHandler<TEvent, TContext, TResult>>;
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/load_package.d.ts
|
|
52
|
+
type NodeRuntime = 'commonjs' | 'module';
|
|
53
|
+
declare function resetRuntime(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Detect current JavaScript runtime environment.
|
|
56
|
+
*
|
|
57
|
+
* This function checks for presence of `require` first, then falls back to
|
|
58
|
+
* Node.js ESM detection via `process.versions.node`.
|
|
59
|
+
*
|
|
60
|
+
* @returns {NodeRuntime} Returns `module` for ESM and `commonjs` for CJS.
|
|
61
|
+
* @throws {Error} Throws an error if runtime cannot be determined.
|
|
62
|
+
*/
|
|
63
|
+
declare function detectNodeRuntime(): NodeRuntime;
|
|
64
|
+
/**
|
|
65
|
+
* Asynchronously loads a package by its name, supporting both ESM and CJS.
|
|
66
|
+
*
|
|
67
|
+
* @template T The type of module to be loaded.
|
|
68
|
+
* @param name The package name to load.
|
|
69
|
+
* @param defaultNames Preferred export keys used to resolve default values.
|
|
70
|
+
* @returns Loaded module or resolved default export.
|
|
71
|
+
*/
|
|
72
|
+
declare function loadPackage<T = unknown>(name: string, defaultNames?: string | string[]): Promise<T>;
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/stream.d.ts
|
|
75
|
+
/**
|
|
76
|
+
* Convert ReadableStream to text.
|
|
77
|
+
*
|
|
78
|
+
* @throws {TypeError} If stream is not a ReadableStream instance.
|
|
79
|
+
*/
|
|
80
|
+
declare function streamToText(stream: ReadableStream<Uint8Array>): Promise<string>;
|
|
81
|
+
/**
|
|
82
|
+
* Convert ReadableStream to object.
|
|
83
|
+
*
|
|
84
|
+
* @throws {TypeError} If stream is not a ReadableStream instance.
|
|
85
|
+
*/
|
|
86
|
+
declare function streamToObject<T = any>(stream: ReadableStream<Uint8Array>): Promise<T>;
|
|
87
|
+
declare const streamToString: typeof streamToText;
|
|
88
|
+
//#endregion
|
|
89
|
+
export { type NodeRuntime, deepMerge, detectNodeRuntime, loadConfig, loadFunc, loadPackage, resetRuntime, streamToObject, streamToString, streamToText };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, sep } from "node:path";
|
|
3
|
+
import { Logger } from "@faasjs/logger";
|
|
4
|
+
import { load } from "js-yaml";
|
|
5
|
+
|
|
6
|
+
//#region src/deep_merge.ts
|
|
7
|
+
const shouldMerge = (item) => {
|
|
8
|
+
const type = Object.prototype.toString.call(item);
|
|
9
|
+
return type === "[object Object]" || type === "[object Array]";
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Deep merge two objects or arrays.
|
|
13
|
+
*
|
|
14
|
+
* Features:
|
|
15
|
+
* * All objects will be cloned before merging.
|
|
16
|
+
* * Merging order is from right to left.
|
|
17
|
+
* * If an array include same objects, it will be unique to one.
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* deepMerge({ a: 1 }, { a: 2 }) // { a: 2 }
|
|
21
|
+
* deepMerge([1, 2], [2, 3]) // [1, 2, 3]
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
function deepMerge(...sources) {
|
|
25
|
+
let acc = Object.create(null);
|
|
26
|
+
for (const source of sources) if (Array.isArray(source)) {
|
|
27
|
+
if (!Array.isArray(acc)) acc = [];
|
|
28
|
+
acc = [...new Set(source.concat(...acc))];
|
|
29
|
+
} else if (shouldMerge(source)) for (const [key, value] of Object.entries(source)) {
|
|
30
|
+
let val;
|
|
31
|
+
if (shouldMerge(value)) val = deepMerge(acc[key], value);
|
|
32
|
+
else val = value;
|
|
33
|
+
acc = {
|
|
34
|
+
...acc,
|
|
35
|
+
[key]: val
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return acc;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/load_config.ts
|
|
43
|
+
function isObject(value) {
|
|
44
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
45
|
+
}
|
|
46
|
+
function createConfigError(filePath, keyPath, reason) {
|
|
47
|
+
return Error(`[loadConfig] Invalid faas.yaml ${filePath} at "${keyPath}": ${reason}`);
|
|
48
|
+
}
|
|
49
|
+
function validateServerConfig(filePath, staging, server) {
|
|
50
|
+
if (!isObject(server)) throw createConfigError(filePath, `${staging}.server`, "must be an object");
|
|
51
|
+
if (typeof server.root !== "undefined" && typeof server.root !== "string") throw createConfigError(filePath, `${staging}.server.root`, "must be a string");
|
|
52
|
+
if (typeof server.base !== "undefined" && typeof server.base !== "string") throw createConfigError(filePath, `${staging}.server.base`, "must be a string");
|
|
53
|
+
}
|
|
54
|
+
function validateFaasYaml(filePath, config) {
|
|
55
|
+
if (typeof config === "undefined" || config === null) return Object.create(null);
|
|
56
|
+
if (!isObject(config)) throw createConfigError(filePath, "<root>", "must be an object");
|
|
57
|
+
for (const staging in config) {
|
|
58
|
+
if (staging === "types") throw createConfigError(filePath, "types", "has been removed, move related settings out of faas.yaml");
|
|
59
|
+
const stageConfig = config[staging];
|
|
60
|
+
if (typeof stageConfig === "undefined" || stageConfig === null) continue;
|
|
61
|
+
if (!isObject(stageConfig)) throw createConfigError(filePath, staging, "must be an object");
|
|
62
|
+
if (Object.hasOwn(stageConfig, "types")) throw createConfigError(filePath, `${staging}.types`, "has been removed, move related settings out of faas.yaml");
|
|
63
|
+
if (Object.hasOwn(stageConfig, "server")) validateServerConfig(filePath, staging, stageConfig.server);
|
|
64
|
+
}
|
|
65
|
+
return config;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Load configuration from faas.yaml
|
|
69
|
+
*/
|
|
70
|
+
var Config = class {
|
|
71
|
+
root;
|
|
72
|
+
filename;
|
|
73
|
+
origin;
|
|
74
|
+
defaults;
|
|
75
|
+
logger;
|
|
76
|
+
constructor(root, filename, logger) {
|
|
77
|
+
this.logger = new Logger(logger?.label ? `${logger.label}] [config` : "config");
|
|
78
|
+
this.root = root;
|
|
79
|
+
if (!this.root.endsWith(sep)) this.root += sep;
|
|
80
|
+
this.filename = filename;
|
|
81
|
+
this.logger.debug("load %s in %s", filename, root);
|
|
82
|
+
const configs = [];
|
|
83
|
+
[this.root, "."].concat(dirname(filename.replace(root, "")).split(sep)).reduce((base, path) => {
|
|
84
|
+
const root = join(base, path);
|
|
85
|
+
if (root === base) return base;
|
|
86
|
+
const faas = join(root, "faas.yaml");
|
|
87
|
+
if (existsSync(faas)) configs.push(validateFaasYaml(faas, load(readFileSync(faas).toString())));
|
|
88
|
+
return root;
|
|
89
|
+
});
|
|
90
|
+
this.origin = deepMerge(...configs);
|
|
91
|
+
this.defaults = deepMerge(this.origin.defaults || {});
|
|
92
|
+
for (const key in this.origin) {
|
|
93
|
+
if (key !== "defaults") this[key] = deepMerge(this.defaults, this.origin[key]);
|
|
94
|
+
const data = this[key];
|
|
95
|
+
if (data.plugins) for (const pluginKey in data.plugins) {
|
|
96
|
+
const plugin = data.plugins[pluginKey];
|
|
97
|
+
plugin.name = pluginKey;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
get(key) {
|
|
102
|
+
return this[key] || this.defaults || Object.create(null);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Load configuration from faas.yaml
|
|
107
|
+
*/
|
|
108
|
+
function loadConfig(root, filename, staging, logger) {
|
|
109
|
+
return new Config(root, filename, logger).get(staging);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/load_package.ts
|
|
114
|
+
let _runtime = null;
|
|
115
|
+
function resetRuntime() {
|
|
116
|
+
_runtime = null;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Detect current JavaScript runtime environment.
|
|
120
|
+
*
|
|
121
|
+
* This function checks for presence of `require` first, then falls back to
|
|
122
|
+
* Node.js ESM detection via `process.versions.node`.
|
|
123
|
+
*
|
|
124
|
+
* @returns {NodeRuntime} Returns `module` for ESM and `commonjs` for CJS.
|
|
125
|
+
* @throws {Error} Throws an error if runtime cannot be determined.
|
|
126
|
+
*/
|
|
127
|
+
function detectNodeRuntime() {
|
|
128
|
+
if (_runtime) return _runtime;
|
|
129
|
+
if (typeof globalThis.require === "function" && typeof module !== "undefined") return _runtime = "commonjs";
|
|
130
|
+
if (typeof process !== "undefined" && process.versions?.node) return _runtime = "module";
|
|
131
|
+
throw Error("Unknown runtime");
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Asynchronously loads a package by its name, supporting both ESM and CJS.
|
|
135
|
+
*
|
|
136
|
+
* @template T The type of module to be loaded.
|
|
137
|
+
* @param name The package name to load.
|
|
138
|
+
* @param defaultNames Preferred export keys used to resolve default values.
|
|
139
|
+
* @returns Loaded module or resolved default export.
|
|
140
|
+
*/
|
|
141
|
+
async function loadPackage(name, defaultNames = "default") {
|
|
142
|
+
const runtime = detectNodeRuntime();
|
|
143
|
+
let module;
|
|
144
|
+
if (runtime === "module") {
|
|
145
|
+
module = await import(name);
|
|
146
|
+
if (typeof defaultNames === "string") return defaultNames in module ? module[defaultNames] : module;
|
|
147
|
+
for (const key of defaultNames) if (key in module) return module[key];
|
|
148
|
+
return module;
|
|
149
|
+
}
|
|
150
|
+
if (runtime === "commonjs") {
|
|
151
|
+
module = globalThis.require(name);
|
|
152
|
+
if (typeof defaultNames === "string") return defaultNames in module ? module[defaultNames] : module;
|
|
153
|
+
for (const key of defaultNames) if (key in module) return module[key];
|
|
154
|
+
return module;
|
|
155
|
+
}
|
|
156
|
+
throw Error("Unknown runtime");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/load_func.ts
|
|
161
|
+
/**
|
|
162
|
+
* Load a FaasJS function and its configuration, returning the handler.
|
|
163
|
+
*
|
|
164
|
+
* @param root Project root directory used to resolve configuration.
|
|
165
|
+
* @param filename Path to the packaged FaasJS function file to load.
|
|
166
|
+
* @param staging Staging directory name (used when locating config).
|
|
167
|
+
* @returns A promise that resolves to the function handler.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* import { loadFunc } from '@faasjs/node-utils'
|
|
172
|
+
*
|
|
173
|
+
* const handler = await loadFunc(
|
|
174
|
+
* process.cwd(),
|
|
175
|
+
* __dirname + '/example.func.ts',
|
|
176
|
+
* 'development'
|
|
177
|
+
* )
|
|
178
|
+
*
|
|
179
|
+
* const result = await handler(event, context)
|
|
180
|
+
* console.log(result)
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
async function loadFunc(root, filename, staging) {
|
|
184
|
+
const func = await loadPackage(filename);
|
|
185
|
+
func.config = await loadConfig(root, filename, staging);
|
|
186
|
+
return func.export().handler;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region src/stream.ts
|
|
191
|
+
/**
|
|
192
|
+
* Convert ReadableStream to text.
|
|
193
|
+
*
|
|
194
|
+
* @throws {TypeError} If stream is not a ReadableStream instance.
|
|
195
|
+
*/
|
|
196
|
+
async function streamToText(stream) {
|
|
197
|
+
if (!(stream instanceof ReadableStream)) throw new TypeError("stream must be a ReadableStream instance");
|
|
198
|
+
return new Response(stream).text();
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Convert ReadableStream to object.
|
|
202
|
+
*
|
|
203
|
+
* @throws {TypeError} If stream is not a ReadableStream instance.
|
|
204
|
+
*/
|
|
205
|
+
async function streamToObject(stream) {
|
|
206
|
+
if (!(stream instanceof ReadableStream)) throw new TypeError("stream must be a ReadableStream instance");
|
|
207
|
+
return new Response(stream).json();
|
|
208
|
+
}
|
|
209
|
+
const streamToString = streamToText;
|
|
210
|
+
|
|
211
|
+
//#endregion
|
|
212
|
+
export { deepMerge, detectNodeRuntime, loadConfig, loadFunc, loadPackage, resetRuntime, streamToObject, streamToString, streamToText };
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@faasjs/node-utils",
|
|
3
|
+
"version": "8.0.0-beta.7",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.mjs",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://faasjs.com/doc/node-utils",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/faasjs/faasjs.git",
|
|
20
|
+
"directory": "packages/node-utils"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/faasjs/faasjs/issues"
|
|
24
|
+
},
|
|
25
|
+
"funding": "https://github.com/sponsors/faasjs",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsdown src/index.ts --config ../../tsdown.config.ts"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@faasjs/func": ">=8.0.0-beta.7",
|
|
34
|
+
"@faasjs/logger": ">=8.0.0-beta.7",
|
|
35
|
+
"js-yaml": "*"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@faasjs/func": ">=8.0.0-beta.7",
|
|
39
|
+
"@faasjs/logger": ">=8.0.0-beta.7",
|
|
40
|
+
"@types/js-yaml": "*",
|
|
41
|
+
"js-yaml": "*"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=24.0.0",
|
|
45
|
+
"npm": ">=11.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|