@goatlab/node-backend 0.0.16 → 0.1.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.
- package/README.md +54 -38
- package/dist/container/Container.d.ts +441 -0
- package/dist/container/Container.js +895 -0
- package/dist/container/Container.js.map +1 -0
- package/dist/container/DistributedCacheInvalidator.d.ts +84 -0
- package/dist/container/DistributedCacheInvalidator.js +213 -0
- package/dist/container/DistributedCacheInvalidator.js.map +1 -0
- package/dist/container/LruCache.d.ts +14 -0
- package/dist/container/LruCache.js +23 -0
- package/dist/container/LruCache.js.map +1 -0
- package/dist/container/types.d.ts +128 -0
- package/dist/container/types.js +6 -0
- package/dist/container/types.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +53 -1
- package/dist/index.js.map +1 -1
- package/dist/server/bootstraps/getExpressTrpcApp.d.ts +17 -0
- package/dist/server/bootstraps/getExpressTrpcApp.js +98 -0
- package/dist/server/bootstraps/getExpressTrpcApp.js.map +1 -0
- package/dist/server/consts.d.ts +35 -0
- package/dist/server/consts.js +33 -0
- package/dist/server/consts.js.map +1 -0
- package/dist/server/context/context.model.d.ts +13 -0
- package/dist/server/context/context.model.js +3 -0
- package/dist/server/context/context.model.js.map +1 -0
- package/dist/server/context/request.context.d.ts +40 -0
- package/dist/server/context/request.context.js +65 -0
- package/dist/server/context/request.context.js.map +1 -0
- package/dist/server/context/trpc.context.d.ts +11 -0
- package/dist/server/context/trpc.context.js +67 -0
- package/dist/server/context/trpc.context.js.map +1 -0
- package/dist/server/initOpenApiDocs.d.ts +9 -0
- package/dist/server/initOpenApiDocs.js +18 -0
- package/dist/server/initOpenApiDocs.js.map +1 -0
- package/dist/server/middleware/cloudTaskDecrypt.middleware.d.ts +6 -0
- package/dist/server/middleware/cloudTaskDecrypt.middleware.js +44 -0
- package/dist/server/middleware/cloudTaskDecrypt.middleware.js.map +1 -0
- package/dist/server/middleware/error.middleware.d.ts +17 -0
- package/dist/server/middleware/error.middleware.js +66 -0
- package/dist/server/middleware/error.middleware.js.map +1 -0
- package/dist/server/middleware/handleRequest.middleware.d.ts +7 -0
- package/dist/server/middleware/handleRequest.middleware.js +40 -0
- package/dist/server/middleware/handleRequest.middleware.js.map +1 -0
- package/dist/server/middleware/logger/cloudRun.logger.d.ts +27 -0
- package/dist/server/middleware/logger/cloudRun.logger.js +87 -0
- package/dist/server/middleware/logger/cloudRun.logger.js.map +1 -0
- package/dist/server/middleware/logger/logger.service.d.ts +6 -0
- package/dist/server/middleware/logger/logger.service.js +17 -0
- package/dist/server/middleware/logger/logger.service.js.map +1 -0
- package/dist/server/middleware/logs.middleware.d.ts +7 -0
- package/dist/server/middleware/logs.middleware.js +130 -0
- package/dist/server/middleware/logs.middleware.js.map +1 -0
- package/dist/server/middleware/requireAuthenticated.d.ts +2 -0
- package/dist/server/middleware/requireAuthenticated.js +13 -0
- package/dist/server/middleware/requireAuthenticated.js.map +1 -0
- package/dist/server/middleware/trpcError.middleware.d.ts +4 -0
- package/dist/server/middleware/trpcError.middleware.js +38 -0
- package/dist/server/middleware/trpcError.middleware.js.map +1 -0
- package/dist/server/schemas/user.schema.d.ts +109 -0
- package/dist/server/schemas/user.schema.js +28 -0
- package/dist/server/schemas/user.schema.js.map +1 -0
- package/dist/server/sentry/getSentry.d.ts +6 -0
- package/dist/server/sentry/getSentry.js +45 -0
- package/dist/server/sentry/getSentry.js.map +1 -0
- package/dist/server/sentry/sentry.service.d.ts +34 -0
- package/dist/server/sentry/sentry.service.js +110 -0
- package/dist/server/sentry/sentry.service.js.map +1 -0
- package/dist/server/services/email/email.model.d.ts +84 -0
- package/dist/server/services/email/email.model.js +62 -0
- package/dist/server/services/email/email.model.js.map +1 -0
- package/dist/server/services/email/email.service.d.ts +23 -0
- package/dist/server/services/email/email.service.js +139 -0
- package/dist/server/services/email/email.service.js.map +1 -0
- package/dist/server/services/gcp/getGcpServiceAccountFromBase64.d.ts +15 -0
- package/dist/server/services/gcp/getGcpServiceAccountFromBase64.js +9 -0
- package/dist/server/services/gcp/getGcpServiceAccountFromBase64.js.map +1 -0
- package/dist/server/services/secrets/secret.service.d.ts +31 -0
- package/dist/server/services/secrets/secret.service.js +172 -0
- package/dist/server/services/secrets/secret.service.js.map +1 -0
- package/dist/server/services/sendgrid/sendgrid.model.d.ts +118 -0
- package/dist/server/services/sendgrid/sendgrid.model.js +3 -0
- package/dist/server/services/sendgrid/sendgrid.model.js.map +1 -0
- package/dist/server/services/sendgrid/sendgridApi.service.d.ts +13 -0
- package/dist/server/services/sendgrid/sendgridApi.service.js +79 -0
- package/dist/server/services/sendgrid/sendgridApi.service.js.map +1 -0
- package/dist/server/services/sendgrid/sendgridHooks.model.d.ts +27 -0
- package/dist/server/services/sendgrid/sendgridHooks.model.js +19 -0
- package/dist/server/services/sendgrid/sendgridHooks.model.js.map +1 -0
- package/dist/server/services/translations/template.util.d.ts +7 -0
- package/dist/server/services/translations/template.util.js +11 -0
- package/dist/server/services/translations/template.util.js.map +1 -0
- package/dist/server/services/translations/translation.model.d.ts +4 -0
- package/dist/server/services/translations/translation.model.js +6 -0
- package/dist/server/services/translations/translation.model.js.map +1 -0
- package/dist/server/services/translations/translation.service.d.ts +25 -0
- package/dist/server/services/translations/translation.service.js +97 -0
- package/dist/server/services/translations/translation.service.js.map +1 -0
- package/dist/server/services/util/benchmarker.d.ts +13 -0
- package/dist/server/services/util/benchmarker.js +34 -0
- package/dist/server/services/util/benchmarker.js.map +1 -0
- package/dist/server/services/util/pagination.d.ts +50 -0
- package/dist/server/services/util/pagination.js +57 -0
- package/dist/server/services/util/pagination.js.map +1 -0
- package/dist/server/services/util/url.service.d.ts +75 -0
- package/dist/server/services/util/url.service.js +139 -0
- package/dist/server/services/util/url.service.js.map +1 -0
- package/dist/server/test/express.mock.d.ts +6 -0
- package/dist/server/test/express.mock.js +49 -0
- package/dist/server/test/express.mock.js.map +1 -0
- package/dist/server/test/firebase.mock.d.ts +4 -0
- package/dist/server/test/firebase.mock.js +30 -0
- package/dist/server/test/firebase.mock.js.map +1 -0
- package/dist/server/test/mock.model.d.ts +5 -0
- package/dist/server/test/mock.model.js +3 -0
- package/dist/server/test/mock.model.js.map +1 -0
- package/dist/server/test/trpc.mock.d.ts +6 -0
- package/dist/server/test/trpc.mock.js +14 -0
- package/dist/server/test/trpc.mock.js.map +1 -0
- package/dist/server/trpc.d.ts +364 -0
- package/dist/server/trpc.js +87 -0
- package/dist/server/trpc.js.map +1 -0
- package/dist/server/types/Envinronment.d.ts +1 -0
- package/dist/server/types/Envinronment.js +3 -0
- package/dist/server/types/Envinronment.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +33 -3
package/README.md
CHANGED
|
@@ -1,45 +1,61 @@
|
|
|
1
|
-
|
|
1
|
+
# @goatlab/node-backend
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[![Issues][issues-shield]][issues-url]
|
|
5
|
-
[![MIT License][license-shield]][license-url]
|
|
6
|
-
[](http://commitizen.github.io/cz-cli/)
|
|
3
|
+
A flexible caching solution for Node.js applications that supports both Redis and in-memory LRU caching with multi-tenancy support.
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
<br />
|
|
10
|
-
<p align="center">
|
|
11
|
-
<a href="https://github.com/github_username/repo">
|
|
12
|
-
<img src="https://docs.goatlab.io/logo.png" alt="Logo" width="150" height="150">
|
|
13
|
-
</a>
|
|
14
|
-
|
|
15
|
-
<h3 align="center">GOAT - QUEUE</h3>
|
|
16
|
-
|
|
17
|
-
<p align="center">
|
|
18
|
-
Fluent - Time Saving (TS) utils
|
|
19
|
-
<br />
|
|
20
|
-
<a href="https://docs.goatlab.io/#/0.7.x/fluent/fluent"><strong>Explore the docs »</strong></a>
|
|
21
|
-
<br />
|
|
22
|
-
<br />
|
|
23
|
-
·
|
|
24
|
-
<a href="https://github.com/goat-io/fluent/issues">Report Bug</a>
|
|
25
|
-
·
|
|
26
|
-
<a href="https://github.com/goat-io/fluent/issues">Request Feature</a>
|
|
27
|
-
</p>
|
|
28
|
-
</p>
|
|
29
|
-
</p>
|
|
30
|
-
|
|
31
|
-
# Goat - JS UTILS
|
|
32
|
-
|
|
33
|
-
Standard queue interfaces for different providers
|
|
34
|
-
|
|
35
|
-
### Installing
|
|
36
|
-
|
|
37
|
-
To install this package in your project, you can use the following command within your terminal.
|
|
5
|
+
## Installation
|
|
38
6
|
|
|
39
7
|
```bash
|
|
40
|
-
|
|
8
|
+
npm install @goatlab/node-backend
|
|
9
|
+
# or
|
|
10
|
+
yarn add @goatlab/node-backend
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @goatlab/node-backend
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Basic Usage
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { Cache } from '@goatlab/node-backend'
|
|
19
|
+
|
|
20
|
+
// Use Redis cache
|
|
21
|
+
const redisCache = new Cache({
|
|
22
|
+
connection: 'redis://localhost:6379',
|
|
23
|
+
opts: { namespace: 'my-app' }
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// Use in-memory LRU cache
|
|
27
|
+
const memoryCache = new Cache({
|
|
28
|
+
connection: undefined,
|
|
29
|
+
opts: { namespace: 'my-app' }
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// Cache with LRU memory layer for improved performance
|
|
33
|
+
const hybridCache = new Cache({
|
|
34
|
+
connection: 'redis://localhost:6379',
|
|
35
|
+
opts: {
|
|
36
|
+
namespace: 'my-app',
|
|
37
|
+
usesLRUMemory: true // Adds LRU memory caching layer
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Basic operations
|
|
42
|
+
await cache.set('key', { data: 'value' }, 60000) // TTL in milliseconds
|
|
43
|
+
const value = await cache.get('key')
|
|
44
|
+
await cache.delete('key')
|
|
45
|
+
|
|
46
|
+
// Advanced operations
|
|
47
|
+
const result = await cache.remember('expensive-op', 300000, async () => {
|
|
48
|
+
// This function only runs if key doesn't exist
|
|
49
|
+
return await expensiveOperation()
|
|
50
|
+
})
|
|
41
51
|
```
|
|
42
52
|
|
|
43
|
-
|
|
53
|
+
## Key Features
|
|
44
54
|
|
|
45
|
-
|
|
55
|
+
- **Dual Storage**: Supports Redis for distributed caching or in-memory LRU for single instances
|
|
56
|
+
- **Multi-tenancy**: Built-in tenant isolation with namespace support
|
|
57
|
+
- **Memory Layer**: Optional LRU memory caching on top of Redis for improved performance
|
|
58
|
+
- **Cache Helpers**: Laravel-inspired helper methods like `remember()`, `rememberForever()`, and `pull()`
|
|
59
|
+
- **Namespace Operations**: Delete or retrieve values by key prefix with `deleteWhereStartsWith()` and `getValueWhereKeyStartsWith()`
|
|
60
|
+
- **Type Safety**: Full TypeScript support with generic types
|
|
61
|
+
- **Automatic Validation**: Skips caching of null, undefined, empty strings, empty arrays, and empty objects
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { ContainerOptions, InstancesStructure, PreloadStructure } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
4
|
+
* 🏗️ MULTI-TENANT DEPENDENCY INJECTION CONTAINER
|
|
5
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
6
|
+
*
|
|
7
|
+
* This Container provides a dependency injection system designed
|
|
8
|
+
* for multi-tenant applications. Each tenant gets their own isolated service
|
|
9
|
+
* instances while sharing the same factory definitions.
|
|
10
|
+
*
|
|
11
|
+
* Key Features:
|
|
12
|
+
* • 🔄 Tenant-isolated service instances using AsyncLocalStorage
|
|
13
|
+
* • ⚡ High-performance caching with LRU eviction
|
|
14
|
+
* • 🪞 Intelligent proxy system for lazy loading and error handling
|
|
15
|
+
* • 📊 Built-in performance metrics and debugging tools
|
|
16
|
+
* • 🛡️ Type-safe service resolution with full TypeScript support
|
|
17
|
+
* • 🔧 Support for both class constructors and factory functions
|
|
18
|
+
*
|
|
19
|
+
* Architecture Overview:
|
|
20
|
+
* ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
21
|
+
* │ Factories │ -> │ Container │ -> │ Instances │
|
|
22
|
+
* │ (Shared Defs) │ │ (Per-tenant) │ │ (Per-tenant) │
|
|
23
|
+
* └─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
24
|
+
*
|
|
25
|
+
* Flow:
|
|
26
|
+
* 1. Define factories once (shared across all tenants)
|
|
27
|
+
* 2. Bootstrap container with tenant metadata
|
|
28
|
+
* 3. Services are lazy-loaded and cached per tenant
|
|
29
|
+
* 4. AsyncLocalStorage provides automatic context isolation
|
|
30
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* 🏗️ Multi-Tenant Dependency Injection Container
|
|
34
|
+
*
|
|
35
|
+
* The Container is the heart of the multi-tenant service architecture. It manages
|
|
36
|
+
* service instantiation, caching, and tenant isolation using AsyncLocalStorage.
|
|
37
|
+
*
|
|
38
|
+
* @template Defs - Factory definitions record (shared across tenants)
|
|
39
|
+
* @template TenantMetadata - Type of tenant-specific metadata (DB config, secrets, etc.)
|
|
40
|
+
*
|
|
41
|
+
* Key Responsibilities:
|
|
42
|
+
* 1. 🏭 **Factory Management**: Register and cache service factories
|
|
43
|
+
* 2. 🔄 **Tenant Isolation**: Each tenant gets isolated service instances
|
|
44
|
+
* 3. ⚡ **Performance**: Multi-level caching for optimal performance
|
|
45
|
+
* 4. 🪞 **Lazy Loading**: Services instantiated only when accessed
|
|
46
|
+
* 5. 🛡️ **Error Handling**: Clear error messages for missing services
|
|
47
|
+
* 6. 📊 **Observability**: Performance metrics and debugging tools
|
|
48
|
+
*
|
|
49
|
+
* Usage Pattern:
|
|
50
|
+
* ```typescript
|
|
51
|
+
* // 1. Define your services
|
|
52
|
+
* const factories = {
|
|
53
|
+
* database: DatabaseService,
|
|
54
|
+
* api: {
|
|
55
|
+
* users: UserApiService,
|
|
56
|
+
* auth: AuthApiService
|
|
57
|
+
* }
|
|
58
|
+
* }
|
|
59
|
+
*
|
|
60
|
+
* // 2. Create container with initializer
|
|
61
|
+
* const container = new Container(factories, async (preload, meta) => {
|
|
62
|
+
* const db = preload.database('main', meta.connectionString)
|
|
63
|
+
* return {
|
|
64
|
+
* database: db,
|
|
65
|
+
* api: {
|
|
66
|
+
* users: preload.api.users('users', db),
|
|
67
|
+
* auth: preload.api.auth('auth', db, meta.jwtSecret)
|
|
68
|
+
* }
|
|
69
|
+
* }
|
|
70
|
+
* })
|
|
71
|
+
*
|
|
72
|
+
* // 3. Bootstrap for a tenant and run code
|
|
73
|
+
* await container.bootstrap(tenantMeta, async () => {
|
|
74
|
+
* const { database, api } = container.context
|
|
75
|
+
* const users = await api.users.getAll()
|
|
76
|
+
* return users
|
|
77
|
+
* })
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export declare class Container<Defs extends Record<string, unknown>, TenantMetadata> {
|
|
81
|
+
private readonly factories;
|
|
82
|
+
private readonly initializer;
|
|
83
|
+
/**
|
|
84
|
+
* Service instance cache managers - one per service type
|
|
85
|
+
* Each manager handles LRU caching for that specific service
|
|
86
|
+
*/
|
|
87
|
+
private readonly managers;
|
|
88
|
+
/**
|
|
89
|
+
* AsyncLocalStorage provides automatic tenant context isolation
|
|
90
|
+
* Each async call tree gets its own isolated service instances
|
|
91
|
+
* Also stores tenant metadata for introspection
|
|
92
|
+
*/
|
|
93
|
+
private readonly als;
|
|
94
|
+
/**
|
|
95
|
+
* Pre-resolved factory lookup cache for performance
|
|
96
|
+
* Avoids recursive object traversal on every service access
|
|
97
|
+
*/
|
|
98
|
+
private readonly factoryCache;
|
|
99
|
+
/**
|
|
100
|
+
* Cached preload proxy to avoid recreating the same proxy structure
|
|
101
|
+
*/
|
|
102
|
+
private preloadProxy;
|
|
103
|
+
/**
|
|
104
|
+
* Container configuration with sensible defaults
|
|
105
|
+
*/
|
|
106
|
+
private readonly options;
|
|
107
|
+
/**
|
|
108
|
+
* Path string cache: converts ['user', 'repo'] -> "user.repo"
|
|
109
|
+
* Avoids repeated string joining operations
|
|
110
|
+
*/
|
|
111
|
+
private readonly pathCache;
|
|
112
|
+
/**
|
|
113
|
+
* Proxy object cache: reuses proxy objects for the same paths
|
|
114
|
+
* Reduces memory allocation and improves performance
|
|
115
|
+
*/
|
|
116
|
+
private readonly proxyCache;
|
|
117
|
+
/**
|
|
118
|
+
* Context proxy cache: reuses proxies for the same object instances
|
|
119
|
+
* Uses WeakMap for automatic garbage collection when objects are freed
|
|
120
|
+
*/
|
|
121
|
+
private readonly contextProxyCache;
|
|
122
|
+
/**
|
|
123
|
+
* Initializer cache: stores initialized instances per tenant
|
|
124
|
+
* Avoids re-running the expensive initializer function for the same tenant
|
|
125
|
+
* Key is a serialized version of tenant metadata, value is the initialized instances
|
|
126
|
+
*/
|
|
127
|
+
private readonly initializerCache;
|
|
128
|
+
/**
|
|
129
|
+
* Performance metrics for monitoring and optimization
|
|
130
|
+
* Only collected when enableMetrics is true
|
|
131
|
+
* Includes overflow protection for long-running services
|
|
132
|
+
*/
|
|
133
|
+
private metrics;
|
|
134
|
+
/**
|
|
135
|
+
* Maximum safe value for metrics before overflow protection kicks in
|
|
136
|
+
* Set to 90% of MAX_SAFE_INTEGER to provide buffer
|
|
137
|
+
*/
|
|
138
|
+
private readonly MAX_METRIC_VALUE;
|
|
139
|
+
/**
|
|
140
|
+
* Safely increment a metric with overflow protection
|
|
141
|
+
* When approaching MAX_SAFE_INTEGER, resets all metrics to prevent overflow
|
|
142
|
+
*/
|
|
143
|
+
private safeIncrementMetric;
|
|
144
|
+
/**
|
|
145
|
+
* Create a new Container instance
|
|
146
|
+
*
|
|
147
|
+
* @param factories - Service factory definitions (shared across all tenants)
|
|
148
|
+
* @param initializer - Function that creates tenant-specific service instances
|
|
149
|
+
* @param options - Configuration options for performance and debugging
|
|
150
|
+
*
|
|
151
|
+
* The initializer function receives:
|
|
152
|
+
* - preload: Proxy object for creating service instances with parameters
|
|
153
|
+
* - meta: Tenant-specific metadata (DB config, secrets, etc.)
|
|
154
|
+
*
|
|
155
|
+
* And should return a structure matching your factory definitions but with
|
|
156
|
+
* actual service instances instead of factory functions.
|
|
157
|
+
*/
|
|
158
|
+
constructor(factories: Defs, initializer: (preload: PreloadStructure<Defs>, meta: TenantMetadata) => Promise<Partial<InstancesStructure<Defs>>>, options?: ContainerOptions);
|
|
159
|
+
/**
|
|
160
|
+
* Recursively create cache managers for all services in the factory tree
|
|
161
|
+
* Each service gets its own LRU cache with the configured size limit
|
|
162
|
+
*/
|
|
163
|
+
private createManagers;
|
|
164
|
+
/**
|
|
165
|
+
* Efficiently cache path string conversions
|
|
166
|
+
* Converts ['user', 'service'] to "user.service" and caches the result
|
|
167
|
+
*
|
|
168
|
+
* Uses different separators for cache key vs final path to avoid conflicts
|
|
169
|
+
*/
|
|
170
|
+
private getOrCachePath;
|
|
171
|
+
/**
|
|
172
|
+
* Pre-populate the factory cache by walking the entire factory tree
|
|
173
|
+
* This eliminates the need for recursive object traversal during runtime
|
|
174
|
+
*/
|
|
175
|
+
private preloadFactoryCache;
|
|
176
|
+
/**
|
|
177
|
+
* Recursive factory tree walker that builds the flat factory cache
|
|
178
|
+
* Converts nested object structure to flat dot-notation keys
|
|
179
|
+
*/
|
|
180
|
+
private walkFactories;
|
|
181
|
+
/**
|
|
182
|
+
* Get the preload proxy for service instantiation
|
|
183
|
+
* The preload proxy allows you to create services with parameters:
|
|
184
|
+
*
|
|
185
|
+
* ```typescript
|
|
186
|
+
* const db = preload.database('main', connectionString)
|
|
187
|
+
* const userApi = preload.api.users('users', db, config)
|
|
188
|
+
* ```
|
|
189
|
+
*
|
|
190
|
+
* This is used during the initialization phase to wire up dependencies
|
|
191
|
+
*/
|
|
192
|
+
get preload(): PreloadStructure<Defs>;
|
|
193
|
+
/**
|
|
194
|
+
* Create a proxy that intercepts property access and provides factory functions
|
|
195
|
+
*
|
|
196
|
+
* The proxy works by:
|
|
197
|
+
* 1. Intercepting property access (e.g., preload.database)
|
|
198
|
+
* 2. Looking up the factory for that path
|
|
199
|
+
* 3. Returning a function that creates and caches instances
|
|
200
|
+
* 4. For nested paths, returning another proxy
|
|
201
|
+
*
|
|
202
|
+
* This enables natural dot-notation access while maintaining lazy loading
|
|
203
|
+
*/
|
|
204
|
+
private createPreloadProxy;
|
|
205
|
+
/**
|
|
206
|
+
* Run a function within a specific tenant context
|
|
207
|
+
* This is usually called internally by bootstrap, but can be used directly
|
|
208
|
+
* for testing or advanced use cases
|
|
209
|
+
*/
|
|
210
|
+
runWithContext<T>(instances: Partial<InstancesStructure<Defs>>, tenantMetadata: TenantMetadata, fn: () => Promise<T>): Promise<T>;
|
|
211
|
+
/**
|
|
212
|
+
* Get the current tenant's service context
|
|
213
|
+
*
|
|
214
|
+
* This is the main way to access services within a tenant context:
|
|
215
|
+
* ```typescript
|
|
216
|
+
* const { database, api } = container.context
|
|
217
|
+
* const users = await api.users.getAll()
|
|
218
|
+
* ```
|
|
219
|
+
*
|
|
220
|
+
* Throws an error if called outside of a tenant context
|
|
221
|
+
*/
|
|
222
|
+
get context(): InstancesStructure<Defs>;
|
|
223
|
+
/**
|
|
224
|
+
* Create a proxy for the runtime context that provides intelligent error handling
|
|
225
|
+
*
|
|
226
|
+
* The context proxy:
|
|
227
|
+
* 1. Provides helpful error messages when services are missing
|
|
228
|
+
* 2. Handles nested object access gracefully
|
|
229
|
+
* 3. Avoids wrapping Promises or arrays (which would break them)
|
|
230
|
+
* 4. Uses WeakMap caching for automatic memory management
|
|
231
|
+
*
|
|
232
|
+
* This ensures that accessing container.context.someService either works
|
|
233
|
+
* or gives you a clear error message about what's available
|
|
234
|
+
*/
|
|
235
|
+
private createContextProxy;
|
|
236
|
+
/**
|
|
237
|
+
* Create a stable cache key from tenant metadata
|
|
238
|
+
* Uses a deterministic approach focusing on tenant identity
|
|
239
|
+
*/
|
|
240
|
+
private createTenantCacheKey;
|
|
241
|
+
/**
|
|
242
|
+
* Simple hash function for cache keys
|
|
243
|
+
* Not cryptographically secure, but good enough for cache key generation
|
|
244
|
+
*/
|
|
245
|
+
private simpleHash;
|
|
246
|
+
/**
|
|
247
|
+
* Get or create initialized instances for a tenant
|
|
248
|
+
* Uses caching to avoid re-running the expensive initializer function
|
|
249
|
+
*/
|
|
250
|
+
private getOrCreateInstances;
|
|
251
|
+
/**
|
|
252
|
+
* Bootstrap the container for a specific tenant and execute a function
|
|
253
|
+
*
|
|
254
|
+
* This is the main entry point for tenant-specific operations:
|
|
255
|
+
*
|
|
256
|
+
* @param meta - Tenant-specific metadata (DB config, secrets, etc.)
|
|
257
|
+
* @param fn - Function to execute within the tenant context (optional)
|
|
258
|
+
* @returns Object containing the initialized instances and function result
|
|
259
|
+
*
|
|
260
|
+
* ```typescript
|
|
261
|
+
* // Example: Process a user request for tenant "acme"
|
|
262
|
+
* const result = await container.bootstrap(acmeTenantMeta, async () => {
|
|
263
|
+
* const { api } = container.context
|
|
264
|
+
* return await api.users.getById(userId)
|
|
265
|
+
* })
|
|
266
|
+
*
|
|
267
|
+
* console.log(result.instances) // All initialized services
|
|
268
|
+
* console.log(result.result) // Return value from the function
|
|
269
|
+
* ```
|
|
270
|
+
*
|
|
271
|
+
* The bootstrap process:
|
|
272
|
+
* 1. Gets or creates initialized services for this tenant (with caching)
|
|
273
|
+
* 2. Sets up AsyncLocalStorage context with the service instances
|
|
274
|
+
* 3. Executes your function within that context
|
|
275
|
+
* 4. Returns both the instances and your function's result
|
|
276
|
+
*/
|
|
277
|
+
bootstrap<T>(meta: TenantMetadata, fn?: () => Promise<T>): Promise<{
|
|
278
|
+
instances: InstancesStructure<Defs>;
|
|
279
|
+
result?: T;
|
|
280
|
+
}>;
|
|
281
|
+
/**
|
|
282
|
+
* Get current performance metrics
|
|
283
|
+
* Useful for monitoring cache effectiveness and performance tuning
|
|
284
|
+
*/
|
|
285
|
+
getMetrics(): {
|
|
286
|
+
cacheHits: number;
|
|
287
|
+
cacheMisses: number;
|
|
288
|
+
instanceCreations: number;
|
|
289
|
+
contextAccesses: number;
|
|
290
|
+
pathCacheHits: number;
|
|
291
|
+
proxyCacheHits: number;
|
|
292
|
+
initializerCacheHits: number;
|
|
293
|
+
};
|
|
294
|
+
/**
|
|
295
|
+
* Reset all performance metrics to zero
|
|
296
|
+
* Useful for benchmarking specific operations
|
|
297
|
+
*/
|
|
298
|
+
resetMetrics(): void;
|
|
299
|
+
/**
|
|
300
|
+
* Clear all service instance caches
|
|
301
|
+
* Forces fresh instantiation of all services on next access
|
|
302
|
+
* Useful for testing or when you need to ensure clean state
|
|
303
|
+
*/
|
|
304
|
+
clearCaches(): void;
|
|
305
|
+
/**
|
|
306
|
+
* Setup distributed cache invalidation system
|
|
307
|
+
* Connects to Redis pub/sub for coordinating cache invalidation across instances
|
|
308
|
+
*/
|
|
309
|
+
private setupDistributedInvalidation;
|
|
310
|
+
/**
|
|
311
|
+
* Invalidate all cached data for a specific tenant across all instances
|
|
312
|
+
* This sends a distributed invalidation message via Redis pub/sub
|
|
313
|
+
*/
|
|
314
|
+
invalidateTenantDistributed(tenantId: string, reason?: string): Promise<void>;
|
|
315
|
+
/**
|
|
316
|
+
* Invalidate all cached data for a specific service type across all instances
|
|
317
|
+
*/
|
|
318
|
+
invalidateServiceDistributed(serviceType: string, reason?: string): Promise<void>;
|
|
319
|
+
/**
|
|
320
|
+
* Invalidate all cached data across all instances
|
|
321
|
+
*/
|
|
322
|
+
invalidateAllDistributed(reason?: string): Promise<void>;
|
|
323
|
+
/**
|
|
324
|
+
* Invalidate cached data for a specific tenant (local only)
|
|
325
|
+
* This only affects the current instance
|
|
326
|
+
*/
|
|
327
|
+
private invalidateTenantLocally;
|
|
328
|
+
/**
|
|
329
|
+
* Invalidate cached data for a specific service type (local only)
|
|
330
|
+
*/
|
|
331
|
+
private invalidateServiceLocally;
|
|
332
|
+
/**
|
|
333
|
+
* Invalidate all cached data (local only)
|
|
334
|
+
*/
|
|
335
|
+
private invalidateAllLocally;
|
|
336
|
+
/**
|
|
337
|
+
* Get detailed cache statistics for each service
|
|
338
|
+
* Shows how many instances are cached and the cache limits
|
|
339
|
+
*/
|
|
340
|
+
getCacheStats(): Record<string, {
|
|
341
|
+
size: number;
|
|
342
|
+
maxSize?: number;
|
|
343
|
+
}>;
|
|
344
|
+
/**
|
|
345
|
+
* Get comprehensive performance statistics
|
|
346
|
+
* Combines metrics, cache stats, and computed ratios for full observability
|
|
347
|
+
*/
|
|
348
|
+
getPerformanceStats(): {
|
|
349
|
+
cacheStats: Record<string, {
|
|
350
|
+
size: number;
|
|
351
|
+
maxSize?: number;
|
|
352
|
+
}>;
|
|
353
|
+
totalCacheSize: number;
|
|
354
|
+
pathCacheSize: number;
|
|
355
|
+
proxyCacheSize: number;
|
|
356
|
+
factoryCacheSize: number;
|
|
357
|
+
initializerCacheSize: number;
|
|
358
|
+
cacheHitRatio: number;
|
|
359
|
+
cacheHits: number;
|
|
360
|
+
cacheMisses: number;
|
|
361
|
+
instanceCreations: number;
|
|
362
|
+
contextAccesses: number;
|
|
363
|
+
pathCacheHits: number;
|
|
364
|
+
proxyCacheHits: number;
|
|
365
|
+
initializerCacheHits: number;
|
|
366
|
+
};
|
|
367
|
+
/**
|
|
368
|
+
* Check if there's an active tenant context
|
|
369
|
+
*
|
|
370
|
+
* @returns true if called within a tenant context, false otherwise
|
|
371
|
+
*
|
|
372
|
+
* ```typescript
|
|
373
|
+
* if (container.hasActiveContext()) {
|
|
374
|
+
* const services = container.context
|
|
375
|
+
* // Safe to access services
|
|
376
|
+
* }
|
|
377
|
+
* ```
|
|
378
|
+
*/
|
|
379
|
+
hasActiveContext(): boolean;
|
|
380
|
+
/**
|
|
381
|
+
* Check if a service is available in the current tenant context
|
|
382
|
+
*
|
|
383
|
+
* @param servicePath - Dot-notation path to the service (e.g., "api.users")
|
|
384
|
+
* @returns true if the service exists and is initialized, false otherwise
|
|
385
|
+
*
|
|
386
|
+
* ```typescript
|
|
387
|
+
* if (container.hasService('api.users')) {
|
|
388
|
+
* const users = container.context.api.users
|
|
389
|
+
* // Safe to use users service
|
|
390
|
+
* }
|
|
391
|
+
* ```
|
|
392
|
+
*/
|
|
393
|
+
hasService(servicePath: string): boolean;
|
|
394
|
+
/**
|
|
395
|
+
* Get the current tenant's metadata
|
|
396
|
+
*
|
|
397
|
+
* This allows access to tenant-specific configuration, credentials, and other
|
|
398
|
+
* metadata that was passed to the bootstrap method:
|
|
399
|
+
*
|
|
400
|
+
* ```typescript
|
|
401
|
+
* await container.bootstrap(tenantMeta, async () => {
|
|
402
|
+
* const meta = container.getCurrentTenantMetadata()
|
|
403
|
+
* console.log('Current tenant:', meta.id)
|
|
404
|
+
* console.log('DB URL:', meta.connectionString)
|
|
405
|
+
* })
|
|
406
|
+
* ```
|
|
407
|
+
*
|
|
408
|
+
* @returns The tenant metadata that was passed to bootstrap
|
|
409
|
+
* @throws Error if called outside of a tenant context
|
|
410
|
+
*/
|
|
411
|
+
getCurrentTenantMetadata(): TenantMetadata;
|
|
412
|
+
/**
|
|
413
|
+
* Get the current tenant ID from metadata
|
|
414
|
+
*
|
|
415
|
+
* This is a convenience method that extracts the tenant ID from the metadata.
|
|
416
|
+
* It assumes the metadata has an 'id' property (common pattern).
|
|
417
|
+
*
|
|
418
|
+
* ```typescript
|
|
419
|
+
* await container.bootstrap(tenantMeta, async () => {
|
|
420
|
+
* const tenantId = container.getCurrentTenantId()
|
|
421
|
+
* console.log('Processing request for tenant:', tenantId)
|
|
422
|
+
* })
|
|
423
|
+
* ```
|
|
424
|
+
*
|
|
425
|
+
* @returns The tenant ID if metadata has an 'id' property, undefined otherwise
|
|
426
|
+
* @throws Error if called outside of a tenant context
|
|
427
|
+
*/
|
|
428
|
+
getCurrentTenantId(): string | undefined;
|
|
429
|
+
/**
|
|
430
|
+
* Get a list of all available services in the current tenant context
|
|
431
|
+
* Useful for debugging, testing, or dynamic service discovery
|
|
432
|
+
*
|
|
433
|
+
* @returns Array of dot-notation service paths (e.g., ["database", "api.users", "api.auth"])
|
|
434
|
+
*/
|
|
435
|
+
getAvailableServices(): string[];
|
|
436
|
+
/**
|
|
437
|
+
* Recursively collect all service paths from the current context
|
|
438
|
+
* Helper method for getAvailableServices()
|
|
439
|
+
*/
|
|
440
|
+
private collectServices;
|
|
441
|
+
}
|