@velajs/vela 1.4.0 → 1.5.0

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/README.md +52 -0
  3. package/dist/cache/cache.module.d.ts +1 -0
  4. package/dist/cache/cache.module.js +6 -0
  5. package/dist/config/config.module.d.ts +4 -1
  6. package/dist/config/config.module.js +6 -0
  7. package/dist/container/container.d.ts +32 -4
  8. package/dist/container/container.js +252 -103
  9. package/dist/container/decorators.d.ts +8 -1
  10. package/dist/container/decorators.js +7 -4
  11. package/dist/container/index.d.ts +1 -1
  12. package/dist/container/index.js +1 -1
  13. package/dist/container/types.d.ts +13 -0
  14. package/dist/container/types.js +10 -0
  15. package/dist/cors/cors.module.d.ts +3 -1
  16. package/dist/cors/cors.module.js +2 -0
  17. package/dist/fetch/fetch.module.d.ts +3 -1
  18. package/dist/fetch/fetch.module.js +8 -3
  19. package/dist/http/argument-resolver.d.ts +10 -0
  20. package/dist/http/argument-resolver.js +89 -0
  21. package/dist/http/handler-executor.d.ts +20 -0
  22. package/dist/http/handler-executor.js +108 -0
  23. package/dist/http/instantiate.d.ts +4 -0
  24. package/dist/http/instantiate.js +18 -0
  25. package/dist/http/response-mapper.d.ts +8 -0
  26. package/dist/http/response-mapper.js +42 -0
  27. package/dist/http/route.manager.d.ts +1 -9
  28. package/dist/http/route.manager.js +23 -240
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.js +2 -2
  31. package/dist/module/decorators.d.ts +6 -2
  32. package/dist/module/decorators.js +7 -6
  33. package/dist/module/index.d.ts +2 -1
  34. package/dist/module/index.js +2 -1
  35. package/dist/module/module-loader.d.ts +6 -1
  36. package/dist/module/module-loader.js +127 -47
  37. package/dist/module/stable-hash.d.ts +15 -0
  38. package/dist/module/stable-hash.js +52 -0
  39. package/dist/registry/types.d.ts +11 -0
  40. package/dist/throttler/throttler.module.d.ts +6 -2
  41. package/dist/throttler/throttler.module.js +6 -0
  42. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { Scope } from "../constants.js";
2
- import { ForwardRef, InjectionToken, ModuleVisibilityError } from "../container/types.js";
2
+ import { ForwardRef, InjectionToken, ModuleVisibilityError, MultipleProvidersFoundError } from "../container/types.js";
3
3
  import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from "../pipeline/tokens.js";
4
4
  import { MetadataRegistry } from "../registry/metadata.registry.js";
5
5
  import { getOrCreateArray } from "../registry/util.js";
@@ -12,22 +12,43 @@ const APP_TOKENS = new Set([
12
12
  APP_FILTER,
13
13
  APP_MIDDLEWARE
14
14
  ]);
15
+ const DEFAULT_KEY = "default";
15
16
  function isDynamicModule(value) {
17
+ // TS 4.9+ narrows `'module' in value` so `value.module` is typed `unknown`
18
+ // — no cast required for the typeof check below.
16
19
  return typeof value === "object" && value !== null && "module" in value && typeof value.module === "function";
17
20
  }
18
21
  function implementsNestModule(cls) {
22
+ // `Type.prototype` is `any` — direct property access is type-safe enough.
19
23
  return typeof cls.prototype?.configure === "function";
20
24
  }
21
25
  function tokenOfProvider(provider) {
22
26
  return typeof provider === "function" ? provider : provider.provide;
23
27
  }
