@faasjs/func 8.0.0-beta.3 → 8.0.0-beta.5

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/README.md CHANGED
@@ -19,7 +19,9 @@ npm install @faasjs/func
19
19
 
20
20
  ## Functions
21
21
 
22
+ - [defineFunc](functions/defineFunc.md)
22
23
  - [nameFunc](functions/nameFunc.md)
24
+ - [parseFuncFilenameFromStack](functions/parseFuncFilenameFromStack.md)
23
25
  - [useFunc](functions/useFunc.md)
24
26
  - [usePlugin](functions/usePlugin.md)
25
27
 
package/dist/index.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var crypto = require('crypto');
4
+ var url = require('url');
4
5
  var logger = require('@faasjs/logger');
5
6
 
6
7
  // src/index.ts
@@ -38,12 +39,41 @@ function nameFunc(name, handler) {
38
39
  }
39
40
 
40
41
  // src/index.ts
42
+ function parseFuncFilenameFromStack(stack) {
43
+ if (!stack) return;
44
+ const frame = stack.split("\n").map((line) => line.trim()).find((line) => line.includes(".func.ts"));
45
+ if (!frame) return;
46
+ const content = frame.replace(/^at\s+/, "");
47
+ const location = content.endsWith(")") && content.includes("(") ? content.slice(content.lastIndexOf("(") + 1, -1) : content;
48
+ const match = location.match(/^(.+\.func\.ts):\d+:\d+$/);
49
+ if (!match) return;
50
+ const filename = match[1];
51
+ if (filename.startsWith("file://")) {
52
+ try {
53
+ return url.fileURLToPath(filename);
54
+ } catch (_) {
55
+ return filename;
56
+ }
57
+ }
58
+ return filename;
59
+ }
60
+ function formatPluginModuleName(type) {
61
+ if (type.startsWith("npm:")) return type.slice(4);
62
+ if (type.startsWith("@") || type.startsWith(".") || type.startsWith("/") || type.includes(":"))
63
+ return type;
64
+ return `@faasjs/${type}`;
65
+ }
66
+ function formatPluginClassName(type) {
67
+ return type.replace(/^@[^/]+\//, "").split(/[^A-Za-z0-9]+/).filter(Boolean).map((item) => item.slice(0, 1).toUpperCase() + item.slice(1)).join("");
68
+ }
41
69
  var Func = class {
42
70
  plugins;
43
71
  handler;
44
72
  config;
45
73
  mounted = false;
46
74
  filename;
75
+ autoLoadPluginsFromConfig;
76
+ loadedConfigPlugins = false;
47
77
  cachedFunctions = /* @__PURE__ */ Object.create(null);
48
78
  /**
49
79
  * Create a cloud function
@@ -55,20 +85,90 @@ var Func = class {
55
85
  this.handler = config.handler;
56
86
  this.plugins = config.plugins || [];
57
87
  this.plugins.push(new RunHandler());
88
+ this.autoLoadPluginsFromConfig = !!config.autoLoadPluginsFromConfig;
58
89
  this.config = {
59
90
  plugins: /* @__PURE__ */ Object.create(null)
60
91
  };
61
92
  try {
62
- const stack = new Error().stack;
63
- if (stack) {
64
- const match = stack.split("\n").find((s) => /[^/]\.func\.ts/.test(s))?.match(/\((.*\.func\.ts).*\)/);
65
- if (match) {
66
- this.filename = match[1];
67
- }
68
- }
93
+ this.filename = parseFuncFilenameFromStack(new Error().stack);
69
94
  } catch (_) {
70
95
  }
71
96
  }
