@faasjs/func 8.0.0-beta.5 → 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/dist/index.mjs CHANGED
@@ -1,321 +1,266 @@
1
- import { randomBytes } from 'crypto';
2
- import { fileURLToPath } from 'url';
3
- import { Logger } from '@faasjs/logger';
1
+ import { randomBytes } from "node:crypto";
2
+ import { fileURLToPath } from "node:url";
3
+ import { Logger } from "@faasjs/logger";
4
4
 
5
- // src/index.ts
6
-
7
- // src/plugins/run_handler/index.ts
8
- var Name = "handler";
5
+ //#region src/plugins/run_handler/index.ts
6
+ const Name = "handler";
9
7
  var RunHandler = class {
10
- type = Name;
11
- name = Name;
12
- async onInvoke(data, next) {
13
- if (data.handler)
14
- if (!data.runHandler) {
15
- try {
16
- data.response = await new Promise((resolve, reject) => {
17
- data.callback = (error, result) => {
18
- if (error) reject(error);
19
- else resolve(result);
20
- };
21
- Promise.resolve(data.handler?.(data)).then(resolve).catch(reject);
22
- });
23
- } catch (error) {
24
- data.logger.error(error);
25
- data.response = error;
26
- }
27
- data.runHandler = true;
28
- } else data.logger.warn("handler has been run");
29
- await next();
30
- }
8
+ type = Name;
9
+ name = Name;
10
+ async onInvoke(data, next) {
11
+ if (data.handler) if (!data.runHandler) {
12
+ try {
13
+ data.response = await new Promise((resolve, reject) => {
14
+ data.callback = (error, result) => {
15
+ if (error) reject(error);
16
+ else resolve(result);
17
+ };
18
+ Promise.resolve(data.handler?.(data)).then(resolve).catch(reject);
19
+ });
20
+ } catch (error) {
21
+ data.logger.error(error);
22
+ data.response = error;
23
+ }
24
+ data.runHandler = true;
25
+ } else data.logger.warn("handler has been run");
26
+ await next();
27
+ }
31
28
  };
32
29
 
33
- // src/utils.ts
30
+ //#endregion
31
+ //#region src/utils.ts
32
+ /**
33
+ * Assigns a name to a given function handler, which will be displayed in logs and error messages.
34
+ *
35
+ * @template T - The type of the function handler.
36
+ * @param {string} name - The name to assign to the function handler.
37
+ * @param {T} handler - The function handler to which the name will be assigned.
38
+ * @returns {T} - The original function handler with the assigned name.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * import { nameFunc } from '@faasjs/func'
43
+ *
44
+ * const handler = nameFunc('myHandler', () => {
45
+ * return 'Hello World'
46
+ * })
47
+ *
48
+ * console.log(handler.name) // => 'myHandler'
49
+ * ```
50
+ */
34
51
  function nameFunc(name, handler) {
35
- Object.defineProperty(handler, "name", { value: name });
36
- return handler;
52
+ Object.defineProperty(handler, "name", { value: name });
53
+ return handler;
37
54
  }
38
55
 