28
+ function keyOfImport(entry) {
29
+ return isDynamicModule(entry) ? entry.key ?? DEFAULT_KEY : DEFAULT_KEY;
30
+ }
31
+ /**
32
+ * `ForwardRef.factory` is typed to return the broader `Token<T>` because the
33
+ * primitive is shared with provider injection. In module-imports position the
34
+ * runtime contract narrows: the factory must yield a module class or a
35
+ * `DynamicModule`. This helper validates the contract AND narrows the type
36
+ * without a structural cast.
37
+ */ function unwrapModuleForwardRef(ref) {
38
+ const result = ref.factory();
39
+ if (typeof result === 'function') return result;
40
+ if (isDynamicModule(result)) return result;
41
+ throw new Error(`forwardRef in module imports must resolve to a module class or DynamicModule; ` + `got ${typeof result === 'object' ? 'a non-module object' : typeof result}.`);
42
+ }
24
43
  export class ModuleLoader {
25
44
  container;
26
45
  router;
27
- processedModules = new Set();
46
+ // class set of keys already processed (multi-instance dedup is by both)
47
+ processedModules = new Map();
28
48
  processingStack = new Set();
29
49
  collectedControllers = new Set();
30
50
  registeredProviders = [];
51
+ // (class, key) → exported tokens
31
52
  moduleExportsCache = new Map();
32
53
  globalExports = new Set();
33
54
  consumerMiddlewareDefinitions = [];
@@ -54,7 +75,8 @@ export class ModuleLoader {
54
75
  []
55
76
  ]
56
77
  ]);
57
- moduleIdByClass = new Map();
78
+ // (class, key) → composed moduleId (cached for stable identity within a load)
79
+ moduleIdByClassKey = new Map();
58
80
  seenModuleIds = new Set();
59
81
  constructor(container, router){
60
82
  this.container = container;
@@ -66,47 +88,80 @@ export class ModuleLoader {
66
88
  this.router.registerController(controller);
67
89
  }
68
90
  }
