@ordius/adonisjs-currencyx 1.4.9

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.
@@ -0,0 +1,29 @@
1
+ /*
2
+ * @ordius/adonisjs-currencyx
3
+ *
4
+ * (c) Mixxtor
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import app from '@adonisjs/core/services/app';
10
+ /**
11
+ * Currency service with full type inference
12
+ *
13
+ * Usage:
14
+ * ```ts
15
+ * import currency from '@ordius/adonisjs-currencyx/services/currency'
16
+ *
17
+ * // Direct usage - no type casting needed
18
+ * const rates = await currency.latestRates()
19
+ *
20
+ * // Provider switching with type inference
21
+ * const googleProvider = currency.use('google') // Only configured providers
22
+ * const rates = await googleProvider.latestRates()
23
+ * ```
24
+ */
25
+ let currency;
26
+ await app.booted(async () => {
27
+ currency = await app.container.make('currency.manager');
28
+ });
29
+ export { currency as default };
@@ -0,0 +1,31 @@
1
+ import type { DatabaseConfig, ServiceConfigProvider, ExchangeFactory } from './types.js';
2
+ import { DatabaseExchange } from './exchanges/database.js';
3
+ import type { ConfigProvider } from '@adonisjs/core/types';
4
+ /**
5
+ * Define database exchange provider configuration
6
+ * Returns a factory function to avoid eager instantiation
7
+ */
8
+ declare function database(config: DatabaseConfig): DatabaseExchange;
9
+ /**
10
+ * Exchange configuration helpers
11
+ */
12
+ export declare const exchanges: {
13
+ readonly database: typeof database;
14
+ readonly google: (config?: import("@mixxtor/currencyx-js").GoogleFinanceConfig) => import("@mixxtor/currencyx-js").GoogleFinanceExchange;
15
+ readonly fixer: (config: import("@mixxtor/currencyx-js").FixerConfig) => import("@mixxtor/currencyx-js").FixerExchange;
16
+ };
17
+ /**
18
+ * Helper to remap known exchange exchanges to factory functions
19
+ */
20
+ type ResolvedConfig<Exchanges extends Record<string, ExchangeFactory>> = {
21
+ default: keyof Exchanges;
22
+ exchanges: {
23
+ [K in keyof Exchanges]: Exchanges[K] extends ServiceConfigProvider<infer A> ? A : Exchanges[K];
24
+ };
25
+ };
26
+ /**
27
+ * Define currency configuration with type inference
28
+ * Following AdonisJS pattern for better type safety
29
+ */
30
+ export declare function defineConfig<Exchanges extends Record<string, any>>(config: ResolvedConfig<Exchanges>): ConfigProvider<ResolvedConfig<Exchanges>>;
31
+ export {};
@@ -0,0 +1,56 @@
1
+ import { exchanges as currencyExchanges } from '@mixxtor/currencyx-js';
2
+ import { DatabaseExchange } from './exchanges/database.js';
3
+ import { configProvider } from '@adonisjs/core';
4
+ /**
5
+ * Define database exchange provider configuration
6
+ * Returns a factory function to avoid eager instantiation
7
+ */
8
+ function database(config) {
9
+ if (!config.model) {
10
+ throw new Error('Database exchange requires a model');
11
+ }
12
+ const dbConfig = {
13
+ model: config.model,
14
+ base: config.base || 'USD',
15
+ columns: {
16
+ code: 'code',
17
+ rate: 'exchange_rate',
18
+ ...config.columns,
19
+ },
20
+ cache: config.cache,
21
+ };
22
+ return new DatabaseExchange(dbConfig);
23
+ }
24
+ /**
25
+ * Exchange configuration helpers
26
+ */
27
+ export const exchanges = {
28
+ ...currencyExchanges,
29
+ database,
30
+ };
31
+ /**
32
+ * Define currency configuration with type inference
33
+ * Following AdonisJS pattern for better type safety
34
+ */
35
+ export function defineConfig(config) {
36
+ return configProvider.create(async (_app) => {
37
+ const { exchanges: exchangesFactory, default: defaultExchange } = config;
38
+ const exchangesNames = Object.keys(exchangesFactory);
39
+ /**
40
+ * Configured exchanges
41
+ */
42
+ const exchangeExchanges = {};
43
+ /**
44
+ * Looping over providers and resolving their config providers
45
+ * to get factory functions
46
+ */
47
+ for (let providerName of exchangesNames) {
48
+ const exchange = exchangesFactory[providerName];
49
+ exchangeExchanges[providerName] = exchange;
50
+ }
51
+ return {
52
+ default: defaultExchange,
53
+ exchanges: exchangeExchanges,
54
+ };
55
+ });
56
+ }
@@ -0,0 +1,54 @@
1
+ import type { CurrencyCode, ConversionResult, ExchangeRatesResult, ConvertParams, ExchangeRatesParams } from '@mixxtor/currencyx-js';
2
+ import { BaseCurrencyExchange } from '@mixxtor/currencyx-js';
3
+ import type { DatabaseConfig } from '../types.js';
4
+ import type { CacheService } from '@adonisjs/cache/types';
5
+ import { PROVIDER_CURRENCY_MODEL } from '../symbols.js';
6
+ import type { LucidModel } from '@adonisjs/lucid/types/model';
7
+ export declare class DatabaseExchange<Model extends LucidModel = LucidModel> extends BaseCurrencyExchange {
8
+ #private;
9
+ [PROVIDER_CURRENCY_MODEL]: InstanceType<Model>;
10
+ readonly name = "database";
11
+ protected model?: Model;
12
+ private columns;
13
+ private configModel?;
14
+ private cache?;
15
+ private cacheSetupPromise?;
16
+ private config;
17
+ constructor(config: DatabaseConfig<Model>);
18
+ /**
19
+ * Imports the model from the provider, returns and caches it
20
+ * for further operations.
21
+ */
22
+ protected getModel(): Promise<Model>;
23
+ /**
24
+ * Imports the cache service from the provider, returns and caches it
25
+ * for further operations.
26
+ */
27
+ protected getCacheService(): Promise<CacheService>;
28
+ /**
29
+ * Convert currency using database rates
30
+ */
31
+ convert(params: ConvertParams): Promise<ConversionResult>;
32
+ /**
33
+ * Get latest rates (required abstract method)
34
+ */
35
+ latestRates(params?: ExchangeRatesParams & {
36
+ cache?: boolean;
37
+ }): Promise<ExchangeRatesResult>;
38
+ /**
39
+ * Clear the currency cache
40
+ */
41
+ clearCache(): Promise<void>;
42
+ /**
43
+ * Refresh currency data from database
44
+ */
45
+ refreshCurrencyData(): Promise<void>;
46
+ /**
47
+ * Get convert rate (required abstract method)
48
+ */
49
+ getConvertRate(from: CurrencyCode, to: CurrencyCode): Promise<number | undefined>;
50
+ /**
51
+ * Cleanup method for graceful shutdown
52
+ */
53
+ cleanup(): Promise<void>;
54
+ }
@@ -0,0 +1,361 @@
1
+ import { BaseCurrencyExchange } from '@mixxtor/currencyx-js';
2
+ export class DatabaseExchange extends BaseCurrencyExchange {
3
+ name = 'database';
4
+ model;
5
+ columns;
6
+ configModel;
7
+ cache;
8
+ cacheSetupPromise;
9
+ config;
10
+ #defaultCacheTTL = '1h'; // in milliseconds or human-readable string (e.g., '1d')
11
+ #defaultCacheKeyPrefix = 'currency';
12
+ constructor(config) {
13
+ super();
14
+ this.config = config;
15
+ this.columns = {
16
+ code: config.columns?.code || 'code',
17
+ rate: config.columns?.rate || 'exchange_rate',
18
+ created_at: config.columns?.created_at || 'created_at',
19
+ updated_at: config.columns?.updated_at || 'updated_at',
20
+ ...config.columns,
21
+ };
22
+ this.base = config.base || 'USD';
23
+ this.configModel = config.model;
24
+ // Validate configuration
25
+ this.#validateConfig();
26
+ }
27
+ /**
28
+ * Validate configuration to prevent runtime errors
29
+ */
30
+ #validateConfig() {
31
+ if (!this.configModel) {
32
+ throw new Error('Currency model configuration is required');
33
+ }
34
+ const cacheConfig = this.config.cache;
35
+ if (cacheConfig !== false && cacheConfig && !cacheConfig.service) {
36
+ throw new Error('Cache service configuration is required when cache is enabled');
37
+ }
38
+ // Validate base currency format
39
+ if (this.base && !/^[A-Z]{3}$/.test(this.base)) {
40
+ console.warn(`Base currency '${this.base}' should be a 3-letter ISO currency code`);
41
+ }
42
+ }
43
+ /**
44
+ * Imports the model from the provider, returns and caches it
45
+ * for further operations.
46
+ */
47
+ async getModel() {
48
+ if (!this.configModel) {
49
+ throw new Error('Currency model not configured');
50
+ }
51
+ if (this.model && !('hot' in import.meta)) {
52
+ return this.model;
53
+ }
54
+ const importedModel = await this.configModel();
55
+ this.model = 'default' in importedModel ? importedModel.default : importedModel;
56
+ return this.model;
57
+ }
58
+ /**
59
+ * Imports the cache service from the provider, returns and caches it
60
+ * for further operations.
61
+ */
62
+ async getCacheService() {
63
+ const cacheConfig = this.config.cache;
64
+ if (cacheConfig === false || !cacheConfig || !cacheConfig.service) {
65
+ throw new Error('Currency cache not configured');
66
+ }
67
+ if (this.cache && !('hot' in import.meta)) {
68
+ return this.cache;
69
+ }
70
+ const importedCache = await cacheConfig.service();
71
+ this.cache = 'default' in importedCache ? importedCache.default : importedCache;
72
+ return this.cache;
73
+ }
74
+ /**
75
+ * Setup cache based on configuration (lazy initialization)
76
+ */
77
+ async #ensureCacheSetup() {
78
+ if (this.cacheSetupPromise) {
79
+ return this.cacheSetupPromise;
80
+ }
81
+ this.cacheSetupPromise = this.#setupCache().catch((error) => {
82
+ // Reset the promise on error so it can be retried
83
+ this.cacheSetupPromise = undefined;
84
+ throw error;
85
+ });
86
+ return this.cacheSetupPromise;
87
+ }
88
+ /**
89
+ * Setup cache based on configuration
90
+ */
91
+ async #setupCache() {
92
+ const cacheConfig = this.config.cache;
93
+ if (cacheConfig === false || !cacheConfig) {
94
+ return;
95
+ }
96
+ try {
97
+ this.cache = await this.getCacheService();
98
+ }
99
+ catch (error) {
100
+ console.warn('Cache setup failed, continuing without cache:', error.message);
101
+ }
102
+ }
103
+ /**
104
+ * Convert currency using database rates
105
+ */
106
+ async convert(params) {
107
+ // Input validation
108
+ const { amount, from, to } = params;
109
+ if (!amount || amount <= 0) {
110
+ return {
111
+ success: false,
112
+ query: { from, to, amount },
113
+ info: { timestamp: Date.now() },
114
+ date: new Date().toISOString(),
115
+ error: { info: 'Invalid amount: must be greater than 0' },
116
+ };
117
+ }
118
+ if (!from || !to) {
119
+ return {
120
+ success: false,
121
+ query: { from, to, amount },
122
+ info: { timestamp: Date.now() },
123
+ date: new Date().toISOString(),
124
+ error: { info: 'Invalid currency codes: from and to are required' },
125
+ };
126
+ }
127
+ const result = {
128
+ success: false,
129
+ query: { from, to, amount },
130
+ info: { timestamp: Date.now(), rate: 1 },
131
+ date: new Date().toISOString(),
132
+ result: amount,
133
+ };
134
+ // Same currency conversion
135
+ if (from === to) {
136
+ result.success = true;
137
+ return result;
138
+ }
139
+ try {
140
+ const currencies = await this.#getCurrenciesByCodes([from, to]);
141
+ const fromCurrency = currencies?.find((c) => this.#getCurrencyCode(c) === from);
142
+ const toCurrency = currencies?.find((c) => this.#getCurrencyCode(c) === to);
143
+ if (!fromCurrency || !toCurrency) {
144
+ return {
145
+ ...result,
146
+ error: {
147
+ info: `Currency not found: ${!fromCurrency ? from : to}`,
148
+ },
149
+ };
150
+ }
151
+ const fromRate = this.#getCurrencyRate(fromCurrency);
152
+ const toRate = this.#getCurrencyRate(toCurrency);
153
+ const updatedAt = this.#getCurrencyUpdatedAt(fromCurrency) || this.#getCurrencyUpdatedAt(toCurrency);
154
+ if (!fromRate || !toRate) {
155
+ return {
156
+ ...result,
157
+ error: {
158
+ info: 'Invalid exchange rates found in database',
159
+ },
160
+ };
161
+ }
162
+ // Conversion formula: amount * (1/fromCurrencyRate) * toCurrencyRate
163
+ const convertRate = (1 / fromRate) * toRate;
164
+ const convertAmount = amount * convertRate;
165
+ result.success = true;
166
+ result.info.rate = convertRate;
167
+ result.result = convertAmount;
168
+ if (updatedAt) {
169
+ const timestamp = new Date(updatedAt).getTime();
170
+ result.info.timestamp = timestamp;
171
+ result.date = new Date(updatedAt).toISOString();
172
+ }
173
+ return result;
174
+ }
175
+ catch (error) {
176
+ const errorMessage = error instanceof Error ? error.message : 'Unknown database error';
177
+ return {
178
+ success: false,
179
+ query: { from, to, amount },
180
+ info: { timestamp: Date.now() },
181
+ date: new Date().toISOString(),
182
+ error: {
183
+ info: errorMessage,
184
+ type: 'database_error',
185
+ },
186
+ };
187
+ }
188
+ }
189
+ async #currencyList(useCache = true) {
190
+ // Ensure cache is setup before using it
191
+ await this.#ensureCacheSetup();
192
+ const Model = await this.getModel();
193
+ const query = Model.query().select(Object.values(this.columns));
194
+ if (!useCache || !this.cache || !this.config.cache) {
195
+ return await query;
196
+ }
197
+ const { prefix = this.#defaultCacheKeyPrefix, ttl = this.#defaultCacheTTL } = this.config.cache;
198
+ return await this.cache.getOrSet({ key: prefix, factory: () => query, ttl });
199
+ }
200
+ /**
201
+ * Get specific currencies by codes (optimized for targeted queries)
202
+ */
203
+ async #getCurrenciesByCodes(codes, useCache = true) {
204
+ if (!codes || codes.length === 0) {
205
+ return this.#currencyList(useCache);
206
+ }
207
+ // Ensure cache is setup before using it
208
+ await this.#ensureCacheSetup();
209
+ const Model = await this.getModel();
210
+ const query = Model.query()
211
+ .select(Object.values(this.columns))
212
+ .whereIn(this.columns.code, codes);
213
+ if (!useCache || !this.cache || !this.config.cache) {
214
+ return await query;
215
+ }
216
+ const { prefix = this.#defaultCacheKeyPrefix, ttl = this.#defaultCacheTTL } = this.config.cache;
217
+ const cacheKey = `${prefix}_${codes.sort().join('_')}`;
218
+ return await this.cache.getOrSet({ key: cacheKey, factory: () => query, ttl });
219
+ }
220
+ /**
221
+ * Helper method to get currency code from a record
222
+ */
223
+ #getCurrencyCode(record) {
224
+ return record[this.columns.code];
225
+ }
226
+ /**
227
+ * Helper method to get currency rate from a record
228
+ */
229
+ #getCurrencyRate(record) {
230
+ return record[this.columns.rate];
231
+ }
232
+ /**
233
+ * Helper method to get currency updated at from a record
234
+ */
235
+ #getCurrencyUpdatedAt(record) {
236
+ const updatedAtColumn = this.columns.updated_at || 'updated_at';
237
+ return record[updatedAtColumn];
238
+ }
239
+ /**
240
+ * Get latest rates (required abstract method)
241
+ */
242
+ async latestRates(params) {
243
+ const { base = this.base, codes: currencyCodes, cache = true } = params || {};
244
+ const result = {
245
+ success: false,
246
+ timestamp: new Date().getTime(),
247
+ date: new Date().toISOString(),
248
+ base: base,
249
+ rates: {},
250
+ error: undefined,
251
+ };
252
+ try {
253
+ const currencies = await this.#currencyList(cache);
254
+ if (!currencies || currencies.length === 0) {
255
+ result.error = {
256
+ info: 'No currencies found in database',
257
+ type: 'database_error',
258
+ };
259
+ return result;
260
+ }
261
+ let latestDate;
262
+ for (const record of currencies) {
263
+ const code = this.#getCurrencyCode(record);
264
+ const rate = this.#getCurrencyRate(record);
265
+ const updatedAt = this.#getCurrencyUpdatedAt(record);
266
+ if (!code || rate === undefined || rate === null) {
267
+ continue;
268
+ }
269
+ // Filter by currency codes if specified
270
+ if (!currencyCodes || currencyCodes.length === 0 || currencyCodes.includes(code)) {
271
+ result.rates[code] = rate;
272
+ // Track the latest update date
273
+ if (updatedAt) {
274
+ const updatedAtDate = new Date(updatedAt);
275
+ if (!latestDate || updatedAtDate > latestDate) {
276
+ latestDate = updatedAtDate;
277
+ }
278
+ }
279
+ }
280
+ }
281
+ // Update result with latest date if found
282
+ if (latestDate) {
283
+ result.date = latestDate.toISOString();
284
+ result.timestamp = latestDate.getTime();
285
+ }
286
+ result.success = Object.keys(result.rates).length > 0;
287
+ if (!result.success) {
288
+ result.error = {
289
+ info: currencyCodes?.length
290
+ ? `No matching currencies found for codes: ${currencyCodes.join(', ')}`
291
+ : 'No valid currencies found in database',
292
+ type: 'database_error',
293
+ };
294
+ }
295
+ }
296
+ catch (error) {
297
+ const errorMessage = error instanceof Error ? error.message : 'Unknown database error';
298
+ result.error = {
299
+ info: errorMessage,
300
+ type: 'database_error',
301
+ };
302
+ }
303
+ return result;
304
+ }
305
+ /**
306
+ * Clear the currency cache
307
+ */
308
+ async clearCache() {
309
+ if (!this.cache || !this.config.cache) {
310
+ return;
311
+ }
312
+ const { prefix = this.#defaultCacheKeyPrefix } = this.config.cache;
313
+ await this.cache.delete({ key: prefix });
314
+ }
315
+ /**
316
+ * Refresh currency data from database
317
+ */
318
+ async refreshCurrencyData() {
319
+ await this.clearCache();
320
+ // Pre-warm the cache
321
+ await this.#currencyList(true);
322
+ }
323
+ /**
324
+ * Get convert rate (required abstract method)
325
+ */
326
+ async getConvertRate(from, to) {
327
+ try {
328
+ const currencies = await this.#getCurrenciesByCodes([from, to]);
329
+ const fromCurrency = currencies?.find((c) => this.#getCurrencyCode(c) === from);
330
+ const toCurrency = currencies?.find((c) => this.#getCurrencyCode(c) === to);
331
+ if (!fromCurrency || !toCurrency) {
332
+ return undefined;
333
+ }
334
+ const fromRate = this.#getCurrencyRate(fromCurrency);
335
+ const toRate = this.#getCurrencyRate(toCurrency);
336
+ if (fromRate && toRate && fromRate > 0 && toRate > 0) {
337
+ return (1 / fromRate) * toRate;
338
+ }
339
+ return undefined;
340
+ }
341
+ catch {
342
+ return undefined;
343
+ }
344
+ }
345
+ /**
346
+ * Cleanup method for graceful shutdown
347
+ */
348
+ async cleanup() {
349
+ if (this.cacheSetupPromise) {
350
+ try {
351
+ await this.cacheSetupPromise;
352
+ }
353
+ catch {
354
+ // Ignore errors during cleanup
355
+ }
356
+ this.cacheSetupPromise = undefined;
357
+ }
358
+ this.cache = undefined;
359
+ this.model = undefined;
360
+ }
361
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * A symbol to identify the type of the currency model for a given
3
+ * currency provider
4
+ */
5
+ export declare const PROVIDER_CURRENCY_MODEL: unique symbol;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * A symbol to identify the type of the currency model for a given
3
+ * currency provider
4
+ */
5
+ export const PROVIDER_CURRENCY_MODEL = Symbol.for('PROVIDER_CURRENCY_MODEL');
@@ -0,0 +1,111 @@
1
+ import { type CacheOptions, type CacheService } from '@adonisjs/cache/types';
2
+ import { type ApplicationService, type ConfigProvider } from '@adonisjs/core/types';
3
+ import { type LucidModel } from '@adonisjs/lucid/types/model';
4
+ import type BaseCurrencyService from '@mixxtor/currencyx-js';
5
+ import type { CurrencyExchanges, BaseCurrencyExchange, createCurrency } from '@mixxtor/currencyx-js';
6
+ export type { CurrencyExchanges, CurrencyCode } from '@mixxtor/currencyx-js';
7
+ /**
8
+ * Database configuration for currency provider
9
+ */
10
+ export interface DatabaseConfig<Model extends LucidModel = LucidModel, Cache extends CacheConfig | undefined | false = CacheConfig | undefined> {
11
+ /**
12
+ * The Lucid model to use for currency queries
13
+ */
14
+ model: () => Promise<{
15
+ default: Model;
16
+ }> | Model;
17
+ /**
18
+ * Base currency - all exchange rates in database are relative to this currency
19
+ * @default 'USD'
20
+ * @example 'USD' // 1 USD = 0.85 EUR, 1 USD = 0.73 GBP
21
+ */
22
+ base?: string;
23
+ /**
24
+ * Column mapping for the currency table
25
+ */
26
+ columns?: {
27
+ /**
28
+ * Currency code column (e.g., 'USD', 'EUR')
29
+ * @default 'code'
30
+ */
31
+ code: string;
32
+ /**
33
+ * Exchange rate column
34
+ * @default 'exchange_rate'
35
+ */
36
+ rate: string;
37
+ /**
38
+ * Created at column
39
+ * @default 'created_at'
40
+ */
41
+ created_at?: string;
42
+ /**
43
+ * Updated at column
44
+ * @default 'updated_at'
45
+ */
46
+ updated_at?: string;
47
+ };
48
+ /**
49
+ * Cache configuration for this database provider
50
+ * @default false
51
+ */
52
+ cache?: Cache | undefined | false;
53
+ }
54
+ /**
55
+ * Cache configuration for database provider
56
+ */
57
+ export interface CacheConfig extends CacheOptions {
58
+ /**
59
+ * The AdonisJS cache service instance
60
+ * @requires @adonisjs/cache
61
+ */
62
+ service: () => Promise<{
63
+ default: CacheService;
64
+ }> | CacheService;
65
+ }
66
+ /**
67
+ * Complete currency configuration for AdonisJS
68
+ */
69
+ export interface CurrencyConfig<KnownExchanges extends CurrencyExchanges = CurrencyExchanges> {
70
+ /**
71
+ * Default provider to use
72
+ */
73
+ default: keyof KnownExchanges;
74
+ /**
75
+ * Provider configurations
76
+ */
77
+ exchanges: Record<keyof KnownExchanges, BaseCurrencyExchange>;
78
+ }
79
+ /**
80
+ * Infer the providers from the user config
81
+ */
82
+ export type InferExchanges<T extends ConfigProvider<{
83
+ exchanges: Record<string, ExchangeFactory>;
84
+ }>> = Awaited<ReturnType<T['resolver']>>['exchanges'];
85
+ /**
86
+ * Currency record interface for database queries
87
+ */
88
+ export interface CurrencyRecord {
89
+ [key: string]: any;
90
+ code?: string;
91
+ rate?: number;
92
+ updated_at?: Date;
93
+ }
94
+ /**
95
+ * Representation of a factory function that returns
96
+ * an instance of a driver.
97
+ */
98
+ export type ExchangeFactory = BaseCurrencyExchange;
99
+ /**
100
+ * Main Currency Service Implementation
101
+ */
102
+ export interface CurrencyService extends BaseCurrencyService<CurrencyExchanges extends Record<string, ReturnType<typeof createCurrency>> ? CurrencyExchanges : never> {
103
+ }
104
+ /**
105
+ * Service config provider is an extension of the config
106
+ * provider and accepts the name of the disk service
107
+ */
108
+ export type ServiceConfigProvider<Factory extends ExchangeFactory> = {
109
+ type: 'provider';
110
+ resolver: (name: string, app: ApplicationService) => Promise<Factory>;
111
+ };
@@ -0,0 +1 @@
1
+ export {};