39
- // src/index.ts
56
+ //#endregion
57
+ //#region src/index.ts
58
+ /**
59
+ * FaasJS's function module.
60
+ *
61
+ * [![License: MIT](https://img.shields.io/npm/l/@faasjs/func.svg)](https://github.com/faasjs/faasjs/blob/main/packages/func/LICENSE)
62
+ * [![NPM Version](https://img.shields.io/npm/v/@faasjs/func.svg)](https://www.npmjs.com/package/@faasjs/func)
63
+ *
64
+ * ## Install
65
+ *
66
+ * ```sh
67
+ * npm install @faasjs/func
68
+ * ```
69
+ *
70
+ * ## Usage
71
+ *
72
+ * @see {@link useFunc}
73
+ *
74
+ * @packageDocumentation
75
+ */
40
76
  function parseFuncFilenameFromStack(stack) {
41
- if (!stack) return;
42
- const frame = stack.split("\n").map((line) => line.trim()).find((line) => line.includes(".func.ts"));
43
- if (!frame) return;
44
- const content = frame.replace(/^at\s+/, "");
45
- const location = content.endsWith(")") && content.includes("(") ? content.slice(content.lastIndexOf("(") + 1, -1) : content;
46
- const match = location.match(/^(.+\.func\.ts):\d+:\d+$/);
47
- if (!match) return;
48
- const filename = match[1];
49
- if (filename.startsWith("file://")) {
50
- try {
51
- return fileURLToPath(filename);
52
- } catch (_) {
53
- return filename;
54
- }
55
- }
56
- return filename;
57
- }
58
- function formatPluginModuleName(type) {
59
- if (type.startsWith("npm:")) return type.slice(4);
60
- if (type.startsWith("@") || type.startsWith(".") || type.startsWith("/") || type.includes(":"))
61
- return type;
62
- return `@faasjs/${type}`;
63
- }
64
- function formatPluginClassName(type) {
65
- return type.replace(/^@[^/]+\//, "").split(/[^A-Za-z0-9]+/).filter(Boolean).map((item) => item.slice(0, 1).toUpperCase() + item.slice(1)).join("");
77
+ if (!stack) return;
78
+ const frame = stack.split("\n").map((line) => line.trim()).find((line) => line.includes(".func.ts"));
79
+ if (!frame) return;
80
+ const content = frame.replace(/^at\s+/, "");
81
+ const match = (content.endsWith(")") && content.includes("(") ? content.slice(content.lastIndexOf("(") + 1, -1) : content).match(/^(.+\.func\.ts):\d+:\d+$/);
82
+ if (!match) return;
83
+ const filename = match[1];
84
+ if (filename.startsWith("file://")) try {
85
+ return fileURLToPath(filename);
86
+ } catch (_) {
87
+ return filename;
88
+ }
89
+ return filename;
66
90
  }
67
91
  var Func = class {
68
- plugins;
69
- handler;
70
- config;
71
- mounted = false;
72
- filename;
73
- autoLoadPluginsFromConfig;
74
- loadedConfigPlugins = false;
75
- cachedFunctions = /* @__PURE__ */ Object.create(null);
76
- /**
77
- * Create a cloud function
78
- * @param config {object} config
79
- * @param config.plugins {Plugin[]} plugins list
80
- * @param config.handler {Handler} business logic
81
- */
82
- constructor(config) {
83
- this.handler = config.handler;
84
- this.plugins = config.plugins || [];
85
- this.plugins.push(new RunHandler());
86
- this.autoLoadPluginsFromConfig = !!config.autoLoadPluginsFromConfig;
87
- this.config = {
88
- plugins: /* @__PURE__ */ Object.create(null)
89
- };
90
- try {
91
- this.filename = parseFuncFilenameFromStack(new Error().stack);
92
- } catch (_) {
93
- }
94
- }
95
- insertPluginBeforeRunHandler(plugin) {
96
- const index = this.plugins.findIndex(
97
- (p) => p.type === "handler" && p.name === "handler"
98
- );
99
- if (index === -1) this.plugins.push(plugin);
100
- else this.plugins.splice(index, 0, plugin);
101
- this.cachedFunctions = /* @__PURE__ */ Object.create(null);
102
- }
103
- async resolvePluginConstructor(moduleName, className, pluginName) {
104
- let mod;
105
- try {
106
- mod = await import(moduleName);
107
- } catch (error) {
108
- throw Error(
109
- `[defineFunc] Failed to load plugin "${pluginName}" from "${moduleName}": ${error.message}`
110
- );
111
- }
112
- const constructors = [];
113
- if (className && mod[className]) constructors.push(mod[className]);
114
- if (typeof mod.default === "function") constructors.push(mod.default);
115
- if (mod.default && typeof mod.default === "object" && className && mod.default[className])
116
- constructors.push(mod.default[className]);
117
- for (const key in mod) {
118
- if (key === className || key === "default") continue;
119
- constructors.push(mod[key]);
120
- }
121
- if (mod.default && typeof mod.default === "object")
122
- for (const key in mod.default) {
123
- if (key === className) continue;
124
- constructors.push(mod.default[key]);
125
- }
126
- const PluginConstructor = constructors.find(
127
- (pluginConstructor) => typeof pluginConstructor === "function" && pluginConstructor.prototype && (typeof pluginConstructor.prototype.onMount === "function" || typeof pluginConstructor.prototype.onInvoke === "function")
128
- );
129
- if (!PluginConstructor)
130
- throw Error(
131
- `[defineFunc] Failed to resolve plugin class "${className}" from "${moduleName}" for plugin "${pluginName}".`
132
- );
133
- return PluginConstructor;
134
- }
135
- async loadPluginsFromConfig(config) {
136
- const pluginConfigs = config.plugins || /* @__PURE__ */ Object.create(null);
137
- for (const key in pluginConfigs) {
138
- const rawConfig = pluginConfigs[key];
139
- const configValue = rawConfig && typeof rawConfig === "object" ? Object.assign(/* @__PURE__ */ Object.create(null), rawConfig) : /* @__PURE__ */ Object.create(null);
140
- const pluginName = typeof configValue.name === "string" && configValue.name.length ? configValue.name : key;
141
- if (this.plugins.find((plugin2) => plugin2.name === pluginName)) continue;
142
- const pluginType = typeof configValue.type === "string" && configValue.type || typeof rawConfig === "string" && rawConfig || key;
143
- const moduleName = formatPluginModuleName(pluginType);
144
- const className = formatPluginClassName(pluginType);
145
- const PluginConstructor = await this.resolvePluginConstructor(
146
- moduleName,
147
- className,
148
- pluginName
149
- );
150
- let plugin;
151
- try {
152
- plugin = new PluginConstructor({
153
- ...configValue,
154
- name: pluginName,
155
- type: pluginType
156
- });
157
- } catch (error) {
158
- throw Error(
159
- `[defineFunc] Failed to initialize plugin "${pluginName}" from "${moduleName}": ${error.message}`
160
- );
161
- }
162
- if (!plugin || typeof plugin !== "object")
163
- throw Error(
164
- `[defineFunc] Invalid plugin instance for "${pluginName}" from "${moduleName}".`
165
- );
166
- this.insertPluginBeforeRunHandler(plugin);
167
- }
168
- this.loadedConfigPlugins = true;
169
- }
170
- compose(key) {
171
- let list = [];
172
- if (this.cachedFunctions[key]) list = this.cachedFunctions[key];
173
- else {
174
- for (const plugin of this.plugins) {
175
- const handler = plugin[key];
176
- if (typeof handler === "function")
177
- list.push({
178
- key: plugin.name,
179
- handler: handler.bind(plugin)
180
- });
181
- }
182
- this.cachedFunctions[key] = list;
183
- }
184
- return async (data, next) => {
185
- let index = -1;
186
- if (!data.logger) data.logger = new Logger();
187
- const dispatch = async (i) => {
188
- if (i <= index)
189
- return Promise.reject(Error("next() called multiple times"));
190
- index = i;
191
- let fn = list[i];
192
- if (i === list.length) fn = next;
193
- if (!fn) return Promise.resolve();
194
- if (typeof fn.key === "undefined") fn.key = `uname#${i}`;
195
- if (!data.context) data.context = /* @__PURE__ */ Object.create(null);
196
- if (!data.context.request_at)
197
- data.context.request_at = randomBytes(16).toString("hex");
198
- const label = `${data.context.request_id}] [${fn.key}] [${key}`;
199
- data.logger.label = label;
200
- data.logger.debug("begin");
201
- data.logger.time(label);
202
- try {
203
- const res = await Promise.resolve(
204
- fn.handler(data, dispatch.bind(null, i + 1))
205
- );
206
- data.logger.label = label;
207
- data.logger.timeEnd(label, "end");
208
- return res;
209
- } catch (err) {
210
- data.logger.label = label;
211
- data.logger.timeEnd(label, "failed");
212
- data.logger.error(err);
213
- return Promise.reject(err);
214
- }
215
- };
216
- return await dispatch(0);
217
- };
218
- }
219
- /**
220
- * First time mount the function
221
- */
222
- async mount(data = {
223
- event: /* @__PURE__ */ Object.create(null),
224
- context: /* @__PURE__ */ Object.create(null)
225
- }) {
226
- if (!data.logger) data.logger = new Logger("Func");
227
- if (this.mounted) {
228
- data.logger.warn("mount() has been called, skipped.");
229
- return;
230
- }
231
- if (!data.config) data.config = this.config;
232
- if (this.autoLoadPluginsFromConfig && !this.loadedConfigPlugins)
233
- await this.loadPluginsFromConfig(data.config);
234
- data.logger.debug(
235
- `plugins: ${this.plugins.map((p) => `${p.type}#${p.name}`).join(",")}`
236
- );
237
- await this.compose("onMount")(data);
238
- this.mounted = true;
239
- }
240
- /**
241
- * Invoke the function
242
- * @param data {object} data
243
- */
244
- async invoke(data) {
245
- if (!this.mounted) await this.mount(data);
246
- try {
247
- await this.compose("onInvoke")(data);
248
- } catch (error) {
249
- data.logger.error(error);
250
- data.response = error;
251
- }
252
- }
253
- /**
254
- * Export the function
255
- */
256
- export() {
257
- const handler = async (event, context, callback) => {
258
- if (typeof context === "undefined") context = {};
259
- if (!context.request_id)
260
- context.request_id = event?.headers?.["x-faasjs-request-id"] || randomBytes(16).toString("hex");
261
- if (!context.request_at)
262
- context.request_at = randomBytes(16).toString("hex");
263
- context.callbackWaitsForEmptyEventLoop = false;
264
- const logger = new Logger(context.request_id);
265
- const data = {
266
- event: event ?? /* @__PURE__ */ Object.create(null),
267
- context,
268
- callback,
269
- response: void 0,
270
- handler: this.handler,
271
- logger,
272
- config: this.config
273
- };
274
- await this.invoke(data);
275
- if (Object.prototype.toString.call(data.response) === "[object Error]")
276
- throw data.response;
277
- return data.response;
278
- };
279
- return {
280
- handler: handler.bind(this)
281
- };
282
- }
92
+ plugins;
93
+ handler;
94
+ config;
95
+ mounted = false;
96
+ filename;
97
+ cachedFunctions = Object.create(null);
98
+ /**
99
+ * Create a cloud function
100
+ * @param config {object} config
101
+ * @param config.plugins {Plugin[]} plugins list
102
+ * @param config.handler {Handler} business logic
103
+ */
104
+ constructor(config) {
105
+ if (config.handler) this.handler = config.handler;
106
+ this.plugins = config.plugins || [];
107
+ this.plugins.push(new RunHandler());
108
+ this.config = { plugins: Object.create(null) };
109
+ try {
110
+ const filename = parseFuncFilenameFromStack((/* @__PURE__ */ new Error()).stack);
111
+ if (filename) this.filename = filename;
112
+ } catch (_) {}
113
+ }
114
+ compose(key) {
115
+ let list = [];
116
+ if (this.cachedFunctions[key]) list = this.cachedFunctions[key];
117
+ else {
118
+ for (const plugin of this.plugins) {
119
+ const handler = plugin[key];
120
+ if (typeof handler === "function") list.push({
121
+ key: plugin.name,
122
+ handler: handler.bind(plugin)
123
+ });
124
+ }
125
+ this.cachedFunctions[key] = list;
126
+ }
127
+ return async (data, next) => {
128
+ let index = -1;
129
+ if (!data.logger) data.logger = new Logger();
130
+ const dispatch = async (i) => {
131
+ if (i <= index) return Promise.reject(Error("next() called multiple times"));
132
+ index = i;
133
+ let fn = list[i];
134
+ if (i === list.length) fn = next;
135
+ if (!fn) return Promise.resolve();
136
+ if (typeof fn.key === "undefined") fn.key = `uname#${i}`;
137
+ if (!data.context) data.context = Object.create(null);
138
+ if (!data.context.request_at) data.context.request_at = randomBytes(16).toString("hex");
139
+ const label = `${data.context.request_id}] [${fn.key}] [${key}`;
140
+ data.logger.label = label;
141
+ data.logger.debug("begin");
142
+ data.logger.time(label);
143
+ try {
144
+ const res = await Promise.resolve(fn.handler(data, dispatch.bind(null, i + 1)));
145
+ data.logger.label = label;
146
+ data.logger.timeEnd(label, "end");
147
+ return res;
148
+ } catch (err) {
149
+ data.logger.label = label;
150
+ data.logger.timeEnd(label, "failed");
151
+ data.logger.error(err);
152
+ return Promise.reject(err);
153
+ }
154
+ };
155
+ return await dispatch(0);
156
+ };
157
+ }
158
+ /**
159
+ * First time mount the function
160
+ */
161
+ async mount(data = {
162
+ event: Object.create(null),
163
+ context: Object.create(null)
164
+ }) {
165
+ if (!data.logger) data.logger = new Logger("Func");
166
+ if (this.mounted) {
167
+ data.logger.warn("mount() has been called, skipped.");
168
+ return;
169
+ }
170
+ if (!data.config) data.config = this.config;
171
+ data.logger.debug(`plugins: ${this.plugins.map((p) => `${p.type}#${p.name}`).join(",")}`);
172
+ await this.compose("onMount")(data);
173
+ this.mounted = true;
174
+ }
175
+ /**
176
+ * Invoke the function
177
+ * @param data {object} data
178
+ */
179
+ async invoke(data) {
180
+ if (!this.mounted) await this.mount(data);
181
+ try {
182
+ await this.compose("onInvoke")(data);
183
+ } catch (error) {
184
+ data.logger.error(error);
185
+ data.response = error;
186
+ }
187
+ }
188
+ /**
189
+ * Export the function
190
+ */
191
+ export() {
192
+ const handler = async (event, context, callback) => {
193
+ if (typeof context === "undefined") context = {};
194
+ if (!context.request_id) context.request_id = event?.headers?.["x-faasjs-request-id"] || randomBytes(16).toString("hex");
195
+ if (!context.request_at) context.request_at = randomBytes(16).toString("hex");
196
+ context.callbackWaitsForEmptyEventLoop = false;
197
+ const logger = new Logger(context.request_id);
198
+ const data = {
199
+ event: event ?? Object.create(null),
200
+ context,
201
+ callback,
202
+ response: void 0,
203
+ logger,
204
+ config: this.config,
205
+ ...this.handler ? { handler: this.handler } : {}
206
+ };
207
+ await this.invoke(data);
208
+ if (Object.prototype.toString.call(data.response) === "[object Error]") throw data.response;
209
+ return data.response;
210
+ };
211
+ return { handler: handler.bind(this) };
212
+ }
283
213
  };
284
- var plugins = [];
214
+ let plugins = [];
285
215
  function usePlugin(plugin) {
286
- if (!plugins.find((p) => p.name === plugin.name)) plugins.push(plugin);
287
- if (!plugin.mount)
288
- plugin.mount = async (data) => {
289
- const parsedData = data || /* @__PURE__ */ Object.create(null);
290
- if (!parsedData.config) parsedData.config = /* @__PURE__ */ Object.create(null);
291
- if (!parsedData.context) parsedData.context = /* @__PURE__ */ Object.create(null);
292
- if (!parsedData.event) parsedData.event = /* @__PURE__ */ Object.create(null);
293
- if (!parsedData.logger) parsedData.logger = new Logger(plugin.name);
294
- if (plugin.onMount)
295
- await plugin.onMount(parsedData, async () => Promise.resolve());
296
- return plugin;
297
- };
298
- return plugin;
216
+ if (!plugins.find((p) => p.name === plugin.name)) plugins.push(plugin);
217
+ if (!plugin.mount) plugin.mount = async (data) => {
218
+ const parsedData = data || Object.create(null);
219
+ if (!parsedData.config) parsedData.config = Object.create(null);
220
+ if (!parsedData.context) parsedData.context = Object.create(null);
221
+ if (!parsedData.event) parsedData.event = Object.create(null);
222
+ if (!parsedData.logger) parsedData.logger = new Logger(plugin.name);
223
+ if (plugin.onMount) await plugin.onMount(parsedData, async () => Promise.resolve());
224
+ return plugin;
225
+ };
226
+ return plugin;
299
227
  }
228
+ /**
229
+ * Create a cloud function.
230
+ *
231
+ * @example
232
+ * ```ts
233
+ * // pure function
234
+ * export const func = useFunc(() => {
235
+ * return () => {
236
+ * return 'Hello World'
237
+ * }
238
+ * })
239
+ *
240
+ * // with http
241
+ * import { useHttp } from '@faasjs/http'
242
+ *
243
+ * export const func = useFunc<{
244
+ * params: { name: string }
245
+ * }>(() => {
246
+ * useHttp()
247
+ *
248
+ * return ({ event }) => {
249
+ * return `Hello ${event.params.name}`
250
+ * }
251
+ * })
252
+ * ```
253
+ */
300
254
  function useFunc(handler) {
301
- plugins = [];
302
- const invokeHandler = handler();
303
- const func = new Func({
304
- plugins,
305
- handler: invokeHandler
306
- });
307
- plugins = [];
308
- return func;
309
- }
310
- function defineFunc(handler) {
311
- plugins = [];
312
- const func = new Func({
313
- plugins,
314
- handler,
315
- autoLoadPluginsFromConfig: true
316
- });
317
- plugins = [];
318
- return func;
255
+ plugins = [];
256
+ const invokeHandler = handler();
257
+ const func = new Func({
258
+ plugins,
259
+ handler: invokeHandler
260
+ });
261
+ plugins = [];
262
+ return func;
319
263
  }
320
264
 
321
- export { Func, defineFunc, nameFunc, parseFuncFilenameFromStack, useFunc, usePlugin };
265
+ //#endregion
266
+ export { Func, nameFunc, parseFuncFilenameFromStack, useFunc, usePlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faasjs/func",
3
- "version": "v8.0.0-beta.5",
3
+ "version": "8.0.0-beta.7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -24,18 +24,16 @@
24
24
  },
25
25
  "funding": "https://github.com/sponsors/faasjs",
26
26
  "scripts": {
27
- "build": "tsup-node src/index.ts --config ../../tsup.config.ts"
27
+ "build": "tsdown src/index.ts --config ../../tsdown.config.ts"
28
28
  },
29
29
  "files": [
30
30
  "dist"
31
31
  ],
32
32
  "peerDependencies": {
33
- "@faasjs/deep_merge": ">=v8.0.0-beta.5",
34
- "@faasjs/logger": ">=v8.0.0-beta.5"
33
+ "@faasjs/logger": ">=8.0.0-beta.7"
35
34
  },
36
35
  "devDependencies": {
37
- "@faasjs/deep_merge": ">=v8.0.0-beta.5",
38
- "@faasjs/logger": ">=v8.0.0-beta.5"
36
+ "@faasjs/logger": ">=8.0.0-beta.7"
39
37
  },
40
38
  "engines": {
41
39
  "node": ">=24.0.0",