69
- getModuleId(moduleClass) {
70
- const cached = this.moduleIdByClass.get(moduleClass);
71
- if (cached) return cached;
91
+ getModuleId(moduleClass, key) {
92
+ let perClass = this.moduleIdByClassKey.get(moduleClass);
93
+ if (perClass) {
94
+ const cached = perClass.get(key);
95
+ if (cached) return cached;
96
+ } else {
97
+ perClass = new Map();
98
+ this.moduleIdByClassKey.set(moduleClass, perClass);
99
+ }
72
100
  const baseName = moduleClass.name || "AnonModule";
73
- let id = baseName;
101
+ let id = `${baseName}#${key}`;
74
102
  let counter = 0;
103
+ // Cross-class name collision (rare): two different classes named identically.
104
+ // The (class, key) pair identifies us; bump suffix to keep `id` strings unique
105
+ // in `seenModuleIds` so debug output stays unambiguous.
75
106
  while(this.seenModuleIds.has(id)){
76
- id = `${baseName}#${++counter}`;
107
+ id = `${baseName}#${key}~${++counter}`;
77
108
  }
78
109
  this.seenModuleIds.add(id);
79
- this.moduleIdByClass.set(moduleClass, id);
110
+ perClass.set(key, id);
80
111
  return id;
81
112
  }
113
+ isProcessed(moduleClass, key) {
114
+ return this.processedModules.get(moduleClass)?.has(key) ?? false;
115
+ }
116
+ markProcessed(moduleClass, key) {
117
+ let keys = this.processedModules.get(moduleClass);
118
+ if (!keys) {
119
+ keys = new Set();
120
+ this.processedModules.set(moduleClass, keys);
121
+ }
122
+ keys.add(key);
123
+ }
124
+ getCachedExports(moduleClass, key) {
125
+ return this.moduleExportsCache.get(moduleClass)?.get(key);
126
+ }
127
+ cacheExports(moduleClass, key, exports) {
128
+ let perClass = this.moduleExportsCache.get(moduleClass);
129
+ if (!perClass) {
130
+ perClass = new Map();
131
+ this.moduleExportsCache.set(moduleClass, perClass);
132
+ }
133
+ perClass.set(key, exports);
134
+ }
82
135
  processModule(moduleClassOrDynamic) {
83
- // Handle dynamic modules ({ module, imports, controllers, providers })
84
136
  let moduleClass;
85
137
  let extraImports = [];
86
138
  let extraControllers = [];
87
139
  let extraProviders = [];
88
140
  let extraExports = [];
141
+ let key = DEFAULT_KEY;
89
142
  if (isDynamicModule(moduleClassOrDynamic)) {
90
143
  moduleClass = moduleClassOrDynamic.module;
91
144
  extraImports = moduleClassOrDynamic.imports ?? [];
92
145
  extraControllers = moduleClassOrDynamic.controllers ?? [];
93
146
  extraProviders = moduleClassOrDynamic.providers ?? [];
94
147
  extraExports = moduleClassOrDynamic.exports ?? [];
148
+ key = moduleClassOrDynamic.key ?? DEFAULT_KEY;
95
149
  } else {
96
150
  moduleClass = moduleClassOrDynamic;
97
151
  }
98
- if (this.processedModules.has(moduleClass)) {
152
+ if (this.isProcessed(moduleClass, key)) {
99
153
  // Even if already processed, still collect extra controllers from dynamic module
100
154
  for (const controller of extraControllers){
101
155
  this.collectedControllers.add(controller);
102
156
  }
103
- return this.moduleExportsCache.get(moduleClass) ?? new Set();
157
+ return this.getCachedExports(moduleClass, key) ?? new Set();
104
158
  }
105
- if (this.processingStack.has(moduleClass)) {
159
+ const stackKey = `${moduleClass.name}#${key}`;
160
+ if (this.processingStack.has(stackKey)) {
106
161
  const chain = [
107
162
  ...this.processingStack,
108
- moduleClass
109
- ].map((m)=>m.name).join(" -> ");
163
+ stackKey
164
+ ].join(" -> ");
110
165
  throw new Error(`Circular module dependency detected: ${chain}`);
111
166
  }
112
167
  if (!isModule(moduleClass)) {
@@ -120,25 +175,35 @@ export class ModuleLoader {
120
175
  if (!metadata) {
121
176
  throw new Error(`Failed to get module metadata for ${moduleClass.name}`);
122
177
  }
123
- this.processingStack.add(moduleClass);
124
- const moduleId = this.getModuleId(moduleClass);
178
+ this.processingStack.add(stackKey);
179
+ const moduleId = this.getModuleId(moduleClass, key);
125
180
  try {
126
181
  const importedProviders = new Set(this.globalExports);
127
182
  // Determine importedModuleIds eagerly (before recursing) so child
128
183
  // modules can be referenced in our scope's importedModules set.
129
184
  const importedModuleIds = new Set();
130
- for (const entry of [
185
+ // Track per-class keys seen in this imports array — emit a loader-time
186
+ // diagnostic when the same module class appears under both `"default"`
187
+ // and at least one explicit key (almost always user error).
188
+ const keysByClassInImports = new Map();
189
+ const allImports = [
131
190
  ...metadata.imports,
132
191
  ...extraImports
133
- ]){
134
- // Unwrap forwardRef(() => Module) — resolves lazy circular references
135
- const isForwardRef = entry instanceof ForwardRef;
136
- const importedModule = isForwardRef ? entry.factory() : entry;
192
+ ];
193
+ for (const entry of allImports){
194
+ const importedModule = entry instanceof ForwardRef ? unwrapModuleForwardRef(entry) : entry;
137
195
  const importedModuleClass = isDynamicModule(importedModule) ? importedModule.module : importedModule;
138
- importedModuleIds.add(this.getModuleId(importedModuleClass));
139
- // If this forwardRef-wrapped import is currently being processed, skip it to
140
- // break the circular chain. Non-forwardRef circular imports still throw.
141
- if (isForwardRef && this.processingStack.has(importedModuleClass)) {
196
+ const importedKey = keyOfImport(importedModule);
197
+ let keys = keysByClassInImports.get(importedModuleClass);
198
+ if (!keys) {
199
+ keys = new Set();
200
+ keysByClassInImports.set(importedModuleClass, keys);
201
+ }
202
+ keys.add(importedKey);
203
+ const importedId = this.getModuleId(importedModuleClass, importedKey);
204
+ importedModuleIds.add(importedId);
205
+ const importedStackKey = `${importedModuleClass.name}#${importedKey}`;
206
+ if (entry instanceof ForwardRef && this.processingStack.has(importedStackKey)) {
142
207
  continue;
143
208
  }
144
209
  const exportedTokens = this.processModule(importedModule);
@@ -146,6 +211,7 @@ export class ModuleLoader {
146
211
  importedProviders.add(token);
147
212
  }
148
213
  }
214
+ this.warnOnMixedDefaultAndKeyed(moduleClass.name, keysByClassInImports);
149
215
  const allProviders = [
150
216
  ...metadata.providers,
151
217
  ...extraProviders
@@ -183,15 +249,18 @@ export class ModuleLoader {
183
249
  this.registerProvider(provider, moduleId);
184
250
  }
185
251
  for (const controller of allControllers){
252
+ // Register the controller in its owning module's bucket so its
253
+ // dependencies resolve from the module's POV (vs `__root__`'s).
254
+ this.container.register(controller, moduleId);
186
255
  this.collectedControllers.add(controller);
187
256
  }
188
257
  // Propagate module-level Use* decorators (@UseGuards, @UseInterceptors, etc.) to each controller
189
258
  for (const controller of allControllers){
190
259
  MetadataRegistry.propagateControllerComponents(moduleClass, controller);
191
260
  }
192
- this.processedModules.add(moduleClass);
261
+ this.markProcessed(moduleClass, key);
193
262
  const exports = this.buildExportSet(allExports, allProviders, importedProviders);
194
- this.moduleExportsCache.set(moduleClass, exports);
263
+ this.cacheExports(moduleClass, key, exports);
195
264
  if (isGlobal) {
196
265
  for (const token of exports){
197
266
  this.globalExports.add(token);
@@ -200,7 +269,7 @@ export class ModuleLoader {
200
269
  // Call configure() if the module implements NestModule. The module is
201
270
  // resolved through the container so it can have its own DI dependencies.
202
271
  if (implementsNestModule(moduleClass)) {
203
- if (!this.container.has(moduleClass)) {
272
+ if (!this.container.hasInScope(moduleClass, moduleId)) {
204
273
  this.container.register(moduleClass, moduleId);
205
274
  }
206
275
  const instance = this.container.resolve(moduleClass, moduleId);
@@ -210,15 +279,26 @@ export class ModuleLoader {
210
279
  }
211
280
  return exports;
212
281
  } finally{
213
- this.processingStack.delete(moduleClass);
282
+ this.processingStack.delete(stackKey);
283
+ }
284
+ }
285
+ warnOnMixedDefaultAndKeyed(parentName, keysByClass) {
286
+ const mode = this.container.getDiagnostics();
287
+ if (mode === "silent") return;
288
+ for (const [cls, keys] of keysByClass){
289
+ if (keys.size < 2) continue;
290
+ if (!keys.has(DEFAULT_KEY)) continue;
291
+ const message = `[vela] ${cls.name} imported in both bare and keyed form in '${parentName}'. ` + `These resolve to distinct module instances; consumers asking for an exported ` + `token will hit MultipleProvidersFoundError. Use one form consistently.`;
292
+ if (mode === "throw") throw new Error(message);
293
+ console.warn(message);
214
294
  }
215
295
  }
216
296
  registerProvider(provider, moduleId) {
217
297
  if (typeof provider === "function") {
218
- if (!this.container.has(provider)) {
219
- this.container.register(provider, moduleId);
220
- this.registeredProviders.push(provider);
221
- }
298
+ // Per-module bucket: the same class can be registered in multiple
299
+ // modules' buckets simultaneously without collision.
300
+ this.container.register(provider, moduleId);
301
+ this.registeredProviders.push(provider);
222
302
  } else {
223
303
  const token = provider.provide;
224
304
  if (!token) {
@@ -226,22 +306,21 @@ export class ModuleLoader {
226
306
  return;
227
307
  }
228
308
  if (this.isAppToken(token)) {
309
+ // Multiple APP_* providers within the same module bucket need
310
+ // distinct tokens so they don't overwrite each other in the bucket
311
+ // Map. Across buckets, `container.resolveAll(APP_GUARD)` walks
312
+ // every bucket — no need to mark synthetic tokens global.
229
313
  const syntheticToken = new InjectionToken(`${token.toString()}:${this.appProviderCounter++}`);
230
314
  this.container.register({
231
315
  ...provider,
232
316
  provide: syntheticToken
233
317
  }, moduleId);
234
- // Synthetic APP_* tokens are resolved at request time by RouteManager
235
- // with no requester; mark them global so the visibility check passes.
236
- this.container.markGlobalToken(syntheticToken);
237
318
  this.registeredProviders.push(syntheticToken);
238
319
  getOrCreateArray(this.appProviderTokens, token).push(syntheticToken);
239
320
  return;
240
321
  }
241
- if (!this.container.has(token)) {
242
- this.container.register(provider, moduleId);
243
- this.registeredProviders.push(token);
244
- }
322
+ this.container.register(provider, moduleId);
323
+ this.registeredProviders.push(token);
245
324
  }
246
325
  }
247
326
  isAppToken(token) {
@@ -258,7 +337,7 @@ export class ModuleLoader {
258
337
  const isLocalProvider = providerTokens.has(exported);
259
338
  const isImportedProvider = importedProviders.has(exported);
260
339
  if (!isLocalProvider && !isImportedProvider) {
261
- const name = exported.name ?? String(exported);
340
+ const name = typeof exported === 'function' ? exported.name : String(exported);
262
341
  console.warn(`Warning: Exporting '${name}' which is neither a local provider nor imported from another module.`);
263
342
  }
264
343
  exportSet.add(exported);
@@ -295,9 +374,9 @@ export class ModuleLoader {
295
374
  const instance = await this.container.resolveAsync(token);
296
375
  instanceSet.add(instance);
297
376
  } catch (err) {
298
- // Module visibility errors must always propagate they indicate a
299
- // genuine wiring bug, not something to swallow as a discovery skip.
300
- if (err instanceof ModuleVisibilityError) throw err;
377
+ if (err instanceof ModuleVisibilityError || err instanceof MultipleProvidersFoundError) {
378
+ throw err;
379
+ }
301
380
  this.routeError(err, `resolve provider`);
302
381
  }
303
382
  }
@@ -309,7 +388,9 @@ export class ModuleLoader {
309
388
  const instance = await this.container.resolveAsync(controller);
310
389
  instanceSet.add(instance);
311
390
  } catch (err) {
312
- if (err instanceof ModuleVisibilityError) throw err;
391
+ if (err instanceof ModuleVisibilityError || err instanceof MultipleProvidersFoundError) {
392
+ throw err;
393
+ }
313
394
  this.routeError(err, `resolve controller ${controller.name}`);
314
395
  }
315
396
  }
@@ -323,7 +404,6 @@ export class ModuleLoader {
323
404
  if (mode === "throw") {
324
405
  throw err instanceof Error ? err : new Error(String(err));
325
406
  }
326
- // 'log'
327
407
  console.warn(`[vela] ${context} failed:`, err);
328
408
  }
329
409
  }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Deterministic, sync, edge-safe hash for module-instance discrimination.
3
+ *
4
+ * Walks values (objects/arrays/primitives/functions/class instances) into a
5
+ * sorted, source-fingerprinting JSON string, then runs FNV-1a (32-bit) for a
6
+ * compact 8-hex-char output.
7
+ *
8
+ * Functions and class instances are tolerated — they become `__fn:source` or
9
+ * `__cls:ClassName` markers. Two distinct closures with the same source code
10
+ * collide; module authors who care about that should pass an explicit `key`.
11
+ *
12
+ * Not cryptographic. Sufficient for discriminating module instances within
13
+ * an app.
14
+ */
15
+ export declare function stableHash(value: unknown): string;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Deterministic, sync, edge-safe hash for module-instance discrimination.
3
+ *
4
+ * Walks values (objects/arrays/primitives/functions/class instances) into a
5
+ * sorted, source-fingerprinting JSON string, then runs FNV-1a (32-bit) for a
6
+ * compact 8-hex-char output.
7
+ *
8
+ * Functions and class instances are tolerated — they become `__fn:source` or
9
+ * `__cls:ClassName` markers. Two distinct closures with the same source code
10
+ * collide; module authors who care about that should pass an explicit `key`.
11
+ *
12
+ * Not cryptographic. Sufficient for discriminating module instances within
13
+ * an app.
14
+ */ export function stableHash(value) {
15
+ return fnv1a(stableStringify(value));
16
+ }
17
+ function stableStringify(value) {
18
+ if (value === null || value === undefined) return JSON.stringify(value) ?? 'undefined';
19
+ if (typeof value === 'function') {
20
+ // Source-fingerprint via FNV-1a so we don't bloat the parent hash; this
21
+ // keeps two distinct factory functions distinguishable as long as their
22
+ // source differs (the common case). Names are intentionally excluded —
23
+ // two arrow functions with the same body should dedup.
24
+ return `__fn:${fnv1a(value.toString())}`;
25
+ }
26
+ if (typeof value === 'symbol') {
27
+ return `__sym:${value.description ?? ''}`;
28
+ }
29
+ if (typeof value !== 'object') return JSON.stringify(value);
30
+ if (Array.isArray(value)) {
31
+ return '[' + value.map(stableStringify).join(',') + ']';
32
+ }
33
+ const proto = Object.getPrototypeOf(value);
34
+ if (proto !== null && proto !== Object.prototype) {
35
+ const name = proto?.constructor?.name ?? 'AnonymousClass';
36
+ return `__cls:${name}` + stringifyOwnProps(value);
37
+ }
38
+ return stringifyOwnProps(value);
39
+ }
40
+ function stringifyOwnProps(obj) {
41
+ const keys = Object.keys(obj).sort();
42
+ const parts = keys.map((k)=>JSON.stringify(k) + ':' + stableStringify(obj[k]));
43
+ return '{' + parts.join(',') + '}';
44
+ }
45
+ function fnv1a(s) {
46
+ let h = 0x811c9dc5;
47
+ for(let i = 0; i < s.length; i++){
48
+ h ^= s.charCodeAt(i);
49
+ h = Math.imul(h, 0x01000193);
50
+ }
51
+ return (h >>> 0).toString(16).padStart(8, '0');
52
+ }
@@ -45,6 +45,17 @@ export interface AsyncModuleOptions<T = unknown> {
45
45
  }
46
46
  export interface DynamicModule {
47
47
  module: Type;
48
+ /**
49
+ * Author-supplied instance discriminator. Two DynamicModules with the same
50
+ * `module` class and the same `key` dedup; with different keys they coexist
51
+ * as separate module instances. Defaults to `"default"` when absent — which
52
+ * preserves single-instance dedup for the common case.
53
+ *
54
+ * For `forRoot(options)`, derive `key: stableHash(options)` so identical
55
+ * options dedup automatically. For `forRootAsync` (factories aren't
56
+ * structurally hashable), pass an explicit string.
57
+ */
58
+ key?: string;
48
59
  imports?: ModuleImport[];
49
60
  providers?: Array<Type | ProviderOptions>;
50
61
  controllers?: Type[];
@@ -1,6 +1,10 @@
1
1
  import type { AsyncModuleOptions, DynamicModule } from '../module/types';
2
2
  import type { ThrottlerModuleOptions } from './throttler.types';
3
3
  export declare class ThrottlerModule {
4
- static forRoot(options: ThrottlerModuleOptions): DynamicModule;
5
- static forRootAsync(options: AsyncModuleOptions<ThrottlerModuleOptions>): DynamicModule;
4
+ static forRoot(options: ThrottlerModuleOptions & {
5
+ key?: string;
6
+ }): DynamicModule;
7
+ static forRootAsync(options: AsyncModuleOptions<ThrottlerModuleOptions> & {
8
+ key?: string;
9
+ }): DynamicModule;
6
10
  }
@@ -1,3 +1,4 @@
1
+ import { stableHash } from "../module/stable-hash.js";
1
2
  import { APP_GUARD } from "../pipeline/tokens.js";
2
3
  import { ThrottlerGuard } from "./throttler.guard.js";
3
4
  import { ThrottlerStorage } from "./throttler.storage.js";
@@ -21,6 +22,7 @@ export class ThrottlerModule {
21
22
  ];
22
23
  return {
23
24
  module: ThrottlerModule,
25
+ key: options.key ?? stableHash(options),
24
26
  providers,
25
27
  exports: [
26
28
  THROTTLER_OPTIONS,
@@ -51,6 +53,10 @@ export class ThrottlerModule {
51
53
  ];
52
54
  return {
53
55
  module: ThrottlerModule,
56
+ key: options.key ?? stableHash({
57
+ inject: options.inject,
58
+ useFactory: options.useFactory
59
+ }),
54
60
  imports: options.imports ?? [],
55
61
  providers,
56
62
  exports: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velajs/vela",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "NestJS-compatible framework for edge runtimes, powered by Hono",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",