97
+ insertPluginBeforeRunHandler(plugin) {
98
+ const index = this.plugins.findIndex(
99
+ (p) => p.type === "handler" && p.name === "handler"
100
+ );
101
+ if (index === -1) this.plugins.push(plugin);
102
+ else this.plugins.splice(index, 0, plugin);
103
+ this.cachedFunctions = /* @__PURE__ */ Object.create(null);
104
+ }
105
+ async resolvePluginConstructor(moduleName, className, pluginName) {
106
+ let mod;
107
+ try {
108
+ mod = await import(moduleName);
109
+ } catch (error) {
110
+ throw Error(
111
+ `[defineFunc] Failed to load plugin "${pluginName}" from "${moduleName}": ${error.message}`
112
+ );
113
+ }
114
+ const constructors = [];
115
+ if (className && mod[className]) constructors.push(mod[className]);
116
+ if (typeof mod.default === "function") constructors.push(mod.default);
117
+ if (mod.default && typeof mod.default === "object" && className && mod.default[className])
118
+ constructors.push(mod.default[className]);
119
+ for (const key in mod) {
120
+ if (key === className || key === "default") continue;
121
+ constructors.push(mod[key]);
122
+ }
123
+ if (mod.default && typeof mod.default === "object")
124
+ for (const key in mod.default) {
125
+ if (key === className) continue;
126
+ constructors.push(mod.default[key]);
127
+ }
128
+ const PluginConstructor = constructors.find(
129
+ (pluginConstructor) => typeof pluginConstructor === "function" && pluginConstructor.prototype && (typeof pluginConstructor.prototype.onMount === "function" || typeof pluginConstructor.prototype.onInvoke === "function")
130
+ );
131
+ if (!PluginConstructor)
132
+ throw Error(
133
+ `[defineFunc] Failed to resolve plugin class "${className}" from "${moduleName}" for plugin "${pluginName}".`
134
+ );
135
+ return PluginConstructor;
136
+ }
137
+ async loadPluginsFromConfig(config) {
138
+ const pluginConfigs = config.plugins || /* @__PURE__ */ Object.create(null);
139
+ for (const key in pluginConfigs) {
140
+ const rawConfig = pluginConfigs[key];
141
+ const configValue = rawConfig && typeof rawConfig === "object" ? Object.assign(/* @__PURE__ */ Object.create(null), rawConfig) : /* @__PURE__ */ Object.create(null);
142
+ const pluginName = typeof configValue.name === "string" && configValue.name.length ? configValue.name : key;
143
+ if (this.plugins.find((plugin2) => plugin2.name === pluginName)) continue;
144
+ const pluginType = typeof configValue.type === "string" && configValue.type || typeof rawConfig === "string" && rawConfig || key;
145
+ const moduleName = formatPluginModuleName(pluginType);
146
+ const className = formatPluginClassName(pluginType);
147
+ const PluginConstructor = await this.resolvePluginConstructor(
148
+ moduleName,
149
+ className,
150
+ pluginName
151
+ );
152
+ let plugin;
153
+ try {
154
+ plugin = new PluginConstructor({
155
+ ...configValue,
156
+ name: pluginName,
157
+ type: pluginType
158
+ });
159
+ } catch (error) {
160
+ throw Error(
161
+ `[defineFunc] Failed to initialize plugin "${pluginName}" from "${moduleName}": ${error.message}`
162
+ );
163
+ }
164
+ if (!plugin || typeof plugin !== "object")
165
+ throw Error(
166
+ `[defineFunc] Invalid plugin instance for "${pluginName}" from "${moduleName}".`
167
+ );
168
+ this.insertPluginBeforeRunHandler(plugin);
169
+ }
170
+ this.loadedConfigPlugins = true;
171
+ }
72
172
  compose(key) {
73
173
  let list = [];
74
174
  if (this.cachedFunctions[key]) list = this.cachedFunctions[key];
@@ -131,6 +231,8 @@ var Func = class {
131
231
  return;
132
232
  }
133
233
  if (!data.config) data.config = this.config;
234
+ if (this.autoLoadPluginsFromConfig && !this.loadedConfigPlugins)
235
+ await this.loadPluginsFromConfig(data.config);
134
236
  data.logger.debug(
135
237
  `plugins: ${this.plugins.map((p) => `${p.type}#${p.name}`).join(",")}`
136
238
  );
@@ -207,8 +309,20 @@ function useFunc(handler) {
207
309
  plugins = [];
208
310
  return func;
209
311
  }
312
+ function defineFunc(handler) {
313
+ plugins = [];
314
+ const func = new Func({
315
+ plugins,
316
+ handler,
317
+ autoLoadPluginsFromConfig: true
318
+ });
319
+ plugins = [];
320
+ return func;
321
+ }
210
322
 
211
323
  exports.Func = Func;
324
+ exports.defineFunc = defineFunc;
212
325
  exports.nameFunc = nameFunc;
326
+ exports.parseFuncFilenameFromStack = parseFuncFilenameFromStack;
213
327
  exports.useFunc = useFunc;
214
328
  exports.usePlugin = usePlugin;
package/dist/index.d.ts CHANGED
@@ -55,7 +55,7 @@ type Config = {
55
55
  plugins?: {
56
56
  [key: string]: {
57
57
  [key: string]: any;
58
- type: string;
58
+ type?: string;
59
59
  config?: {
60
60
  [key: string]: any;
61
61
  };
@@ -82,6 +82,7 @@ type LifeCycleKey = 'onMount' | 'onInvoke';
82
82
  type FuncConfig<TEvent = any, TContext = any, TResult = any> = {
83
83
  plugins?: Plugin[];
84
84
  handler?: Handler<TEvent, TContext, TResult>;
85
+ autoLoadPluginsFromConfig?: boolean;
85
86
  };
86
87
  /**
87
88
  * Get the event type of a func
@@ -109,6 +110,7 @@ type FuncEventType<T extends Func<any, any, any>> = T extends Func<infer P, any,
109
110
  * ```
110
111
  */
111
112
  type FuncReturnType<T extends Func<any, any, any>> = T extends Func<any, any, infer R> ? R : any;
113
+ declare function parseFuncFilenameFromStack(stack?: string): string | undefined;
112
114
  declare class Func<TEvent = any, TContext = any, TResult = any> {
113
115
  [key: string]: any;
114
116
  plugins: Plugin[];
@@ -116,6 +118,8 @@ declare class Func<TEvent = any, TContext = any, TResult = any> {
116
118
  config: Config;
117
119
  mounted: boolean;
118
120
  filename?: string;
121
+ private readonly autoLoadPluginsFromConfig;
122
+ private loadedConfigPlugins;
119
123
  private cachedFunctions;
120
124
  /**
121
125
  * Create a cloud function
@@ -124,6 +128,9 @@ declare class Func<TEvent = any, TContext = any, TResult = any> {
124
128
  * @param config.handler {Handler} business logic
125
129
  */
126
130
  constructor(config: FuncConfig<TEvent, TContext>);
131
+ private insertPluginBeforeRunHandler;
132
+ private resolvePluginConstructor;
133
+ private loadPluginsFromConfig;
127
134
  private compose;
128
135
  /**
129
136
  * First time mount the function
@@ -179,5 +186,15 @@ declare function usePlugin<T extends Plugin>(plugin: T & {
179
186
  * ```
180
187
  */
181
188
  declare function useFunc<TEvent = any, TContext = any, TResult = any>(handler: () => Handler<TEvent, TContext, TResult>): Func<TEvent, TContext, TResult>;
189
+ /**
190
+ * Create a cloud function from business logic and auto-load plugins from
191
+ * `func.config.plugins`.
192
+ *
193
+ * `defineFunc` receives business logic directly (no wrapper function), and
194
+ * resolves plugin modules during the first mount based on config from
195
+ * `faas.yaml` (already loaded into `func.config` by `@faasjs/load`,
196
+ * `@faasjs/server`, or `@faasjs/dev`).
197
+ */
198
+ declare function defineFunc<TEvent = any, TContext = any, TResult = any>(handler: Handler<TEvent, TContext, TResult>): Func<TEvent, TContext, TResult>;
182
199
 
183
- export { type Config, type ExportedHandler, Func, type FuncConfig, type FuncEventType, type FuncReturnType, type Handler, type InvokeData, type LifeCycleKey, type MountData, type Next, type Plugin, type UseifyPlugin, nameFunc, useFunc, usePlugin };
200
+ export { type Config, type ExportedHandler, Func, type FuncConfig, type FuncEventType, type FuncReturnType, type Handler, type InvokeData, type LifeCycleKey, type MountData, type Next, type Plugin, type UseifyPlugin, defineFunc, nameFunc, parseFuncFilenameFromStack, useFunc, usePlugin };
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { randomBytes } from 'crypto';
2
+ import { fileURLToPath } from 'url';
2
3
  import { Logger } from '@faasjs/logger';
3
4
 
4
5
  // src/index.ts
@@ -36,12 +37,41 @@ function nameFunc(name, handler) {
36
37
  }
37
38
 
38
39
  // src/index.ts
40
+ 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("");
66
+ }
39
67
  var Func = class {
40
68
  plugins;
41
69
  handler;
42
70
  config;
43
71
  mounted = false;
44
72
  filename;
73
+ autoLoadPluginsFromConfig;
74
+ loadedConfigPlugins = false;
45
75
  cachedFunctions = /* @__PURE__ */ Object.create(null);
46
76
  /**
47
77
  * Create a cloud function
@@ -53,20 +83,90 @@ var Func = class {
53
83
  this.handler = config.handler;
54
84
  this.plugins = config.plugins || [];
55
85
  this.plugins.push(new RunHandler());
86
+ this.autoLoadPluginsFromConfig = !!config.autoLoadPluginsFromConfig;
56
87
  this.config = {
57
88
  plugins: /* @__PURE__ */ Object.create(null)
58
89
  };
59
90
  try {
60
- const stack = new Error().stack;
61
- if (stack) {
62
- const match = stack.split("\n").find((s) => /[^/]\.func\.ts/.test(s))?.match(/\((.*\.func\.ts).*\)/);
63
- if (match) {
64
- this.filename = match[1];
65
- }
66
- }
91
+ this.filename = parseFuncFilenameFromStack(new Error().stack);
67
92
  } catch (_) {
68
93
  }
69
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
+ }
70
170
  compose(key) {
71
171
  let list = [];
72
172
  if (this.cachedFunctions[key]) list = this.cachedFunctions[key];
@@ -129,6 +229,8 @@ var Func = class {
129
229
  return;
130
230
  }
131
231
  if (!data.config) data.config = this.config;
232
+ if (this.autoLoadPluginsFromConfig && !this.loadedConfigPlugins)
233
+ await this.loadPluginsFromConfig(data.config);
132
234
  data.logger.debug(
133
235
  `plugins: ${this.plugins.map((p) => `${p.type}#${p.name}`).join(",")}`
134
236
  );
@@ -205,5 +307,15 @@ function useFunc(handler) {
205
307
  plugins = [];
206
308
  return func;
207
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;
319
+ }
208
320
 
209
- export { Func, nameFunc, useFunc, usePlugin };
321
+ export { Func, defineFunc, 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.3",
3
+ "version": "v8.0.0-beta.5",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -30,12 +30,12 @@
30
30
  "dist"
31
31
  ],
32
32
  "peerDependencies": {
33
- "@faasjs/deep_merge": ">=v8.0.0-beta.3",
34
- "@faasjs/logger": ">=v8.0.0-beta.3"
33
+ "@faasjs/deep_merge": ">=v8.0.0-beta.5",
34
+ "@faasjs/logger": ">=v8.0.0-beta.5"
35
35
  },
36
36
  "devDependencies": {
37
- "@faasjs/deep_merge": ">=v8.0.0-beta.3",
38
- "@faasjs/logger": ">=v8.0.0-beta.3"
37
+ "@faasjs/deep_merge": ">=v8.0.0-beta.5",
38
+ "@faasjs/logger": ">=v8.0.0-beta.5"
39
39
  },
40
40
  "engines": {
41
41
  "node": ">=24.0.0",