@plyaz/core 1.2.1 → 1.3.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 +248 -183
- package/dist/domain/example/FrontendExampleDomainService.d.ts.map +1 -1
- package/dist/domain/featureFlags/providers/database.d.ts +6 -1
- package/dist/domain/featureFlags/providers/database.d.ts.map +1 -1
- package/dist/entry-backend.d.ts +3 -1
- package/dist/entry-backend.d.ts.map +1 -1
- package/dist/entry-backend.js +3145 -3325
- package/dist/entry-backend.js.map +1 -1
- package/dist/entry-backend.mjs +2732 -2899
- package/dist/entry-backend.mjs.map +1 -1
- package/dist/entry-frontend.js +1594 -1406
- package/dist/entry-frontend.js.map +1 -1
- package/dist/entry-frontend.mjs +1568 -1379
- package/dist/entry-frontend.mjs.map +1 -1
- package/dist/index.js +3130 -3318
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3133 -3308
- package/dist/index.mjs.map +1 -1
- package/dist/init/CoreInitializer.d.ts +9 -8
- package/dist/init/CoreInitializer.d.ts.map +1 -1
- package/dist/init/ServiceRegistry.d.ts.map +1 -1
- package/dist/init/nestjs/index.js +1511 -1336
- package/dist/init/nestjs/index.js.map +1 -1
- package/dist/init/nestjs/index.mjs +1527 -1352
- package/dist/init/nestjs/index.mjs.map +1 -1
- package/dist/models/example/ExampleRepository.d.ts +45 -3
- package/dist/models/example/ExampleRepository.d.ts.map +1 -1
- package/dist/models/featureFlags/FeatureFlagRepository.d.ts +72 -471
- package/dist/models/featureFlags/FeatureFlagRepository.d.ts.map +1 -1
- package/dist/services/DbService.d.ts +2 -0
- package/dist/services/DbService.d.ts.map +1 -1
- package/dist/services/NotificationService.d.ts +2 -0
- package/dist/services/NotificationService.d.ts.map +1 -1
- package/dist/services/StorageService.d.ts +2 -0
- package/dist/services/StorageService.d.ts.map +1 -1
- package/dist/utils/common/id.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/entry-frontend.mjs
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { CoreLogger, PackageLogger } from '@plyaz/logger';
|
|
2
|
-
import { createDatabaseService, BaseRepository } from '@plyaz/db';
|
|
3
|
-
import { DatabasePackageError, generateRequestId, CorePackageError, StoragePackageError, NotificationPackageError, BaseError, ValidateAndFormatErrors, ValidationError, initializeErrorSystem } from '@plyaz/errors';
|
|
4
|
-
import { DATABASE_ERROR_CODES, ERROR_CODES as ERROR_CODES$1, STORAGE_ERROR_CODES, NOTIFICATION_ERROR_CODES, API_ERROR_CODES as API_ERROR_CODES$1 } from '@plyaz/types/errors';
|
|
5
|
-
import { CORE_EVENTS, SERVICE_KEYS, FEATURE_FLAG_TABLE } from '@plyaz/types/core';
|
|
6
|
-
import { EventEmitter } from 'events';
|
|
7
1
|
import { CACHE_MAX_SIZE_DEFAULT, CACHE_CLEANUP_INTERVAL_DEFAULT, TIME_CONSTANTS, HTTP_STATUS as HTTP_STATUS$1, DEVELOPMENT_CONFIG, STAGING_CONFIG, PRODUCTION_CONFIG, MATH_CONSTANTS, ISO_STANDARDS, FNV_CONSTANTS } from '@plyaz/config';
|
|
8
|
-
import { HTTP_STATUS, ERROR_CATEGORY, ERROR_CODES, CORE_EVENTS
|
|
2
|
+
import { HTTP_STATUS, ERROR_CATEGORY, ERROR_CODES, CORE_EVENTS, PACKAGE_STATUS_CODES, API_ERROR_CODES, OPERATIONS, BACKEND_RUNTIMES, FRONTEND_RUNTIMES } from '@plyaz/types';
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
import { BaseRepository, createDatabaseService } from '@plyaz/db';
|
|
5
|
+
import { generateRequestId, CorePackageError, BaseError, ValidateAndFormatErrors, ValidationError, DatabasePackageError, initializeErrorSystem, StoragePackageError, NotificationPackageError } from '@plyaz/errors';
|
|
6
|
+
import { ERROR_CODES as ERROR_CODES$1, API_ERROR_CODES as API_ERROR_CODES$1, DATABASE_ERROR_CODES, STORAGE_ERROR_CODES, NOTIFICATION_ERROR_CODES } from '@plyaz/types/errors';
|
|
7
|
+
import { SERVICE_KEYS, CORE_EVENTS as CORE_EVENTS$1, FEATURE_FLAG_TABLE } from '@plyaz/types/core';
|
|
8
|
+
import { StorageService as StorageService$1 } from '@plyaz/storage';
|
|
9
|
+
import { NotificationService as NotificationService$1 } from '@plyaz/notifications';
|
|
10
|
+
import { CoreLogger, PackageLogger } from '@plyaz/logger';
|
|
9
11
|
import { mergeConfigs, createApiClient, ApiPackageError, setDefaultApiClient, evaluateAllFeatureFlags, createFeatureFlag, updateFeatureFlag, deleteFeatureFlag, fetchFeatureFlagRules, ApiProvider as ApiProvider$1 } from '@plyaz/api';
|
|
10
12
|
import { Injectable } from '@nestjs/common';
|
|
11
13
|
import { of, tap } from 'rxjs';
|
|
12
14
|
import { CACHE_STRATEGIES } from '@plyaz/types/features';
|
|
13
|
-
import { StorageService as StorageService$1 } from '@plyaz/storage';
|
|
14
|
-
import { NotificationService as NotificationService$1 } from '@plyaz/notifications';
|
|
15
15
|
import { STORE_KEYS, useRootStore, createStandaloneFeatureFlagStore } from '@plyaz/store';
|
|
16
16
|
import { OBSERVABILITY_METRICS } from '@plyaz/types/observability';
|
|
17
17
|
import { QueryExampleSchema, DeleteExampleSchema, PatchExampleSchema, UpdateExampleSchema, CreateExampleSchema } from '@plyaz/types/examples';
|
|
@@ -28,7 +28,15 @@ import { createEncryptionConfig } from '@plyaz/config/api';
|
|
|
28
28
|
// @plyaz package - Built with tsup
|
|
29
29
|
var __defProp = Object.defineProperty;
|
|
30
30
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
31
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
31
32
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
33
|
+
var __esm = (fn, res) => function __init() {
|
|
34
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
35
|
+
};
|
|
36
|
+
var __export = (target, all) => {
|
|
37
|
+
for (var name in all)
|
|
38
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
39
|
+
};
|
|
32
40
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
33
41
|
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
34
42
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
@@ -45,29 +53,26 @@ function hashString(str) {
|
|
|
45
53
|
}
|
|
46
54
|
return hash >>> 0;
|
|
47
55
|
}
|
|
48
|
-
__name(hashString, "hashString");
|
|
49
56
|
function isInRollout(identifier, percentage) {
|
|
50
57
|
if (percentage >= MATH_CONSTANTS.PERCENTAGE_MAX) return true;
|
|
51
58
|
if (percentage <= 0) return false;
|
|
52
59
|
const hash = hashString(identifier);
|
|
53
60
|
return hash % MATH_CONSTANTS.PERCENTAGE_MAX < percentage;
|
|
54
61
|
}
|
|
55
|
-
__name(isInRollout, "isInRollout");
|
|
56
62
|
function createRolloutIdentifier(featureKey, userId) {
|
|
57
63
|
const trimmedUserId = userId?.trim();
|
|
58
64
|
const effectiveUserId = trimmedUserId && trimmedUserId.length > 0 ? trimmedUserId : "anonymous";
|
|
59
65
|
return `${featureKey}:${effectiveUserId}`;
|
|
60
66
|
}
|
|
61
|
-
|
|
67
|
+
var init_hash = __esm({
|
|
68
|
+
"src/utils/common/hash.ts"() {
|
|
69
|
+
__name(hashString, "hashString");
|
|
70
|
+
__name(isInRollout, "isInRollout");
|
|
71
|
+
__name(createRolloutIdentifier, "createRolloutIdentifier");
|
|
72
|
+
}
|
|
73
|
+
});
|
|
62
74
|
|
|
63
75
|
// src/utils/common/id.ts
|
|
64
|
-
var RANDOM_ID_RADIX = 36;
|
|
65
|
-
var RANDOM_ID_SLICE_START = 2;
|
|
66
|
-
var SHORT_ID_LENGTH = 8;
|
|
67
|
-
var HEX_RADIX = 16;
|
|
68
|
-
var HEX_SLICE_START = 2;
|
|
69
|
-
var HEX_SLICE_END = 18;
|
|
70
|
-
var SPAN_ID_LENGTH = 16;
|
|
71
76
|
function generateId() {
|
|
72
77
|
const cryptoApi = globalThis.crypto;
|
|
73
78
|
if (cryptoApi?.randomUUID) {
|
|
@@ -75,7 +80,6 @@ function generateId() {
|
|
|
75
80
|
}
|
|
76
81
|
return `${Date.now()}-${Math.random().toString(RANDOM_ID_RADIX).slice(RANDOM_ID_SLICE_START)}`;
|
|
77
82
|
}
|
|
78
|
-
__name(generateId, "generateId");
|
|
79
83
|
function generateShortId() {
|
|
80
84
|
const cryptoApi = globalThis.crypto;
|
|
81
85
|
if (cryptoApi?.randomUUID) {
|
|
@@ -83,11 +87,9 @@ function generateShortId() {
|
|
|
83
87
|
}
|
|
84
88
|
return Math.random().toString(RANDOM_ID_RADIX).slice(RANDOM_ID_SLICE_START, RANDOM_ID_SLICE_START + SHORT_ID_LENGTH);
|
|
85
89
|
}
|
|
86
|
-
__name(generateShortId, "generateShortId");
|
|
87
90
|
function generateCorrelationId() {
|
|
88
91
|
return `${Date.now()}-${generateShortId()}`;
|
|
89
92
|
}
|
|
90
|
-
__name(generateCorrelationId, "generateCorrelationId");
|
|
91
93
|
function generateTraceId() {
|
|
92
94
|
const cryptoApi = globalThis.crypto;
|
|
93
95
|
if (cryptoApi?.randomUUID) {
|
|
@@ -95,7 +97,6 @@ function generateTraceId() {
|
|
|
95
97
|
}
|
|
96
98
|
return Math.random().toString(HEX_RADIX).substring(HEX_SLICE_START, HEX_SLICE_END) + Math.random().toString(HEX_RADIX).substring(HEX_SLICE_START, HEX_SLICE_END);
|
|
97
99
|
}
|
|
98
|
-
__name(generateTraceId, "generateTraceId");
|
|
99
100
|
function generateSpanId() {
|
|
100
101
|
const cryptoApi = globalThis.crypto;
|
|
101
102
|
if (cryptoApi?.randomUUID) {
|
|
@@ -103,20 +104,34 @@ function generateSpanId() {
|
|
|
103
104
|
}
|
|
104
105
|
return Math.random().toString(HEX_RADIX).substring(HEX_SLICE_START, HEX_SLICE_END);
|
|
105
106
|
}
|
|
106
|
-
|
|
107
|
+
var RANDOM_ID_RADIX, RANDOM_ID_SLICE_START, SHORT_ID_LENGTH, HEX_RADIX, HEX_SLICE_START, HEX_SLICE_END, SPAN_ID_LENGTH;
|
|
108
|
+
var init_id = __esm({
|
|
109
|
+
"src/utils/common/id.ts"() {
|
|
110
|
+
RANDOM_ID_RADIX = 36;
|
|
111
|
+
RANDOM_ID_SLICE_START = 2;
|
|
112
|
+
SHORT_ID_LENGTH = 8;
|
|
113
|
+
HEX_RADIX = 16;
|
|
114
|
+
HEX_SLICE_START = 2;
|
|
115
|
+
HEX_SLICE_END = 18;
|
|
116
|
+
SPAN_ID_LENGTH = 16;
|
|
117
|
+
__name(generateId, "generateId");
|
|
118
|
+
__name(generateShortId, "generateShortId");
|
|
119
|
+
__name(generateCorrelationId, "generateCorrelationId");
|
|
120
|
+
__name(generateTraceId, "generateTraceId");
|
|
121
|
+
__name(generateSpanId, "generateSpanId");
|
|
122
|
+
}
|
|
123
|
+
});
|
|
107
124
|
function isStringFalsy(value) {
|
|
108
125
|
if (value === "") return true;
|
|
109
126
|
const lower = value.toLowerCase().trim();
|
|
110
127
|
return ["false", "no", "0", "off", "disabled"].includes(lower);
|
|
111
128
|
}
|
|
112
|
-
__name(isStringFalsy, "isStringFalsy");
|
|
113
129
|
function isObjectTruthy(value) {
|
|
114
130
|
if (Array.isArray(value)) return value.length > 0;
|
|
115
131
|
if (value instanceof Map || value instanceof Set) return value.size > 0;
|
|
116
132
|
if (value.constructor === Object) return Object.keys(value).length > 0;
|
|
117
133
|
return true;
|
|
118
134
|
}
|
|
119
|
-
__name(isObjectTruthy, "isObjectTruthy");
|
|
120
135
|
function isTruthy(value) {
|
|
121
136
|
if (value === null || value === void 0) return false;
|
|
122
137
|
switch (typeof value) {
|
|
@@ -135,747 +150,1215 @@ function isTruthy(value) {
|
|
|
135
150
|
return false;
|
|
136
151
|
}
|
|
137
152
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
constructor() {
|
|
144
|
-
this.emitter = new EventEmitter();
|
|
145
|
-
this.subscriptions = /* @__PURE__ */ new Map();
|
|
146
|
-
this.emitter.setMaxListeners(MAX_EVENT_LISTENERS);
|
|
147
|
-
}
|
|
148
|
-
static {
|
|
149
|
-
__name(this, "CoreEventManagerClass");
|
|
150
|
-
}
|
|
151
|
-
emit(eventType, data) {
|
|
152
|
-
const [scope] = eventType.split(":");
|
|
153
|
-
const event = {
|
|
154
|
-
type: eventType,
|
|
155
|
-
scope: scope ?? "system",
|
|
156
|
-
timestamp: Date.now(),
|
|
157
|
-
correlationId: this.getCorrelationId(),
|
|
158
|
-
data
|
|
159
|
-
};
|
|
160
|
-
this.emitter.emit(eventType, event);
|
|
161
|
-
this.emitter.emit("*", event);
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
on(eventType, handler) {
|
|
165
|
-
this.emitter.on(eventType, handler);
|
|
166
|
-
if (!this.subscriptions.has(eventType)) {
|
|
167
|
-
this.subscriptions.set(eventType, /* @__PURE__ */ new Set());
|
|
168
|
-
}
|
|
169
|
-
this.subscriptions.get(eventType).add(handler);
|
|
170
|
-
return () => {
|
|
171
|
-
this.emitter.off(eventType, handler);
|
|
172
|
-
this.subscriptions.get(eventType)?.delete(handler);
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
once(eventType, handler) {
|
|
176
|
-
const wrappedHandler = /* @__PURE__ */ __name((event) => {
|
|
177
|
-
handler(event);
|
|
178
|
-
this.emitter.off(eventType, wrappedHandler);
|
|
179
|
-
this.subscriptions.get(eventType)?.delete(wrappedHandler);
|
|
180
|
-
}, "wrappedHandler");
|
|
181
|
-
this.emitter.on(eventType, wrappedHandler);
|
|
182
|
-
if (!this.subscriptions.has(eventType)) {
|
|
183
|
-
this.subscriptions.set(eventType, /* @__PURE__ */ new Set());
|
|
184
|
-
}
|
|
185
|
-
this.subscriptions.get(eventType).add(wrappedHandler);
|
|
186
|
-
return () => {
|
|
187
|
-
this.emitter.off(eventType, wrappedHandler);
|
|
188
|
-
this.subscriptions.get(eventType)?.delete(wrappedHandler);
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Subscribe to all events in a scope
|
|
193
|
-
*
|
|
194
|
-
* @param scope - Event scope to listen to (e.g., CoreEventScope.AUTH)
|
|
195
|
-
* @param handler - Event handler
|
|
196
|
-
* @returns Unsubscribe function
|
|
197
|
-
*
|
|
198
|
-
* @example
|
|
199
|
-
* ```typescript
|
|
200
|
-
* CoreEventManager.onScope(CoreEventScope.AUTH, (event) => {
|
|
201
|
-
* // Handles auth:login, auth:logout, auth:tokenRefresh, etc.
|
|
202
|
-
* console.log(`Auth event: ${event.type}`, event.data);
|
|
203
|
-
* });
|
|
204
|
-
* ```
|
|
205
|
-
*/
|
|
206
|
-
onScope(scope, handler) {
|
|
207
|
-
const wrappedHandler = /* @__PURE__ */ __name((event) => {
|
|
208
|
-
if (event.scope === scope) {
|
|
209
|
-
handler(event);
|
|
210
|
-
}
|
|
211
|
-
}, "wrappedHandler");
|
|
212
|
-
return this.on("*", wrappedHandler);
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Remove a specific handler from an event
|
|
216
|
-
*
|
|
217
|
-
* @param eventType - Event type
|
|
218
|
-
* @param handler - Handler to remove
|
|
219
|
-
*/
|
|
220
|
-
off(eventType, handler) {
|
|
221
|
-
this.emitter.off(eventType, handler);
|
|
222
|
-
this.subscriptions.get(eventType)?.delete(handler);
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Dispose all subscriptions
|
|
226
|
-
*/
|
|
227
|
-
dispose() {
|
|
228
|
-
this.subscriptions.forEach((handlers, eventType) => {
|
|
229
|
-
handlers.forEach((handler) => this.emitter.off(eventType, handler));
|
|
230
|
-
});
|
|
231
|
-
this.subscriptions.clear();
|
|
232
|
-
this.emitter.removeAllListeners();
|
|
153
|
+
var init_values = __esm({
|
|
154
|
+
"src/utils/common/values.ts"() {
|
|
155
|
+
__name(isStringFalsy, "isStringFalsy");
|
|
156
|
+
__name(isObjectTruthy, "isObjectTruthy");
|
|
157
|
+
__name(isTruthy, "isTruthy");
|
|
233
158
|
}
|
|
234
|
-
|
|
235
|
-
* Generate correlation ID for event tracing
|
|
236
|
-
*/
|
|
237
|
-
getCorrelationId() {
|
|
238
|
-
return generateCorrelationId();
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
var CoreEventManager = new CoreEventManagerClass();
|
|
159
|
+
});
|
|
242
160
|
|
|
243
|
-
// src/
|
|
244
|
-
var
|
|
245
|
-
|
|
246
|
-
users: ["password_hash", "phone_number", "date_of_birth"],
|
|
247
|
-
// KYC sensitive data
|
|
248
|
-
"backoffice.kyc_submissions": ["tax_id", "address_line1", "address_line2"],
|
|
249
|
-
// Connected accounts OAuth tokens
|
|
250
|
-
connected_accounts: ["access_token_encrypted", "refresh_token_encrypted", "wallet_address"],
|
|
251
|
-
// Payment payout accounts
|
|
252
|
-
user_payout_accounts: ["provider_account_id", "account_holder_name"],
|
|
253
|
-
// Sessions
|
|
254
|
-
sessions: ["token"]
|
|
255
|
-
};
|
|
256
|
-
var DEFAULT_CACHE_TTL_SECONDS = 300;
|
|
257
|
-
var DEFAULT_AUDIT_RETENTION_DAYS = 180;
|
|
258
|
-
var DEFAULT_POOL_SIZE = 10;
|
|
259
|
-
var DEFAULT_CONFIG = {
|
|
260
|
-
adapter: "sql",
|
|
261
|
-
cache: {
|
|
262
|
-
enabled: true,
|
|
263
|
-
provider: "memory",
|
|
264
|
-
ttl: DEFAULT_CACHE_TTL_SECONDS
|
|
265
|
-
},
|
|
266
|
-
softDelete: {
|
|
267
|
-
enabled: true,
|
|
268
|
-
field: "deleted_at",
|
|
269
|
-
excludeTables: [
|
|
270
|
-
"audit_logs",
|
|
271
|
-
"audit.audit_logs",
|
|
272
|
-
"audit.feature_flag_evaluations",
|
|
273
|
-
"feature_flag_evaluations",
|
|
274
|
-
"notification_events",
|
|
275
|
-
"payments_activity",
|
|
276
|
-
"campaign_analytics",
|
|
277
|
-
"admin_actions",
|
|
278
|
-
"backoffice.admin_actions"
|
|
279
|
-
]
|
|
280
|
-
},
|
|
281
|
-
audit: {
|
|
282
|
-
enabled: true,
|
|
283
|
-
// Enabled by default for compliance
|
|
284
|
-
retentionDays: DEFAULT_AUDIT_RETENTION_DAYS,
|
|
285
|
-
excludeFields: ["password_hash", "api_key_hash", "token_hash"],
|
|
286
|
-
excludeTables: ["audit_logs", "audit.audit_logs"],
|
|
287
|
-
// Don't audit the audit table itself
|
|
288
|
-
schema: "audit",
|
|
289
|
-
// Use dedicated audit schema
|
|
290
|
-
usePartitionedTables: true
|
|
291
|
-
// Use daily partitioned tables (audit_log_yyyy_mm_dd)
|
|
292
|
-
}
|
|
293
|
-
// NOTE: Encryption requires key from environment or DbService.initialize()
|
|
294
|
-
// Enable via: DbService.initialize({ encryption: { enabled: true, key: process.env.ENCRYPTION_KEY!, fields: DEFAULT_ENCRYPTION_FIELDS } })
|
|
295
|
-
};
|
|
296
|
-
var TABLE_REGISTRY = {
|
|
297
|
-
// Migration 008: Feature Flags (custom ID column 'key')
|
|
298
|
-
feature_flags: { idColumn: "key" },
|
|
299
|
-
// Test Feature Flags (custom ID column 'key')
|
|
300
|
-
test_feature_flags: { idColumn: "key" },
|
|
301
|
-
// Migration 010: Universal Analytics (custom ID column 'user_id')
|
|
302
|
-
user_analytics_summary: { idColumn: "user_id" }
|
|
303
|
-
};
|
|
304
|
-
var DbService = class _DbService {
|
|
305
|
-
constructor() {
|
|
306
|
-
this.databaseService = null;
|
|
307
|
-
this.namedAdapters = /* @__PURE__ */ new Map();
|
|
308
|
-
this.config = null;
|
|
309
|
-
this.initialized = false;
|
|
310
|
-
}
|
|
311
|
-
static {
|
|
312
|
-
__name(this, "DbService");
|
|
161
|
+
// src/utils/common/object.ts
|
|
162
|
+
var init_object = __esm({
|
|
163
|
+
"src/utils/common/object.ts"() {
|
|
313
164
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Emits a database error event via CoreEventManager.
|
|
319
|
-
* Called when database operations fail to integrate with global error handling.
|
|
320
|
-
*
|
|
321
|
-
* @param error - The error that occurred
|
|
322
|
-
* @param operation - The operation that failed (e.g., 'transaction', 'query', 'healthCheck')
|
|
323
|
-
* @param table - Optional table name involved in the operation
|
|
324
|
-
* @param query - Optional query string that failed
|
|
325
|
-
* @param recoverable - Whether the error is recoverable (default: false)
|
|
326
|
-
*/
|
|
327
|
-
emitDatabaseError(error, operation, options) {
|
|
328
|
-
const payload = {
|
|
329
|
-
error,
|
|
330
|
-
operation,
|
|
331
|
-
table: options?.table,
|
|
332
|
-
query: options?.query,
|
|
333
|
-
recoverable: options?.recoverable ?? false
|
|
334
|
-
};
|
|
335
|
-
CoreEventManager.emit(CORE_EVENTS.DATABASE.ERROR, payload);
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* Gets the singleton instance of DbService
|
|
339
|
-
*
|
|
340
|
-
* @returns {DbService} The singleton instance
|
|
341
|
-
*/
|
|
342
|
-
static getInstance() {
|
|
343
|
-
_DbService.instance ??= new _DbService();
|
|
344
|
-
return _DbService.instance;
|
|
165
|
+
});
|
|
166
|
+
var init_validation = __esm({
|
|
167
|
+
"src/utils/common/validation.ts"() {
|
|
345
168
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// src/utils/common/index.ts
|
|
172
|
+
var init_common = __esm({
|
|
173
|
+
"src/utils/common/index.ts"() {
|
|
174
|
+
init_hash();
|
|
175
|
+
init_id();
|
|
176
|
+
init_values();
|
|
177
|
+
init_object();
|
|
178
|
+
init_validation();
|
|
353
179
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
180
|
+
});
|
|
181
|
+
var MAX_EVENT_LISTENERS, CoreEventManagerClass, CoreEventManager;
|
|
182
|
+
var init_CoreEventManager = __esm({
|
|
183
|
+
"src/events/CoreEventManager.ts"() {
|
|
184
|
+
init_common();
|
|
185
|
+
MAX_EVENT_LISTENERS = 100;
|
|
186
|
+
CoreEventManagerClass = class {
|
|
187
|
+
constructor() {
|
|
188
|
+
this.emitter = new EventEmitter();
|
|
189
|
+
this.subscriptions = /* @__PURE__ */ new Map();
|
|
190
|
+
this.emitter.setMaxListeners(MAX_EVENT_LISTENERS);
|
|
191
|
+
}
|
|
192
|
+
static {
|
|
193
|
+
__name(this, "CoreEventManagerClass");
|
|
194
|
+
}
|
|
195
|
+
emit(eventType, data) {
|
|
196
|
+
const [scope] = eventType.split(":");
|
|
197
|
+
const event = {
|
|
198
|
+
type: eventType,
|
|
199
|
+
scope: scope ?? "system",
|
|
200
|
+
timestamp: Date.now(),
|
|
201
|
+
correlationId: this.getCorrelationId(),
|
|
202
|
+
data
|
|
203
|
+
};
|
|
204
|
+
this.emitter.emit(eventType, event);
|
|
205
|
+
this.emitter.emit("*", event);
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
on(eventType, handler) {
|
|
209
|
+
this.emitter.on(eventType, handler);
|
|
210
|
+
if (!this.subscriptions.has(eventType)) {
|
|
211
|
+
this.subscriptions.set(eventType, /* @__PURE__ */ new Set());
|
|
212
|
+
}
|
|
213
|
+
this.subscriptions.get(eventType).add(handler);
|
|
214
|
+
return () => {
|
|
215
|
+
this.emitter.off(eventType, handler);
|
|
216
|
+
this.subscriptions.get(eventType)?.delete(handler);
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
once(eventType, handler) {
|
|
220
|
+
const wrappedHandler = /* @__PURE__ */ __name((event) => {
|
|
221
|
+
handler(event);
|
|
222
|
+
this.emitter.off(eventType, wrappedHandler);
|
|
223
|
+
this.subscriptions.get(eventType)?.delete(wrappedHandler);
|
|
224
|
+
}, "wrappedHandler");
|
|
225
|
+
this.emitter.on(eventType, wrappedHandler);
|
|
226
|
+
if (!this.subscriptions.has(eventType)) {
|
|
227
|
+
this.subscriptions.set(eventType, /* @__PURE__ */ new Set());
|
|
228
|
+
}
|
|
229
|
+
this.subscriptions.get(eventType).add(wrappedHandler);
|
|
230
|
+
return () => {
|
|
231
|
+
this.emitter.off(eventType, wrappedHandler);
|
|
232
|
+
this.subscriptions.get(eventType)?.delete(wrappedHandler);
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Subscribe to all events in a scope
|
|
237
|
+
*
|
|
238
|
+
* @param scope - Event scope to listen to (e.g., CoreEventScope.AUTH)
|
|
239
|
+
* @param handler - Event handler
|
|
240
|
+
* @returns Unsubscribe function
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```typescript
|
|
244
|
+
* CoreEventManager.onScope(CoreEventScope.AUTH, (event) => {
|
|
245
|
+
* // Handles auth:login, auth:logout, auth:tokenRefresh, etc.
|
|
246
|
+
* console.log(`Auth event: ${event.type}`, event.data);
|
|
247
|
+
* });
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
onScope(scope, handler) {
|
|
251
|
+
const wrappedHandler = /* @__PURE__ */ __name((event) => {
|
|
252
|
+
if (event.scope === scope) {
|
|
253
|
+
handler(event);
|
|
254
|
+
}
|
|
255
|
+
}, "wrappedHandler");
|
|
256
|
+
return this.on("*", wrappedHandler);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Remove a specific handler from an event
|
|
260
|
+
*
|
|
261
|
+
* @param eventType - Event type
|
|
262
|
+
* @param handler - Handler to remove
|
|
263
|
+
*/
|
|
264
|
+
off(eventType, handler) {
|
|
265
|
+
this.emitter.off(eventType, handler);
|
|
266
|
+
this.subscriptions.get(eventType)?.delete(handler);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Dispose all subscriptions
|
|
270
|
+
*/
|
|
271
|
+
dispose() {
|
|
272
|
+
this.subscriptions.forEach((handlers, eventType) => {
|
|
273
|
+
handlers.forEach((handler) => this.emitter.off(eventType, handler));
|
|
274
|
+
});
|
|
275
|
+
this.subscriptions.clear();
|
|
276
|
+
this.emitter.removeAllListeners();
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Generate correlation ID for event tracing
|
|
280
|
+
*/
|
|
281
|
+
getCorrelationId() {
|
|
282
|
+
return generateCorrelationId();
|
|
371
283
|
}
|
|
372
|
-
_DbService.instance = null;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* Initializes the database connection
|
|
377
|
-
*
|
|
378
|
-
* @description Sets up the database connection using the provided configuration
|
|
379
|
-
* or environment variables. This method is idempotent - calling it multiple
|
|
380
|
-
* times won't create additional connections.
|
|
381
|
-
*
|
|
382
|
-
* **Environment Variables:**
|
|
383
|
-
* - `DATABASE_URL` - PostgreSQL connection string (for sql/drizzle adapters)
|
|
384
|
-
* - `ENCRYPTION_KEY` - 32-byte encryption key for field encryption (optional)
|
|
385
|
-
* - `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, `SUPABASE_ANON_PUBLIC_KEY` - For supabase adapter
|
|
386
|
-
*
|
|
387
|
-
* **Encryption:** If `ENCRYPTION_KEY` env is set, encryption is auto-enabled
|
|
388
|
-
* using `DEFAULT_ENCRYPTION_FIELDS`. Override via config.encryption.
|
|
389
|
-
*
|
|
390
|
-
* @param {DbServiceConfig} [config] - Optional configuration
|
|
391
|
-
* @returns {Promise<DbService>} The initialized DbService instance
|
|
392
|
-
* @throws {DatabasePackageError} When configuration is invalid or connection fails
|
|
393
|
-
*
|
|
394
|
-
* @example
|
|
395
|
-
* ```typescript
|
|
396
|
-
* // Minimal - uses DATABASE_URL env, auto-enables encryption if ENCRYPTION_KEY set
|
|
397
|
-
* await DbService.initialize();
|
|
398
|
-
*
|
|
399
|
-
* // With explicit encryption
|
|
400
|
-
* await DbService.initialize({
|
|
401
|
-
* adapter: 'sql',
|
|
402
|
-
* encryption: {
|
|
403
|
-
* enabled: true,
|
|
404
|
-
* key: 'your-32-byte-encryption-key-here!!',
|
|
405
|
-
* fields: DEFAULT_ENCRYPTION_FIELDS,
|
|
406
|
-
* },
|
|
407
|
-
* });
|
|
408
|
-
*
|
|
409
|
-
* // Or via Core.initialize() with env:
|
|
410
|
-
* await Core.initialize({
|
|
411
|
-
* env: { ENCRYPTION_KEY: '...' },
|
|
412
|
-
* db: { adapter: 'sql' },
|
|
413
|
-
* });
|
|
414
|
-
* ```
|
|
415
|
-
*/
|
|
416
|
-
/** Build encryption config from user config */
|
|
417
|
-
static buildEncryptionConfig(config) {
|
|
418
|
-
const encryption = config?.encryption;
|
|
419
|
-
if (!encryption?.key) return void 0;
|
|
420
|
-
return {
|
|
421
|
-
enabled: encryption.enabled ?? true,
|
|
422
|
-
key: encryption.key,
|
|
423
|
-
fields: encryption.fields ?? DEFAULT_ENCRYPTION_FIELDS,
|
|
424
|
-
algorithm: encryption.algorithm ?? "aes-256-gcm"
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
/** Merge user config with defaults */
|
|
428
|
-
static mergeConfig(config) {
|
|
429
|
-
return {
|
|
430
|
-
...DEFAULT_CONFIG,
|
|
431
|
-
...config,
|
|
432
|
-
softDelete: { ...DEFAULT_CONFIG.softDelete, ...config?.softDelete },
|
|
433
|
-
cache: { ...DEFAULT_CONFIG.cache, ...config?.cache },
|
|
434
|
-
audit: { ...DEFAULT_CONFIG.audit, ...config?.audit },
|
|
435
|
-
encryption: _DbService.buildEncryptionConfig(config)
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
/** Initialize named adapters */
|
|
439
|
-
async initializeNamedAdapters(mergedConfig) {
|
|
440
|
-
if (!mergedConfig.adapters) return;
|
|
441
|
-
for (const [name, adapterConfig] of Object.entries(mergedConfig.adapters)) {
|
|
442
|
-
const namedDbConfig = this.buildDatabaseConfig({
|
|
443
|
-
...mergedConfig,
|
|
444
|
-
adapter: adapterConfig.adapter,
|
|
445
|
-
drizzle: adapterConfig.drizzle,
|
|
446
|
-
supabase: adapterConfig.supabase,
|
|
447
|
-
sql: adapterConfig.sql
|
|
448
|
-
});
|
|
449
|
-
const namedService = await createDatabaseService(namedDbConfig);
|
|
450
|
-
this.namedAdapters.set(name, namedService);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
static async initialize(config) {
|
|
454
|
-
const instance = _DbService.getInstance();
|
|
455
|
-
if (instance.initialized) {
|
|
456
|
-
return instance;
|
|
457
|
-
}
|
|
458
|
-
const mergedConfig = _DbService.mergeConfig(config);
|
|
459
|
-
instance.config = mergedConfig;
|
|
460
|
-
const dbConfig = instance.buildDatabaseConfig(mergedConfig);
|
|
461
|
-
instance.databaseService = await createDatabaseService(dbConfig);
|
|
462
|
-
await instance.initializeNamedAdapters(mergedConfig);
|
|
463
|
-
instance.initialized = true;
|
|
464
|
-
return instance;
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Builds the DatabaseServiceConfig based on the adapter type
|
|
468
|
-
* @private
|
|
469
|
-
*/
|
|
470
|
-
/** Builds adapter-specific config based on adapter type */
|
|
471
|
-
buildAdapterConfig(adapter, config) {
|
|
472
|
-
const builders = {
|
|
473
|
-
drizzle: /* @__PURE__ */ __name(() => this.buildDrizzleConfig(config), "drizzle"),
|
|
474
|
-
supabase: /* @__PURE__ */ __name(() => this.buildSupabaseConfig(config), "supabase"),
|
|
475
|
-
sql: /* @__PURE__ */ __name(() => this.buildSqlConfig(config), "sql")
|
|
476
|
-
};
|
|
477
|
-
const builder = builders[adapter];
|
|
478
|
-
if (!builder) {
|
|
479
|
-
throw new DatabasePackageError(
|
|
480
|
-
`Unsupported adapter type: ${adapter}`,
|
|
481
|
-
DATABASE_ERROR_CODES.CONFIG_REQUIRED
|
|
482
|
-
);
|
|
483
|
-
}
|
|
484
|
-
return builder();
|
|
485
|
-
}
|
|
486
|
-
/** Builds soft delete extension config */
|
|
487
|
-
buildSoftDeleteExtension(config) {
|
|
488
|
-
if (!config.softDelete?.enabled) return void 0;
|
|
489
|
-
return {
|
|
490
|
-
enabled: true,
|
|
491
|
-
field: config.softDelete.field ?? "deleted_at",
|
|
492
|
-
excludeTables: config.softDelete.excludeTables
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
/** Builds cache extension config */
|
|
496
|
-
buildCacheExtension(config) {
|
|
497
|
-
if (!config.cache?.enabled) return void 0;
|
|
498
|
-
return {
|
|
499
|
-
enabled: true,
|
|
500
|
-
ttl: config.cache.ttl ?? DEFAULT_CACHE_TTL_SECONDS,
|
|
501
|
-
provider: config.cache.provider ?? "memory",
|
|
502
|
-
invalidation: config.cache.invalidation ?? "write"
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
/** Builds audit extension config */
|
|
506
|
-
buildAuditExtension(config) {
|
|
507
|
-
if (!config.audit?.enabled) return void 0;
|
|
508
|
-
return {
|
|
509
|
-
enabled: true,
|
|
510
|
-
retentionDays: config.audit.retentionDays ?? DEFAULT_AUDIT_RETENTION_DAYS,
|
|
511
|
-
excludeFields: config.audit.excludeFields,
|
|
512
|
-
excludeTables: config.audit.excludeTables,
|
|
513
|
-
schema: config.audit.schema ?? "audit",
|
|
514
|
-
usePartitionedTables: config.audit.usePartitionedTables ?? true
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
/** Builds encryption extension config */
|
|
518
|
-
buildEncryptionExtension(config) {
|
|
519
|
-
if (!config.encryption?.enabled || !config.encryption.key) return void 0;
|
|
520
|
-
return {
|
|
521
|
-
enabled: true,
|
|
522
|
-
key: config.encryption.key,
|
|
523
|
-
fields: config.encryption.fields ?? {},
|
|
524
|
-
algorithm: config.encryption.algorithm
|
|
525
284
|
};
|
|
285
|
+
CoreEventManager = new CoreEventManagerClass();
|
|
526
286
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
"Drizzle adapter requires a PostgreSQL connection string. Provide `drizzle.connectionString` in config or use Core.initialize() with DATABASE_URL in env. For Supabase, find this in Dashboard > Database > Connection string (URI).",
|
|
552
|
-
DATABASE_ERROR_CODES.CONFIG_REQUIRED
|
|
553
|
-
);
|
|
554
|
-
}
|
|
555
|
-
const tableIdColumns = this.buildTableIdColumns();
|
|
556
|
-
return {
|
|
557
|
-
connectionString,
|
|
558
|
-
poolSize: config.drizzle?.poolSize ?? DEFAULT_POOL_SIZE,
|
|
559
|
-
ssl: config.drizzle?.ssl,
|
|
560
|
-
schema: config.drizzle?.schema,
|
|
561
|
-
// Pass through schema configuration
|
|
562
|
-
tableIdColumns
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// src/services/DbService.ts
|
|
290
|
+
var DbService_exports = {};
|
|
291
|
+
__export(DbService_exports, {
|
|
292
|
+
DEFAULT_ENCRYPTION_FIELDS: () => DEFAULT_ENCRYPTION_FIELDS,
|
|
293
|
+
DbService: () => DbService,
|
|
294
|
+
TABLE_REGISTRY: () => TABLE_REGISTRY
|
|
295
|
+
});
|
|
296
|
+
var DEFAULT_ENCRYPTION_FIELDS, DEFAULT_CACHE_TTL_SECONDS, DEFAULT_AUDIT_RETENTION_DAYS, DEFAULT_POOL_SIZE, DEFAULT_CONFIG, TABLE_REGISTRY, DbService;
|
|
297
|
+
var init_DbService = __esm({
|
|
298
|
+
"src/services/DbService.ts"() {
|
|
299
|
+
init_CoreEventManager();
|
|
300
|
+
DEFAULT_ENCRYPTION_FIELDS = {
|
|
301
|
+
// User PII
|
|
302
|
+
users: ["password_hash", "phone_number", "date_of_birth"],
|
|
303
|
+
// KYC sensitive data
|
|
304
|
+
"backoffice.kyc_submissions": ["tax_id", "address_line1", "address_line2"],
|
|
305
|
+
// Connected accounts OAuth tokens
|
|
306
|
+
connected_accounts: ["access_token_encrypted", "refresh_token_encrypted", "wallet_address"],
|
|
307
|
+
// Payment payout accounts
|
|
308
|
+
user_payout_accounts: ["provider_account_id", "account_holder_name"],
|
|
309
|
+
// Sessions
|
|
310
|
+
sessions: ["token"]
|
|
563
311
|
};
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
312
|
+
DEFAULT_CACHE_TTL_SECONDS = 300;
|
|
313
|
+
DEFAULT_AUDIT_RETENTION_DAYS = 180;
|
|
314
|
+
DEFAULT_POOL_SIZE = 10;
|
|
315
|
+
DEFAULT_CONFIG = {
|
|
316
|
+
adapter: "sql",
|
|
317
|
+
cache: {
|
|
318
|
+
enabled: true,
|
|
319
|
+
provider: "memory",
|
|
320
|
+
ttl: DEFAULT_CACHE_TTL_SECONDS
|
|
321
|
+
},
|
|
322
|
+
softDelete: {
|
|
323
|
+
enabled: true,
|
|
324
|
+
field: "deleted_at",
|
|
325
|
+
excludeTables: [
|
|
326
|
+
"audit_logs",
|
|
327
|
+
"audit.audit_logs",
|
|
328
|
+
"audit.feature_flag_evaluations",
|
|
329
|
+
"feature_flag_evaluations",
|
|
330
|
+
"notification_events",
|
|
331
|
+
"payments_activity",
|
|
332
|
+
"campaign_analytics",
|
|
333
|
+
"admin_actions",
|
|
334
|
+
"backoffice.admin_actions"
|
|
335
|
+
]
|
|
336
|
+
},
|
|
337
|
+
audit: {
|
|
338
|
+
enabled: true,
|
|
339
|
+
// Enabled by default for compliance
|
|
340
|
+
retentionDays: DEFAULT_AUDIT_RETENTION_DAYS,
|
|
341
|
+
excludeFields: ["password_hash", "api_key_hash", "token_hash"],
|
|
342
|
+
excludeTables: ["audit_logs", "audit.audit_logs"],
|
|
343
|
+
// Don't audit the audit table itself
|
|
344
|
+
schema: "audit",
|
|
345
|
+
// Use dedicated audit schema
|
|
346
|
+
usePartitionedTables: true
|
|
347
|
+
// Use daily partitioned tables (audit_log_yyyy_mm_dd)
|
|
348
|
+
}
|
|
349
|
+
// NOTE: Encryption requires key from environment or DbService.initialize()
|
|
350
|
+
// Enable via: DbService.initialize({ encryption: { enabled: true, key: process.env.ENCRYPTION_KEY!, fields: DEFAULT_ENCRYPTION_FIELDS } })
|
|
593
351
|
};
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
if (!connectionString) {
|
|
602
|
-
throw new DatabasePackageError(
|
|
603
|
-
"SQL adapter requires a connection string. Provide `sql.connectionString` in config or use Core.initialize() with DATABASE_URL in env.",
|
|
604
|
-
DATABASE_ERROR_CODES.CONFIG_REQUIRED
|
|
605
|
-
);
|
|
606
|
-
}
|
|
607
|
-
const tableIdColumns = this.buildTableIdColumns();
|
|
608
|
-
return {
|
|
609
|
-
connectionString,
|
|
610
|
-
dialect: config.sql?.dialect ?? "postgresql",
|
|
611
|
-
schema: config.sql?.schema,
|
|
612
|
-
// Pass through schema configuration
|
|
613
|
-
tableIdColumns
|
|
352
|
+
TABLE_REGISTRY = {
|
|
353
|
+
// Migration 008: Feature Flags (custom ID column 'key')
|
|
354
|
+
feature_flags: { idColumn: "key" },
|
|
355
|
+
// Test Feature Flags (custom ID column 'key')
|
|
356
|
+
test_feature_flags: { idColumn: "key" },
|
|
357
|
+
// Migration 010: Universal Analytics (custom ID column 'user_id')
|
|
358
|
+
user_analytics_summary: { idColumn: "user_id" }
|
|
614
359
|
};
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
360
|
+
DbService = class _DbService {
|
|
361
|
+
constructor() {
|
|
362
|
+
this.databaseService = null;
|
|
363
|
+
this.namedAdapters = /* @__PURE__ */ new Map();
|
|
364
|
+
this.config = null;
|
|
365
|
+
this.initialized = false;
|
|
366
|
+
}
|
|
367
|
+
static {
|
|
368
|
+
__name(this, "DbService");
|
|
369
|
+
}
|
|
370
|
+
static {
|
|
371
|
+
this.instance = null;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Emits a database error event via CoreEventManager.
|
|
375
|
+
* Called when database operations fail to integrate with global error handling.
|
|
376
|
+
*
|
|
377
|
+
* @param error - The error that occurred
|
|
378
|
+
* @param operation - The operation that failed (e.g., 'transaction', 'query', 'healthCheck')
|
|
379
|
+
* @param table - Optional table name involved in the operation
|
|
380
|
+
* @param query - Optional query string that failed
|
|
381
|
+
* @param recoverable - Whether the error is recoverable (default: false)
|
|
382
|
+
*/
|
|
383
|
+
emitDatabaseError(error, operation, options) {
|
|
384
|
+
const payload = {
|
|
385
|
+
error,
|
|
386
|
+
operation,
|
|
387
|
+
table: options?.table,
|
|
388
|
+
query: options?.query,
|
|
389
|
+
recoverable: options?.recoverable ?? false
|
|
390
|
+
};
|
|
391
|
+
CoreEventManager.emit(CORE_EVENTS$1.DATABASE.ERROR, payload);
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Gets the singleton instance of DbService
|
|
395
|
+
*
|
|
396
|
+
* @returns {DbService} The singleton instance
|
|
397
|
+
*/
|
|
398
|
+
static getInstance() {
|
|
399
|
+
_DbService.instance ??= new _DbService();
|
|
400
|
+
return _DbService.instance;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Checks if the database service has been initialized
|
|
404
|
+
*
|
|
405
|
+
* @returns {boolean} True if initialized
|
|
406
|
+
*/
|
|
407
|
+
static isInitialized() {
|
|
408
|
+
return _DbService.instance?.initialized ?? false;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Resets the database service by closing connections and clearing the singleton instance
|
|
412
|
+
*
|
|
413
|
+
* @description Properly closes the database connection and clears the singleton.
|
|
414
|
+
* Useful for testing or when you need to reinitialize with different configuration.
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* ```typescript
|
|
418
|
+
* await DbService.reset();
|
|
419
|
+
* await DbService.initialize({ adapter: 'sql', ... });
|
|
420
|
+
* ```
|
|
421
|
+
*/
|
|
422
|
+
static async reset() {
|
|
423
|
+
if (_DbService.instance) {
|
|
424
|
+
try {
|
|
425
|
+
await _DbService.instance.databaseService?.close?.();
|
|
426
|
+
} catch {
|
|
427
|
+
}
|
|
428
|
+
_DbService.instance = null;
|
|
633
429
|
}
|
|
634
430
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
431
|
+
/**
|
|
432
|
+
* Initializes the database connection
|
|
433
|
+
*
|
|
434
|
+
* @description Sets up the database connection using the provided configuration
|
|
435
|
+
* or environment variables. This method is idempotent - calling it multiple
|
|
436
|
+
* times won't create additional connections.
|
|
437
|
+
*
|
|
438
|
+
* **Environment Variables:**
|
|
439
|
+
* - `DATABASE_URL` - PostgreSQL connection string (for sql/drizzle adapters)
|
|
440
|
+
* - `ENCRYPTION_KEY` - 32-byte encryption key for field encryption (optional)
|
|
441
|
+
* - `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, `SUPABASE_ANON_PUBLIC_KEY` - For supabase adapter
|
|
442
|
+
*
|
|
443
|
+
* **Encryption:** If `ENCRYPTION_KEY` env is set, encryption is auto-enabled
|
|
444
|
+
* using `DEFAULT_ENCRYPTION_FIELDS`. Override via config.encryption.
|
|
445
|
+
*
|
|
446
|
+
* @param {DbServiceConfig} [config] - Optional configuration
|
|
447
|
+
* @returns {Promise<DbService>} The initialized DbService instance
|
|
448
|
+
* @throws {DatabasePackageError} When configuration is invalid or connection fails
|
|
449
|
+
*
|
|
450
|
+
* @example
|
|
451
|
+
* ```typescript
|
|
452
|
+
* // Minimal - uses DATABASE_URL env, auto-enables encryption if ENCRYPTION_KEY set
|
|
453
|
+
* await DbService.initialize();
|
|
454
|
+
*
|
|
455
|
+
* // With explicit encryption
|
|
456
|
+
* await DbService.initialize({
|
|
457
|
+
* adapter: 'sql',
|
|
458
|
+
* encryption: {
|
|
459
|
+
* enabled: true,
|
|
460
|
+
* key: 'your-32-byte-encryption-key-here!!',
|
|
461
|
+
* fields: DEFAULT_ENCRYPTION_FIELDS,
|
|
462
|
+
* },
|
|
463
|
+
* });
|
|
464
|
+
*
|
|
465
|
+
* // Or via Core.initialize() with env:
|
|
466
|
+
* await Core.initialize({
|
|
467
|
+
* env: { ENCRYPTION_KEY: '...' },
|
|
468
|
+
* db: { adapter: 'sql' },
|
|
469
|
+
* });
|
|
470
|
+
* ```
|
|
471
|
+
*/
|
|
472
|
+
/** Build encryption config from user config */
|
|
473
|
+
static buildEncryptionConfig(config) {
|
|
474
|
+
const encryption = config?.encryption;
|
|
475
|
+
if (!encryption?.key) return void 0;
|
|
476
|
+
return {
|
|
477
|
+
enabled: encryption.enabled ?? true,
|
|
478
|
+
key: encryption.key,
|
|
479
|
+
fields: encryption.fields ?? DEFAULT_ENCRYPTION_FIELDS,
|
|
480
|
+
algorithm: encryption.algorithm ?? "aes-256-gcm"
|
|
481
|
+
};
|
|
644
482
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
getDatabase(adapterName) {
|
|
656
|
-
if (adapterName) {
|
|
657
|
-
const namedAdapter = this.namedAdapters.get(adapterName);
|
|
658
|
-
if (!namedAdapter) {
|
|
659
|
-
throw new DatabasePackageError(
|
|
660
|
-
`Named adapter '${adapterName}' not found. Available adapters: ${Array.from(this.namedAdapters.keys()).join(", ")}`,
|
|
661
|
-
DATABASE_ERROR_CODES.INIT_FAILED
|
|
662
|
-
);
|
|
483
|
+
/** Merge user config with defaults */
|
|
484
|
+
static mergeConfig(config) {
|
|
485
|
+
return {
|
|
486
|
+
...DEFAULT_CONFIG,
|
|
487
|
+
...config,
|
|
488
|
+
softDelete: { ...DEFAULT_CONFIG.softDelete, ...config?.softDelete },
|
|
489
|
+
cache: { ...DEFAULT_CONFIG.cache, ...config?.cache },
|
|
490
|
+
audit: { ...DEFAULT_CONFIG.audit, ...config?.audit },
|
|
491
|
+
encryption: _DbService.buildEncryptionConfig(config)
|
|
492
|
+
};
|
|
663
493
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
)
|
|
714
|
-
|
|
715
|
-
throw error;
|
|
716
|
-
}
|
|
717
|
-
return result.value;
|
|
718
|
-
}
|
|
719
|
-
/**
|
|
720
|
-
* Sets audit context for subsequent operations
|
|
721
|
-
*
|
|
722
|
-
* @param context - Audit context (userId, requestId, etc.)
|
|
723
|
-
*/
|
|
724
|
-
async setAuditContext(context) {
|
|
725
|
-
const db = this.getDatabase();
|
|
726
|
-
await db.setAuditContext(context);
|
|
727
|
-
}
|
|
728
|
-
/**
|
|
729
|
-
* Performs a health check on the database connection
|
|
730
|
-
*
|
|
731
|
-
* @returns Health check result
|
|
732
|
-
*/
|
|
733
|
-
async healthCheck() {
|
|
734
|
-
try {
|
|
735
|
-
const db = this.getDatabase();
|
|
736
|
-
const result = await db.healthCheck();
|
|
737
|
-
if (result.success && result.value) {
|
|
494
|
+
/** Initialize named adapters */
|
|
495
|
+
async initializeNamedAdapters(mergedConfig) {
|
|
496
|
+
if (!mergedConfig.adapters) return;
|
|
497
|
+
for (const [name, adapterConfig] of Object.entries(mergedConfig.adapters)) {
|
|
498
|
+
const namedDbConfig = this.buildDatabaseConfig({
|
|
499
|
+
...mergedConfig,
|
|
500
|
+
adapter: adapterConfig.adapter,
|
|
501
|
+
drizzle: adapterConfig.drizzle,
|
|
502
|
+
supabase: adapterConfig.supabase,
|
|
503
|
+
sql: adapterConfig.sql
|
|
504
|
+
});
|
|
505
|
+
const namedService = await createDatabaseService(namedDbConfig);
|
|
506
|
+
this.namedAdapters.set(name, namedService);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
static async initialize(config) {
|
|
510
|
+
const instance = _DbService.getInstance();
|
|
511
|
+
if (instance.initialized) {
|
|
512
|
+
return instance;
|
|
513
|
+
}
|
|
514
|
+
const mergedConfig = _DbService.mergeConfig(config);
|
|
515
|
+
instance.config = mergedConfig;
|
|
516
|
+
const dbConfig = instance.buildDatabaseConfig(mergedConfig);
|
|
517
|
+
instance.databaseService = await createDatabaseService(dbConfig);
|
|
518
|
+
await instance.initializeNamedAdapters(mergedConfig);
|
|
519
|
+
instance.initialized = true;
|
|
520
|
+
return instance;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Builds the DatabaseServiceConfig based on the adapter type
|
|
524
|
+
* @private
|
|
525
|
+
*/
|
|
526
|
+
/** Builds adapter-specific config based on adapter type */
|
|
527
|
+
buildAdapterConfig(adapter, config) {
|
|
528
|
+
const builders = {
|
|
529
|
+
drizzle: /* @__PURE__ */ __name(() => this.buildDrizzleConfig(config), "drizzle"),
|
|
530
|
+
supabase: /* @__PURE__ */ __name(() => this.buildSupabaseConfig(config), "supabase"),
|
|
531
|
+
sql: /* @__PURE__ */ __name(() => this.buildSqlConfig(config), "sql")
|
|
532
|
+
};
|
|
533
|
+
const builder = builders[adapter];
|
|
534
|
+
if (!builder) {
|
|
535
|
+
throw new DatabasePackageError(
|
|
536
|
+
`Unsupported adapter type: ${adapter}`,
|
|
537
|
+
DATABASE_ERROR_CODES.CONFIG_REQUIRED
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
return builder();
|
|
541
|
+
}
|
|
542
|
+
/** Builds soft delete extension config */
|
|
543
|
+
buildSoftDeleteExtension(config) {
|
|
544
|
+
if (!config.softDelete?.enabled) return void 0;
|
|
738
545
|
return {
|
|
739
|
-
|
|
740
|
-
|
|
546
|
+
enabled: true,
|
|
547
|
+
field: config.softDelete.field ?? "deleted_at",
|
|
548
|
+
excludeTables: config.softDelete.excludeTables
|
|
741
549
|
};
|
|
742
550
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
551
|
+
/** Builds cache extension config */
|
|
552
|
+
buildCacheExtension(config) {
|
|
553
|
+
if (!config.cache?.enabled) return void 0;
|
|
554
|
+
return {
|
|
555
|
+
enabled: true,
|
|
556
|
+
ttl: config.cache.ttl ?? DEFAULT_CACHE_TTL_SECONDS,
|
|
557
|
+
provider: config.cache.provider ?? "memory",
|
|
558
|
+
invalidation: config.cache.invalidation ?? "write"
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
/** Builds audit extension config */
|
|
562
|
+
buildAuditExtension(config) {
|
|
563
|
+
if (!config.audit?.enabled) return void 0;
|
|
564
|
+
return {
|
|
565
|
+
enabled: true,
|
|
566
|
+
retentionDays: config.audit.retentionDays ?? DEFAULT_AUDIT_RETENTION_DAYS,
|
|
567
|
+
excludeFields: config.audit.excludeFields,
|
|
568
|
+
excludeTables: config.audit.excludeTables,
|
|
569
|
+
schema: config.audit.schema ?? "audit",
|
|
570
|
+
usePartitionedTables: config.audit.usePartitionedTables ?? true
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
/** Builds encryption extension config */
|
|
574
|
+
buildEncryptionExtension(config) {
|
|
575
|
+
if (!config.encryption?.enabled || !config.encryption.key) return void 0;
|
|
576
|
+
return {
|
|
577
|
+
enabled: true,
|
|
578
|
+
key: config.encryption.key,
|
|
579
|
+
fields: config.encryption.fields ?? {},
|
|
580
|
+
algorithm: config.encryption.algorithm
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
buildDatabaseConfig(config) {
|
|
584
|
+
const adapter = config.adapter ?? "drizzle";
|
|
585
|
+
const dbConfig = {
|
|
586
|
+
adapter,
|
|
587
|
+
config: this.buildAdapterConfig(adapter, config)
|
|
588
|
+
};
|
|
589
|
+
const softDelete = this.buildSoftDeleteExtension(config);
|
|
590
|
+
const cache = this.buildCacheExtension(config);
|
|
591
|
+
const audit = this.buildAuditExtension(config);
|
|
592
|
+
const encryption = this.buildEncryptionExtension(config);
|
|
593
|
+
if (softDelete) dbConfig.softDelete = softDelete;
|
|
594
|
+
if (cache) dbConfig.cache = cache;
|
|
595
|
+
if (audit) dbConfig.audit = audit;
|
|
596
|
+
if (encryption) dbConfig.encryption = encryption;
|
|
597
|
+
return dbConfig;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Builds Drizzle adapter configuration
|
|
601
|
+
* @private
|
|
602
|
+
*/
|
|
603
|
+
buildDrizzleConfig(config) {
|
|
604
|
+
const connectionString = config.drizzle?.connectionString;
|
|
605
|
+
if (!connectionString) {
|
|
606
|
+
throw new DatabasePackageError(
|
|
607
|
+
"Drizzle adapter requires a PostgreSQL connection string. Provide `drizzle.connectionString` in config or use Core.initialize() with DATABASE_URL in env. For Supabase, find this in Dashboard > Database > Connection string (URI).",
|
|
608
|
+
DATABASE_ERROR_CODES.CONFIG_REQUIRED
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
const tableIdColumns = this.buildTableIdColumns();
|
|
612
|
+
return {
|
|
613
|
+
connectionString,
|
|
614
|
+
poolSize: config.drizzle?.poolSize ?? DEFAULT_POOL_SIZE,
|
|
615
|
+
ssl: config.drizzle?.ssl,
|
|
616
|
+
schema: config.drizzle?.schema,
|
|
617
|
+
// Pass through schema configuration
|
|
618
|
+
tableIdColumns
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Builds Supabase adapter configuration
|
|
623
|
+
* @private
|
|
624
|
+
*/
|
|
625
|
+
// eslint-disable-next-line complexity
|
|
626
|
+
buildSupabaseConfig(config) {
|
|
627
|
+
const supabaseUrl = config.supabase?.supabaseUrl;
|
|
628
|
+
const supabaseServiceKey = config.supabase?.supabaseServiceKey;
|
|
629
|
+
const supabaseAnonKey = config.supabase?.supabaseAnonKey;
|
|
630
|
+
if (!supabaseUrl || !supabaseServiceKey) {
|
|
631
|
+
throw new DatabasePackageError(
|
|
632
|
+
"Supabase adapter requires supabaseUrl and supabaseServiceKey. Provide in config or use Core.initialize() with SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in env.",
|
|
633
|
+
DATABASE_ERROR_CODES.CONFIG_REQUIRED
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
if (!supabaseAnonKey) {
|
|
637
|
+
throw new DatabasePackageError(
|
|
638
|
+
"Supabase adapter requires supabaseAnonKey. Provide in config or use Core.initialize() with SUPABASE_ANON_PUBLIC_KEY in env.",
|
|
639
|
+
DATABASE_ERROR_CODES.CONFIG_REQUIRED
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
const tableIdColumns = this.buildTableIdColumns();
|
|
643
|
+
return {
|
|
644
|
+
supabaseUrl,
|
|
645
|
+
supabaseServiceKey,
|
|
646
|
+
supabaseAnonKey,
|
|
647
|
+
schema: config.supabase?.schema ?? "public",
|
|
648
|
+
tableIdColumns
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Builds SQL adapter configuration
|
|
653
|
+
* @private
|
|
654
|
+
*/
|
|
655
|
+
buildSqlConfig(config) {
|
|
656
|
+
const connectionString = config.sql?.connectionString;
|
|
657
|
+
if (!connectionString) {
|
|
658
|
+
throw new DatabasePackageError(
|
|
659
|
+
"SQL adapter requires a connection string. Provide `sql.connectionString` in config or use Core.initialize() with DATABASE_URL in env.",
|
|
660
|
+
DATABASE_ERROR_CODES.CONFIG_REQUIRED
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
const tableIdColumns = this.buildTableIdColumns();
|
|
664
|
+
return {
|
|
665
|
+
connectionString,
|
|
666
|
+
dialect: config.sql?.dialect ?? "postgresql",
|
|
667
|
+
schema: config.sql?.schema,
|
|
668
|
+
// Pass through schema configuration
|
|
669
|
+
tableIdColumns
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Builds table ID column mappings from TABLE_REGISTRY
|
|
674
|
+
* @private
|
|
675
|
+
* @returns Record of table names to ID column names
|
|
676
|
+
*/
|
|
677
|
+
// eslint-disable-next-line complexity
|
|
678
|
+
buildTableIdColumns() {
|
|
679
|
+
const tableIdColumns = {};
|
|
680
|
+
const baseNameConflicts = /* @__PURE__ */ new Set();
|
|
681
|
+
const baseNameMappings = /* @__PURE__ */ new Map();
|
|
682
|
+
for (const tableName of Object.keys(TABLE_REGISTRY)) {
|
|
683
|
+
if (tableName.includes(".")) {
|
|
684
|
+
const baseName = tableName.split(".").pop();
|
|
685
|
+
if (baseNameMappings.has(baseName) || TABLE_REGISTRY[baseName]) {
|
|
686
|
+
baseNameConflicts.add(baseName);
|
|
687
|
+
} else {
|
|
688
|
+
baseNameMappings.set(baseName, tableName);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
for (const [tableName, config] of Object.entries(TABLE_REGISTRY)) {
|
|
693
|
+
const idColumn = config.idColumn ?? "id";
|
|
694
|
+
if (idColumn === "id") continue;
|
|
695
|
+
tableIdColumns[tableName] = idColumn;
|
|
696
|
+
if (!tableName.includes(".")) continue;
|
|
697
|
+
const baseName = tableName.split(".").pop();
|
|
698
|
+
if (!baseNameConflicts.has(baseName)) {
|
|
699
|
+
tableIdColumns[baseName] = idColumn;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return tableIdColumns;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Gets the initialized database service instance
|
|
706
|
+
*
|
|
707
|
+
* @param {string} [adapterName] - Optional named adapter to use instead of default
|
|
708
|
+
* @returns {DatabaseServiceInterface} The database service instance
|
|
709
|
+
* @throws {DatabasePackageError} When database is not initialized or named adapter not found
|
|
710
|
+
*/
|
|
711
|
+
getDatabase(adapterName) {
|
|
712
|
+
if (adapterName) {
|
|
713
|
+
const namedAdapter = this.namedAdapters.get(adapterName);
|
|
714
|
+
if (!namedAdapter) {
|
|
715
|
+
throw new DatabasePackageError(
|
|
716
|
+
`Named adapter '${adapterName}' not found. Available adapters: ${Array.from(this.namedAdapters.keys()).join(", ")}`,
|
|
717
|
+
DATABASE_ERROR_CODES.INIT_FAILED
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
return namedAdapter;
|
|
721
|
+
}
|
|
722
|
+
if (!this.databaseService) {
|
|
723
|
+
throw new DatabasePackageError(
|
|
724
|
+
"Database not initialized. Call DbService.initialize() first.",
|
|
725
|
+
DATABASE_ERROR_CODES.INIT_FAILED
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
return this.databaseService;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Gets a named adapter by name
|
|
732
|
+
*
|
|
733
|
+
* @param {string} name - The name of the adapter
|
|
734
|
+
* @returns {DatabaseServiceInterface} The named adapter instance
|
|
735
|
+
* @throws {DatabasePackageError} When adapter not found
|
|
736
|
+
*/
|
|
737
|
+
getAdapter(name) {
|
|
738
|
+
return this.getDatabase(name);
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Lists all available named adapters
|
|
742
|
+
*
|
|
743
|
+
* @returns {string[]} Array of adapter names
|
|
744
|
+
*/
|
|
745
|
+
getAvailableAdapters() {
|
|
746
|
+
return ["default", ...Array.from(this.namedAdapters.keys())];
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Executes a database transaction with automatic rollback on failure
|
|
750
|
+
*
|
|
751
|
+
* @template T The return type of the transaction callback
|
|
752
|
+
* @param {Function} callback - Function that receives transaction object
|
|
753
|
+
* @returns {Promise<T>} The result of the transaction callback
|
|
754
|
+
* @throws {DatabasePackageError} When transaction fails
|
|
755
|
+
*/
|
|
756
|
+
async transaction(callback) {
|
|
757
|
+
const db = this.getDatabase();
|
|
758
|
+
const result = await db.transaction(callback);
|
|
759
|
+
if (!result.success) {
|
|
760
|
+
const errorMessage = result.error?.message ?? "Transaction failed";
|
|
761
|
+
const error = new DatabasePackageError(errorMessage, DATABASE_ERROR_CODES.TRANSACTION_FAILED);
|
|
762
|
+
this.emitDatabaseError(error, "transaction", { recoverable: true });
|
|
763
|
+
throw error;
|
|
764
|
+
}
|
|
765
|
+
if (result.value === void 0 || result.value === null) {
|
|
766
|
+
const error = new DatabasePackageError(
|
|
767
|
+
"Transaction returned no value",
|
|
768
|
+
DATABASE_ERROR_CODES.INVALID_RESULT
|
|
769
|
+
);
|
|
770
|
+
this.emitDatabaseError(error, "transaction", { recoverable: false });
|
|
771
|
+
throw error;
|
|
772
|
+
}
|
|
773
|
+
return result.value;
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Sets audit context for subsequent operations
|
|
777
|
+
*
|
|
778
|
+
* @param context - Audit context (userId, requestId, etc.)
|
|
779
|
+
*/
|
|
780
|
+
async setAuditContext(context) {
|
|
781
|
+
const db = this.getDatabase();
|
|
782
|
+
await db.setAuditContext(context);
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Performs a health check on the database connection
|
|
786
|
+
*
|
|
787
|
+
* @returns Health check result
|
|
788
|
+
*/
|
|
789
|
+
async healthCheck() {
|
|
790
|
+
try {
|
|
791
|
+
const db = this.getDatabase();
|
|
792
|
+
const result = await db.healthCheck();
|
|
793
|
+
if (result.success && result.value) {
|
|
794
|
+
return {
|
|
795
|
+
isHealthy: result.value.isHealthy,
|
|
796
|
+
responseTime: result.value.responseTime
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
const errorMessage = result.error?.message ?? "Health check failed";
|
|
800
|
+
this.emitDatabaseError(
|
|
801
|
+
new DatabasePackageError(errorMessage, DATABASE_ERROR_CODES.CONNECTION_ERROR),
|
|
802
|
+
"healthCheck",
|
|
803
|
+
{ recoverable: true }
|
|
804
|
+
);
|
|
805
|
+
return {
|
|
806
|
+
isHealthy: false,
|
|
807
|
+
error: errorMessage
|
|
808
|
+
};
|
|
809
|
+
} catch (error) {
|
|
810
|
+
this.emitDatabaseError(error, "healthCheck", { recoverable: true });
|
|
811
|
+
return {
|
|
812
|
+
isHealthy: false,
|
|
813
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Gets the table registry with all known tables and their ID columns
|
|
819
|
+
*
|
|
820
|
+
* @returns The complete table registry
|
|
821
|
+
*/
|
|
822
|
+
static getTableRegistry() {
|
|
823
|
+
return TABLE_REGISTRY;
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Gets ID column for a specific table
|
|
827
|
+
*
|
|
828
|
+
* @param tableName - Name of the table
|
|
829
|
+
* @returns ID column name or 'id' as default
|
|
830
|
+
*/
|
|
831
|
+
static getTableIdColumn(tableName) {
|
|
832
|
+
return TABLE_REGISTRY[tableName]?.idColumn ?? "id";
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Reinitializes the database connection with new config
|
|
836
|
+
*
|
|
837
|
+
* @param {DbServiceConfig} [config] - New configuration
|
|
838
|
+
* @returns {Promise<DbService>} The reinitialized DbService instance
|
|
839
|
+
*/
|
|
840
|
+
static async reinitialize(config) {
|
|
841
|
+
const instance = _DbService.getInstance();
|
|
842
|
+
await instance.close();
|
|
843
|
+
instance.initialized = false;
|
|
844
|
+
return _DbService.initialize(config);
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Closes the database connection and cleans up resources
|
|
848
|
+
*/
|
|
849
|
+
async close() {
|
|
850
|
+
this.databaseService = null;
|
|
851
|
+
this.initialized = false;
|
|
852
|
+
this.config = null;
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Gets the current configuration
|
|
856
|
+
*
|
|
857
|
+
* @returns {DbServiceConfig | null} Current config or null if not initialized
|
|
858
|
+
*/
|
|
859
|
+
getConfig() {
|
|
860
|
+
return this.config;
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Gets the current adapter type
|
|
864
|
+
*
|
|
865
|
+
* @returns The adapter type or null if not initialized
|
|
866
|
+
*/
|
|
867
|
+
getAdapterType() {
|
|
868
|
+
return this.config?.adapter ?? null;
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Creates a dedicated database service instance (NOT the singleton)
|
|
872
|
+
*
|
|
873
|
+
* Use this when you need an isolated database connection with its own configuration
|
|
874
|
+
* that doesn't affect or get affected by the shared singleton instance.
|
|
875
|
+
*
|
|
876
|
+
* @param config - Database service configuration
|
|
877
|
+
* @returns Promise that resolves to a new dedicated DbService instance
|
|
878
|
+
*
|
|
879
|
+
* @example
|
|
880
|
+
* ```typescript
|
|
881
|
+
* // Create a dedicated instance for analytics database
|
|
882
|
+
* const analyticsDb = await DbService.createInstance({
|
|
883
|
+
* adapter: 'sql',
|
|
884
|
+
* sql: { connectionString: process.env.ANALYTICS_DB_URL },
|
|
885
|
+
* cache: { enabled: false }, // No caching for analytics
|
|
886
|
+
* });
|
|
887
|
+
*
|
|
888
|
+
* // This instance is independent from DbService.getInstance()
|
|
889
|
+
* const data = await analyticsDb.getDatabase().list('events');
|
|
890
|
+
*
|
|
891
|
+
* // Clean up when done
|
|
892
|
+
* await analyticsDb.close();
|
|
893
|
+
* ```
|
|
894
|
+
*/
|
|
895
|
+
// eslint-disable-next-line complexity
|
|
896
|
+
static async createInstance(config) {
|
|
897
|
+
const dedicatedInstance = new _DbService();
|
|
898
|
+
const encryptionKey = config.encryption?.key;
|
|
899
|
+
const encryptionConfig = encryptionKey ? {
|
|
900
|
+
enabled: config.encryption?.enabled ?? true,
|
|
901
|
+
key: encryptionKey,
|
|
902
|
+
fields: config.encryption?.fields ?? DEFAULT_ENCRYPTION_FIELDS,
|
|
903
|
+
algorithm: config.encryption?.algorithm ?? "aes-256-gcm"
|
|
904
|
+
} : void 0;
|
|
905
|
+
const mergedConfig = {
|
|
906
|
+
...DEFAULT_CONFIG,
|
|
907
|
+
...config,
|
|
908
|
+
// Deep merge extensions
|
|
909
|
+
softDelete: { ...DEFAULT_CONFIG.softDelete, ...config.softDelete },
|
|
910
|
+
cache: { ...DEFAULT_CONFIG.cache, ...config.cache },
|
|
911
|
+
audit: { ...DEFAULT_CONFIG.audit, ...config.audit },
|
|
912
|
+
// Encryption only if key is available
|
|
913
|
+
encryption: encryptionConfig
|
|
914
|
+
};
|
|
915
|
+
dedicatedInstance.config = mergedConfig;
|
|
916
|
+
const dbConfig = dedicatedInstance.buildDatabaseConfig(mergedConfig);
|
|
917
|
+
dedicatedInstance.databaseService = await createDatabaseService(dbConfig);
|
|
918
|
+
if (mergedConfig.adapters) {
|
|
919
|
+
for (const [name, adapterConfig] of Object.entries(mergedConfig.adapters)) {
|
|
920
|
+
const namedDbConfig = dedicatedInstance.buildDatabaseConfig({
|
|
921
|
+
...mergedConfig,
|
|
922
|
+
adapter: adapterConfig.adapter,
|
|
923
|
+
drizzle: adapterConfig.drizzle,
|
|
924
|
+
supabase: adapterConfig.supabase,
|
|
925
|
+
sql: adapterConfig.sql
|
|
926
|
+
});
|
|
927
|
+
const namedService = await createDatabaseService(namedDbConfig);
|
|
928
|
+
dedicatedInstance.namedAdapters.set(name, namedService);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
dedicatedInstance.initialized = true;
|
|
932
|
+
return dedicatedInstance;
|
|
933
|
+
}
|
|
934
|
+
};
|
|
813
935
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
//
|
|
857
|
-
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
// src/services/StorageService.ts
|
|
939
|
+
var StorageService_exports = {};
|
|
940
|
+
__export(StorageService_exports, {
|
|
941
|
+
StorageService: () => StorageService
|
|
942
|
+
});
|
|
943
|
+
var StorageService;
|
|
944
|
+
var init_StorageService = __esm({
|
|
945
|
+
"src/services/StorageService.ts"() {
|
|
946
|
+
init_CoreEventManager();
|
|
947
|
+
StorageService = class _StorageService {
|
|
948
|
+
constructor() {
|
|
949
|
+
this.storageService = null;
|
|
950
|
+
this.config = null;
|
|
951
|
+
this.initialized = false;
|
|
952
|
+
}
|
|
953
|
+
static {
|
|
954
|
+
__name(this, "StorageService");
|
|
955
|
+
}
|
|
956
|
+
static {
|
|
957
|
+
this.instance = null;
|
|
958
|
+
}
|
|
959
|
+
// ─────────────────────────────────────────────────────────────────
|
|
960
|
+
// Error Handling
|
|
961
|
+
// ─────────────────────────────────────────────────────────────────
|
|
962
|
+
/**
|
|
963
|
+
* Emits a storage error event via CoreEventManager.
|
|
964
|
+
* Called when storage operations fail to integrate with global error handling.
|
|
965
|
+
*/
|
|
966
|
+
emitStorageError(error, operation, options) {
|
|
967
|
+
const payload = {
|
|
968
|
+
error,
|
|
969
|
+
operation,
|
|
970
|
+
fileId: options?.fileId,
|
|
971
|
+
filename: options?.filename,
|
|
972
|
+
recoverable: options?.recoverable ?? false
|
|
973
|
+
};
|
|
974
|
+
CoreEventManager.emit(CORE_EVENTS$1.STORAGE.ERROR, payload);
|
|
975
|
+
}
|
|
976
|
+
// ─────────────────────────────────────────────────────────────────
|
|
977
|
+
// Singleton Management
|
|
978
|
+
// ─────────────────────────────────────────────────────────────────
|
|
979
|
+
/**
|
|
980
|
+
* Gets the singleton instance of StorageService
|
|
981
|
+
*/
|
|
982
|
+
static getInstance() {
|
|
983
|
+
_StorageService.instance ??= new _StorageService();
|
|
984
|
+
return _StorageService.instance;
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Checks if the storage service has been initialized
|
|
988
|
+
*/
|
|
989
|
+
static isInitialized() {
|
|
990
|
+
return _StorageService.instance?.initialized ?? false;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Resets the storage service by clearing the singleton instance
|
|
994
|
+
*/
|
|
995
|
+
static async reset() {
|
|
996
|
+
if (_StorageService.instance) {
|
|
997
|
+
_StorageService.instance.storageService = null;
|
|
998
|
+
_StorageService.instance.config = null;
|
|
999
|
+
_StorageService.instance.initialized = false;
|
|
1000
|
+
_StorageService.instance = null;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Initializes the storage service
|
|
1005
|
+
*
|
|
1006
|
+
* @param config - Storage service configuration
|
|
1007
|
+
* @returns The initialized StorageService instance
|
|
1008
|
+
*/
|
|
1009
|
+
static async initialize(config) {
|
|
1010
|
+
const instance = _StorageService.getInstance();
|
|
1011
|
+
if (instance.initialized) {
|
|
1012
|
+
return instance;
|
|
1013
|
+
}
|
|
1014
|
+
instance.config = config;
|
|
1015
|
+
instance.storageService = new StorageService$1(config);
|
|
1016
|
+
instance.initialized = true;
|
|
1017
|
+
return instance;
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Gets the raw underlying storage service instance without error handling wrapper.
|
|
1021
|
+
* Use this only if you need direct access to the underlying service.
|
|
1022
|
+
*
|
|
1023
|
+
* @returns The raw StorageService instance from @plyaz/storage
|
|
1024
|
+
* @throws {StoragePackageError} When storage is not initialized
|
|
1025
|
+
*/
|
|
1026
|
+
getRawStorage() {
|
|
1027
|
+
if (!this.storageService) {
|
|
1028
|
+
throw new StoragePackageError(
|
|
1029
|
+
"Storage not initialized. Call StorageService.initialize() first or use Core.initialize() with storage config.",
|
|
1030
|
+
STORAGE_ERROR_CODES.INITIALIZATION_FAILED
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
return this.storageService;
|
|
1034
|
+
}
|
|
1035
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1036
|
+
// Service Access
|
|
1037
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1038
|
+
/**
|
|
1039
|
+
* Gets the storage service with automatic error handling.
|
|
1040
|
+
* All method calls are wrapped with try/catch and emit error events on failure.
|
|
1041
|
+
* Any method added to @plyaz/storage will be automatically available.
|
|
1042
|
+
*
|
|
1043
|
+
* @example
|
|
1044
|
+
* ```typescript
|
|
1045
|
+
* const storage = StorageService.getInstance().getStorage();
|
|
1046
|
+
* await storage.uploadFile({ file, filename: 'doc.pdf' });
|
|
1047
|
+
* await storage.deleteFile({ fileId: '123' });
|
|
1048
|
+
* ```
|
|
1049
|
+
*
|
|
1050
|
+
* @returns StorageServiceImpl with automatic error handling
|
|
1051
|
+
*/
|
|
1052
|
+
getStorage() {
|
|
1053
|
+
const self = this;
|
|
1054
|
+
return new Proxy({}, {
|
|
1055
|
+
get(_, prop) {
|
|
1056
|
+
if (typeof prop === "symbol") {
|
|
1057
|
+
return void 0;
|
|
1058
|
+
}
|
|
1059
|
+
const storage = self.getRawStorage();
|
|
1060
|
+
const value = storage[prop];
|
|
1061
|
+
if (typeof value !== "function") {
|
|
1062
|
+
return value;
|
|
1063
|
+
}
|
|
1064
|
+
return (...args) => {
|
|
1065
|
+
try {
|
|
1066
|
+
const result = value.apply(storage, args);
|
|
1067
|
+
if (result instanceof Promise) {
|
|
1068
|
+
return result.catch((error) => {
|
|
1069
|
+
self.emitStorageError(error, prop, { recoverable: true });
|
|
1070
|
+
throw error;
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
return result;
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
self.emitStorageError(error, prop, { recoverable: true });
|
|
1076
|
+
throw error;
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1083
|
+
// Health Check (special handling for response transformation)
|
|
1084
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1085
|
+
/**
|
|
1086
|
+
* Performs a health check on the storage service by checking all adapter health.
|
|
1087
|
+
* This method has special handling to transform the response format.
|
|
1088
|
+
*/
|
|
1089
|
+
async healthCheck() {
|
|
1090
|
+
const startTime = Date.now();
|
|
1091
|
+
try {
|
|
1092
|
+
const storage = this.getRawStorage();
|
|
1093
|
+
if (typeof storage.checkAllAdaptersHealth === "function") {
|
|
1094
|
+
await storage.checkAllAdaptersHealth();
|
|
1095
|
+
}
|
|
1096
|
+
const summary = typeof storage.getHealthSummary === "function" ? storage.getHealthSummary() : null;
|
|
1097
|
+
const responseTime = Date.now() - startTime;
|
|
1098
|
+
return {
|
|
1099
|
+
// Check if all adapters are healthy (healthy count equals total count)
|
|
1100
|
+
isHealthy: summary ? summary.healthy === summary.total : true,
|
|
1101
|
+
responseTime,
|
|
1102
|
+
error: void 0
|
|
1103
|
+
};
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
this.emitStorageError(error, "healthCheck", { recoverable: true });
|
|
1106
|
+
return {
|
|
1107
|
+
isHealthy: false,
|
|
1108
|
+
responseTime: Date.now() - startTime,
|
|
1109
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1114
|
+
// Lifecycle
|
|
1115
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1116
|
+
/**
|
|
1117
|
+
* Gets the current configuration
|
|
1118
|
+
*/
|
|
1119
|
+
getConfig() {
|
|
1120
|
+
return this.config;
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Closes the storage service and cleans up resources
|
|
1124
|
+
*/
|
|
1125
|
+
async close() {
|
|
1126
|
+
if (this.storageService) {
|
|
1127
|
+
await this.storageService.destroy();
|
|
1128
|
+
}
|
|
1129
|
+
this.storageService = null;
|
|
1130
|
+
this.initialized = false;
|
|
1131
|
+
this.config = null;
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Creates a dedicated storage service instance (NOT the singleton)
|
|
1135
|
+
*
|
|
1136
|
+
* Use this when you need an isolated storage connection with its own configuration.
|
|
1137
|
+
*
|
|
1138
|
+
* @param config - Storage service configuration
|
|
1139
|
+
* @returns Promise that resolves to a new dedicated StorageService instance
|
|
1140
|
+
*/
|
|
1141
|
+
static async createInstance(config) {
|
|
1142
|
+
const dedicatedInstance = new _StorageService();
|
|
1143
|
+
dedicatedInstance.config = config;
|
|
1144
|
+
dedicatedInstance.storageService = new StorageService$1(config);
|
|
1145
|
+
dedicatedInstance.initialized = true;
|
|
1146
|
+
return dedicatedInstance;
|
|
1147
|
+
}
|
|
858
1148
|
};
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
// src/services/NotificationService.ts
|
|
1153
|
+
var NotificationService_exports = {};
|
|
1154
|
+
__export(NotificationService_exports, {
|
|
1155
|
+
NotificationService: () => NotificationService
|
|
1156
|
+
});
|
|
1157
|
+
var NotificationService;
|
|
1158
|
+
var init_NotificationService = __esm({
|
|
1159
|
+
"src/services/NotificationService.ts"() {
|
|
1160
|
+
init_CoreEventManager();
|
|
1161
|
+
NotificationService = class _NotificationService {
|
|
1162
|
+
constructor() {
|
|
1163
|
+
this.notificationService = null;
|
|
1164
|
+
this.config = null;
|
|
1165
|
+
this.initialized = false;
|
|
1166
|
+
}
|
|
1167
|
+
static {
|
|
1168
|
+
__name(this, "NotificationService");
|
|
1169
|
+
}
|
|
1170
|
+
static {
|
|
1171
|
+
this.instance = null;
|
|
1172
|
+
}
|
|
1173
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1174
|
+
// Error Handling
|
|
1175
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1176
|
+
/**
|
|
1177
|
+
* Emits a notification error event via CoreEventManager.
|
|
1178
|
+
* Called when notification operations fail to integrate with global error handling.
|
|
1179
|
+
*/
|
|
1180
|
+
emitNotificationError(error, operation, options) {
|
|
1181
|
+
const payload = {
|
|
1182
|
+
error,
|
|
1183
|
+
operation,
|
|
1184
|
+
recipientId: options?.recipientId,
|
|
1185
|
+
channel: options?.channel,
|
|
1186
|
+
recoverable: options?.recoverable ?? false
|
|
1187
|
+
};
|
|
1188
|
+
CoreEventManager.emit(CORE_EVENTS$1.NOTIFICATION.ERROR, payload);
|
|
1189
|
+
}
|
|
1190
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1191
|
+
// Singleton Management
|
|
1192
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1193
|
+
/**
|
|
1194
|
+
* Gets the singleton instance of NotificationService
|
|
1195
|
+
*/
|
|
1196
|
+
static getInstance() {
|
|
1197
|
+
_NotificationService.instance ??= new _NotificationService();
|
|
1198
|
+
return _NotificationService.instance;
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Checks if the notification service has been initialized
|
|
1202
|
+
*/
|
|
1203
|
+
static isInitialized() {
|
|
1204
|
+
return _NotificationService.instance?.initialized ?? false;
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Resets the notification service by clearing the singleton instance
|
|
1208
|
+
*/
|
|
1209
|
+
static async reset() {
|
|
1210
|
+
if (_NotificationService.instance) {
|
|
1211
|
+
_NotificationService.instance.notificationService = null;
|
|
1212
|
+
_NotificationService.instance.config = null;
|
|
1213
|
+
_NotificationService.instance.initialized = false;
|
|
1214
|
+
_NotificationService.instance = null;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Initializes the notification service
|
|
1219
|
+
*
|
|
1220
|
+
* @param config - Notification service configuration
|
|
1221
|
+
* @returns The initialized NotificationService instance
|
|
1222
|
+
*/
|
|
1223
|
+
static async initialize(config) {
|
|
1224
|
+
const instance = _NotificationService.getInstance();
|
|
1225
|
+
if (instance.initialized) {
|
|
1226
|
+
return instance;
|
|
1227
|
+
}
|
|
1228
|
+
instance.config = config;
|
|
1229
|
+
instance.notificationService = new NotificationService$1(config);
|
|
1230
|
+
instance.initialized = true;
|
|
1231
|
+
return instance;
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Gets the raw underlying notification service instance without error handling wrapper.
|
|
1235
|
+
* Use this only if you need direct access to the underlying service.
|
|
1236
|
+
*
|
|
1237
|
+
* @returns The raw NotificationService instance from @plyaz/notifications
|
|
1238
|
+
* @throws {NotificationsPackageError} When notifications is not initialized
|
|
1239
|
+
*/
|
|
1240
|
+
getRawNotifications() {
|
|
1241
|
+
if (!this.notificationService) {
|
|
1242
|
+
throw new NotificationPackageError(
|
|
1243
|
+
"Notifications not initialized. Call NotificationService.initialize() first or use Core.initialize() with notifications config.",
|
|
1244
|
+
NOTIFICATION_ERROR_CODES.INITIALIZATION_FAILED
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
return this.notificationService;
|
|
1248
|
+
}
|
|
1249
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1250
|
+
// Service Access
|
|
1251
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1252
|
+
/**
|
|
1253
|
+
* Gets the notification service with automatic error handling.
|
|
1254
|
+
* All method calls are wrapped with try/catch and emit error events on failure.
|
|
1255
|
+
* Any method added to @plyaz/notifications will be automatically available.
|
|
1256
|
+
*
|
|
1257
|
+
* @example
|
|
1258
|
+
* ```typescript
|
|
1259
|
+
* const notifications = NotificationService.getInstance().getNotifications();
|
|
1260
|
+
* await notifications.sendEmail({ to: 'user@example.com', templateId: 'welcome' });
|
|
1261
|
+
* await notifications.sendSMS({ to: '+1234567890', message: 'Hello!' });
|
|
1262
|
+
* ```
|
|
1263
|
+
*
|
|
1264
|
+
* @returns NotificationServiceImpl with automatic error handling
|
|
1265
|
+
*/
|
|
1266
|
+
getNotifications() {
|
|
1267
|
+
const self = this;
|
|
1268
|
+
return new Proxy({}, {
|
|
1269
|
+
get(_, prop) {
|
|
1270
|
+
if (typeof prop === "symbol") {
|
|
1271
|
+
return void 0;
|
|
1272
|
+
}
|
|
1273
|
+
const notifications = self.getRawNotifications();
|
|
1274
|
+
const value = notifications[prop];
|
|
1275
|
+
if (typeof value !== "function") {
|
|
1276
|
+
return value;
|
|
1277
|
+
}
|
|
1278
|
+
return (...args) => {
|
|
1279
|
+
try {
|
|
1280
|
+
const result = value.apply(notifications, args);
|
|
1281
|
+
if (result instanceof Promise) {
|
|
1282
|
+
return result.catch((error) => {
|
|
1283
|
+
self.emitNotificationError(error, prop, { recoverable: true });
|
|
1284
|
+
throw error;
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
return result;
|
|
1288
|
+
} catch (error) {
|
|
1289
|
+
self.emitNotificationError(error, prop, { recoverable: true });
|
|
1290
|
+
throw error;
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
870
1294
|
});
|
|
871
|
-
const namedService = await createDatabaseService(namedDbConfig);
|
|
872
|
-
dedicatedInstance.namedAdapters.set(name, namedService);
|
|
873
1295
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1296
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1297
|
+
// Health Check (special handling for response transformation)
|
|
1298
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1299
|
+
/**
|
|
1300
|
+
* Performs a health check on the notification service.
|
|
1301
|
+
* This method has special handling to transform the response format.
|
|
1302
|
+
*/
|
|
1303
|
+
async healthCheck() {
|
|
1304
|
+
try {
|
|
1305
|
+
const notifications = this.getRawNotifications();
|
|
1306
|
+
const result = await notifications.healthCheck();
|
|
1307
|
+
return {
|
|
1308
|
+
isHealthy: result.healthy,
|
|
1309
|
+
providers: result.providers,
|
|
1310
|
+
// Use optional chaining since error may not exist on all health check results
|
|
1311
|
+
error: "error" in result ? result.error : void 0
|
|
1312
|
+
};
|
|
1313
|
+
} catch (error) {
|
|
1314
|
+
this.emitNotificationError(error, "healthCheck", { recoverable: true });
|
|
1315
|
+
return {
|
|
1316
|
+
isHealthy: false,
|
|
1317
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1322
|
+
// Lifecycle
|
|
1323
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1324
|
+
/**
|
|
1325
|
+
* Gets the current configuration
|
|
1326
|
+
*/
|
|
1327
|
+
getConfig() {
|
|
1328
|
+
return this.config;
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Closes the notification service and cleans up resources
|
|
1332
|
+
*/
|
|
1333
|
+
async close() {
|
|
1334
|
+
this.notificationService = null;
|
|
1335
|
+
this.initialized = false;
|
|
1336
|
+
this.config = null;
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Creates a dedicated notification service instance (NOT the singleton)
|
|
1340
|
+
*
|
|
1341
|
+
* Use this when you need an isolated notification service with its own configuration.
|
|
1342
|
+
*
|
|
1343
|
+
* @param config - Notification service configuration
|
|
1344
|
+
* @returns Promise that resolves to a new dedicated NotificationService instance
|
|
1345
|
+
*/
|
|
1346
|
+
static async createInstance(config) {
|
|
1347
|
+
const dedicatedInstance = new _NotificationService();
|
|
1348
|
+
dedicatedInstance.config = config;
|
|
1349
|
+
dedicatedInstance.notificationService = new NotificationService$1(config);
|
|
1350
|
+
dedicatedInstance.initialized = true;
|
|
1351
|
+
return dedicatedInstance;
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
877
1354
|
}
|
|
878
|
-
};
|
|
1355
|
+
});
|
|
1356
|
+
|
|
1357
|
+
// src/init/CoreInitializer.ts
|
|
1358
|
+
init_DbService();
|
|
1359
|
+
|
|
1360
|
+
// src/services/ApiClientService.ts
|
|
1361
|
+
init_CoreEventManager();
|
|
879
1362
|
var MIN_RETRY_ATTEMPTS_PRODUCTION = 3;
|
|
880
1363
|
function getConfigForEnvironment(env) {
|
|
881
1364
|
switch (env) {
|
|
@@ -1090,7 +1573,7 @@ var ApiClientService = class _ApiClientService {
|
|
|
1090
1573
|
requestId
|
|
1091
1574
|
}
|
|
1092
1575
|
};
|
|
1093
|
-
CoreEventManager.emit(CORE_EVENTS
|
|
1576
|
+
CoreEventManager.emit(CORE_EVENTS.SYSTEM.ERROR, { errors: [serializedError] });
|
|
1094
1577
|
} else {
|
|
1095
1578
|
const serializedErrors = errorDetails.map(
|
|
1096
1579
|
(detail, index) => ({
|
|
@@ -1114,7 +1597,7 @@ var ApiClientService = class _ApiClientService {
|
|
|
1114
1597
|
}
|
|
1115
1598
|
})
|
|
1116
1599
|
);
|
|
1117
|
-
CoreEventManager.emit(CORE_EVENTS
|
|
1600
|
+
CoreEventManager.emit(CORE_EVENTS.SYSTEM.ERROR, { errors: serializedErrors });
|
|
1118
1601
|
}
|
|
1119
1602
|
} catch (e) {
|
|
1120
1603
|
console.error("[ApiClientService] Failed to emit error event:", e);
|
|
@@ -1267,7 +1750,7 @@ var ApiClientService = class _ApiClientService {
|
|
|
1267
1750
|
error,
|
|
1268
1751
|
duration: options.duration
|
|
1269
1752
|
};
|
|
1270
|
-
CoreEventManager.emit(CORE_EVENTS
|
|
1753
|
+
CoreEventManager.emit(CORE_EVENTS.API.REQUEST_ERROR, payload);
|
|
1271
1754
|
if (options.rethrow && error instanceof Error) {
|
|
1272
1755
|
throw error;
|
|
1273
1756
|
}
|
|
@@ -1945,582 +2428,190 @@ var CacheManager = class {
|
|
|
1945
2428
|
}
|
|
1946
2429
|
}
|
|
1947
2430
|
/**
|
|
1948
|
-
* Disposes of the cache manager and cleans up resources.
|
|
1949
|
-
*
|
|
1950
|
-
* @returns Promise that resolves when cleanup is complete
|
|
1951
|
-
*/
|
|
1952
|
-
async dispose() {
|
|
1953
|
-
await this.strategy.dispose?.();
|
|
1954
|
-
}
|
|
1955
|
-
};
|
|
1956
|
-
var CacheService = class _CacheService {
|
|
1957
|
-
/** Private constructor to enforce singleton */
|
|
1958
|
-
constructor() {
|
|
1959
|
-
/** Cache manager instance */
|
|
1960
|
-
this.cacheManager = null;
|
|
1961
|
-
/** Configuration used to initialize cache */
|
|
1962
|
-
this.config = null;
|
|
1963
|
-
/** Logger instance */
|
|
1964
|
-
this.logger = new CoreLogger({ service: "CacheService" });
|
|
1965
|
-
}
|
|
1966
|
-
static {
|
|
1967
|
-
__name(this, "CacheService");
|
|
1968
|
-
}
|
|
1969
|
-
static {
|
|
1970
|
-
/** Singleton instance */
|
|
1971
|
-
this.instance = null;
|
|
1972
|
-
}
|
|
1973
|
-
/**
|
|
1974
|
-
* Initialize the cache service with configuration
|
|
1975
|
-
*
|
|
1976
|
-
* @param config - Cache configuration
|
|
1977
|
-
* @throws {CorePackageError} If already initialized or config is invalid
|
|
1978
|
-
*
|
|
1979
|
-
* @example
|
|
1980
|
-
* ```typescript
|
|
1981
|
-
* await CacheService.initialize({
|
|
1982
|
-
* strategy: 'redis',
|
|
1983
|
-
* isEnabled: true,
|
|
1984
|
-
* ttl: 300,
|
|
1985
|
-
* prefix: 'app',
|
|
1986
|
-
* redis: { url: process.env.REDIS_URL },
|
|
1987
|
-
* });
|
|
1988
|
-
* ```
|
|
1989
|
-
*/
|
|
1990
|
-
static async initialize(config) {
|
|
1991
|
-
if (this.instance?.cacheManager) {
|
|
1992
|
-
throw new CorePackageError(
|
|
1993
|
-
"CacheService is already initialized. Call reset() first if you need to reinitialize.",
|
|
1994
|
-
ERROR_CODES$1.VALIDATION_ERROR
|
|
1995
|
-
);
|
|
1996
|
-
}
|
|
1997
|
-
const service = this.instance ?? new _CacheService();
|
|
1998
|
-
this.instance = service;
|
|
1999
|
-
try {
|
|
2000
|
-
service.logger.info("[CacheService] Initializing cache...", {
|
|
2001
|
-
strategy: config.strategy,
|
|
2002
|
-
isEnabled: config.isEnabled,
|
|
2003
|
-
ttl: config.ttl
|
|
2004
|
-
});
|
|
2005
|
-
service.validateConfig(config);
|
|
2006
|
-
const cacheConfig = {
|
|
2007
|
-
...config,
|
|
2008
|
-
ttl: config.ttl ?? TIME_CONSTANTS.DEFAULT_CACHE_TTL
|
|
2009
|
-
};
|
|
2010
|
-
service.cacheManager = new CacheManager(cacheConfig);
|
|
2011
|
-
service.config = config;
|
|
2012
|
-
service.logger.info("[CacheService] Cache initialized successfully", {
|
|
2013
|
-
strategy: config.strategy
|
|
2014
|
-
});
|
|
2015
|
-
} catch (error) {
|
|
2016
|
-
service.logger.error("[CacheService] Failed to initialize cache", { error });
|
|
2017
|
-
throw new CorePackageError(
|
|
2018
|
-
`Failed to initialize cache: ${error instanceof Error ? error.message : String(error)}`,
|
|
2019
|
-
ERROR_CODES$1.VALIDATION_ERROR,
|
|
2020
|
-
{ cause: error instanceof Error ? error : void 0 }
|
|
2021
|
-
);
|
|
2022
|
-
}
|
|
2023
|
-
}
|
|
2024
|
-
/**
|
|
2025
|
-
* Get the singleton instance
|
|
2026
|
-
*
|
|
2027
|
-
* @throws {CorePackageError} If not initialized
|
|
2028
|
-
* @returns CacheService singleton instance
|
|
2029
|
-
*
|
|
2030
|
-
* @example
|
|
2031
|
-
* ```typescript
|
|
2032
|
-
* const cacheService = CacheService.getInstance();
|
|
2033
|
-
* const cache = cacheService.getCacheManager();
|
|
2034
|
-
* ```
|
|
2035
|
-
*/
|
|
2036
|
-
static getInstance() {
|
|
2037
|
-
if (!this.instance) {
|
|
2038
|
-
throw new CorePackageError(
|
|
2039
|
-
"CacheService not initialized. Call CacheService.initialize() first.",
|
|
2040
|
-
ERROR_CODES$1.CLIENT_INITIALIZATION_FAILED
|
|
2041
|
-
);
|
|
2042
|
-
}
|
|
2043
|
-
return this.instance;
|
|
2044
|
-
}
|
|
2045
|
-
/**
|
|
2046
|
-
* Get the cache manager instance
|
|
2047
|
-
*
|
|
2048
|
-
* @throws {CorePackageError} If cache not initialized
|
|
2049
|
-
* @returns CacheManager instance
|
|
2050
|
-
*
|
|
2051
|
-
* @example
|
|
2052
|
-
* ```typescript
|
|
2053
|
-
* const cache = CacheService.getInstance().getCacheManager();
|
|
2054
|
-
* await cache.set('user:123', userData);
|
|
2055
|
-
* const user = await cache.get('user:123');
|
|
2056
|
-
* ```
|
|
2057
|
-
*/
|
|
2058
|
-
getCacheManager() {
|
|
2059
|
-
if (!this.cacheManager) {
|
|
2060
|
-
throw new CorePackageError(
|
|
2061
|
-
"Cache manager not initialized. Call CacheService.initialize() first.",
|
|
2062
|
-
ERROR_CODES$1.CLIENT_INITIALIZATION_FAILED
|
|
2063
|
-
);
|
|
2064
|
-
}
|
|
2065
|
-
return this.cacheManager;
|
|
2066
|
-
}
|
|
2067
|
-
/**
|
|
2068
|
-
* Get current cache configuration
|
|
2069
|
-
*
|
|
2070
|
-
* @returns Current cache configuration or null if not initialized
|
|
2071
|
-
*/
|
|
2072
|
-
getConfig() {
|
|
2073
|
-
return this.config;
|
|
2074
|
-
}
|
|
2075
|
-
/**
|
|
2076
|
-
* Check if cache is initialized and enabled
|
|
2077
|
-
*
|
|
2078
|
-
* @returns True if cache is ready to use
|
|
2079
|
-
*/
|
|
2080
|
-
isInitialized() {
|
|
2081
|
-
return this.cacheManager !== null && (this.config?.isEnabled ?? false);
|
|
2082
|
-
}
|
|
2083
|
-
/**
|
|
2084
|
-
* Reset the cache service (useful for testing)
|
|
2085
|
-
*
|
|
2086
|
-
* @internal
|
|
2087
|
-
*/
|
|
2088
|
-
static reset() {
|
|
2089
|
-
if (this.instance?.cacheManager) {
|
|
2090
|
-
this.instance.logger.info("[CacheService] Resetting cache service");
|
|
2091
|
-
this.instance.cacheManager.clear().catch((error) => {
|
|
2092
|
-
this.instance?.logger.error("[CacheService] Error clearing cache during reset", { error });
|
|
2093
|
-
});
|
|
2094
|
-
}
|
|
2095
|
-
this.instance = null;
|
|
2096
|
-
}
|
|
2097
|
-
/**
|
|
2098
|
-
* Validate cache configuration
|
|
2099
|
-
*
|
|
2100
|
-
* @param config - Configuration to validate
|
|
2101
|
-
* @throws {CorePackageError} If configuration is invalid
|
|
2102
|
-
* @private
|
|
2103
|
-
*/
|
|
2104
|
-
// eslint-disable-next-line complexity
|
|
2105
|
-
validateConfig(config) {
|
|
2106
|
-
if (!CACHE_STRATEGIES.includes(config.strategy)) {
|
|
2107
|
-
throw new CorePackageError(
|
|
2108
|
-
`Invalid cache strategy: ${config.strategy}. Must be one of: ${CACHE_STRATEGIES.join(", ")}`,
|
|
2109
|
-
ERROR_CODES$1.VALIDATION_ERROR
|
|
2110
|
-
);
|
|
2111
|
-
}
|
|
2112
|
-
if (config.ttl !== void 0 && (config.ttl < 0 || !Number.isFinite(config.ttl))) {
|
|
2113
|
-
throw new CorePackageError(
|
|
2114
|
-
`Invalid TTL value: ${config.ttl}. Must be a positive number.`,
|
|
2115
|
-
ERROR_CODES$1.VALIDATION_ERROR
|
|
2116
|
-
);
|
|
2117
|
-
}
|
|
2118
|
-
if (config.strategy === "redis") {
|
|
2119
|
-
if (!config.redis?.url && !config.redis?.host) {
|
|
2120
|
-
throw new CorePackageError(
|
|
2121
|
-
"Redis strategy requires either redis.url or redis.host configuration",
|
|
2122
|
-
ERROR_CODES$1.VALIDATION_ERROR
|
|
2123
|
-
);
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
this.logger.debug("[CacheService] Configuration validated successfully");
|
|
2127
|
-
}
|
|
2128
|
-
};
|
|
2129
|
-
var StorageService = class _StorageService {
|
|
2130
|
-
constructor() {
|
|
2131
|
-
this.storageService = null;
|
|
2132
|
-
this.config = null;
|
|
2133
|
-
this.initialized = false;
|
|
2134
|
-
}
|
|
2135
|
-
static {
|
|
2136
|
-
__name(this, "StorageService");
|
|
2137
|
-
}
|
|
2138
|
-
static {
|
|
2139
|
-
this.instance = null;
|
|
2140
|
-
}
|
|
2141
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2142
|
-
// Error Handling
|
|
2143
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2144
|
-
/**
|
|
2145
|
-
* Emits a storage error event via CoreEventManager.
|
|
2146
|
-
* Called when storage operations fail to integrate with global error handling.
|
|
2147
|
-
*/
|
|
2148
|
-
emitStorageError(error, operation, options) {
|
|
2149
|
-
const payload = {
|
|
2150
|
-
error,
|
|
2151
|
-
operation,
|
|
2152
|
-
fileId: options?.fileId,
|
|
2153
|
-
filename: options?.filename,
|
|
2154
|
-
recoverable: options?.recoverable ?? false
|
|
2155
|
-
};
|
|
2156
|
-
CoreEventManager.emit(CORE_EVENTS.STORAGE.ERROR, payload);
|
|
2157
|
-
}
|
|
2158
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2159
|
-
// Singleton Management
|
|
2160
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2161
|
-
/**
|
|
2162
|
-
* Gets the singleton instance of StorageService
|
|
2163
|
-
*/
|
|
2164
|
-
static getInstance() {
|
|
2165
|
-
_StorageService.instance ??= new _StorageService();
|
|
2166
|
-
return _StorageService.instance;
|
|
2167
|
-
}
|
|
2168
|
-
/**
|
|
2169
|
-
* Checks if the storage service has been initialized
|
|
2170
|
-
*/
|
|
2171
|
-
static isInitialized() {
|
|
2172
|
-
return _StorageService.instance?.initialized ?? false;
|
|
2173
|
-
}
|
|
2174
|
-
/**
|
|
2175
|
-
* Resets the storage service by clearing the singleton instance
|
|
2176
|
-
*/
|
|
2177
|
-
static async reset() {
|
|
2178
|
-
if (_StorageService.instance) {
|
|
2179
|
-
_StorageService.instance.storageService = null;
|
|
2180
|
-
_StorageService.instance.config = null;
|
|
2181
|
-
_StorageService.instance.initialized = false;
|
|
2182
|
-
_StorageService.instance = null;
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
/**
|
|
2186
|
-
* Initializes the storage service
|
|
2187
|
-
*
|
|
2188
|
-
* @param config - Storage service configuration
|
|
2189
|
-
* @returns The initialized StorageService instance
|
|
2190
|
-
*/
|
|
2191
|
-
static async initialize(config) {
|
|
2192
|
-
const instance = _StorageService.getInstance();
|
|
2193
|
-
if (instance.initialized) {
|
|
2194
|
-
return instance;
|
|
2195
|
-
}
|
|
2196
|
-
instance.config = config;
|
|
2197
|
-
instance.storageService = new StorageService$1(config);
|
|
2198
|
-
instance.initialized = true;
|
|
2199
|
-
return instance;
|
|
2200
|
-
}
|
|
2201
|
-
/**
|
|
2202
|
-
* Gets the raw underlying storage service instance without error handling wrapper.
|
|
2203
|
-
* Use this only if you need direct access to the underlying service.
|
|
2204
|
-
*
|
|
2205
|
-
* @returns The raw StorageService instance from @plyaz/storage
|
|
2206
|
-
* @throws {StoragePackageError} When storage is not initialized
|
|
2207
|
-
*/
|
|
2208
|
-
getRawStorage() {
|
|
2209
|
-
if (!this.storageService) {
|
|
2210
|
-
throw new StoragePackageError(
|
|
2211
|
-
"Storage not initialized. Call StorageService.initialize() first or use Core.initialize() with storage config.",
|
|
2212
|
-
STORAGE_ERROR_CODES.INITIALIZATION_FAILED
|
|
2213
|
-
);
|
|
2214
|
-
}
|
|
2215
|
-
return this.storageService;
|
|
2216
|
-
}
|
|
2217
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2218
|
-
// Service Access
|
|
2219
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2220
|
-
/**
|
|
2221
|
-
* Gets the storage service with automatic error handling.
|
|
2222
|
-
* All method calls are wrapped with try/catch and emit error events on failure.
|
|
2223
|
-
* Any method added to @plyaz/storage will be automatically available.
|
|
2224
|
-
*
|
|
2225
|
-
* @example
|
|
2226
|
-
* ```typescript
|
|
2227
|
-
* const storage = StorageService.getInstance().getStorage();
|
|
2228
|
-
* await storage.uploadFile({ file, filename: 'doc.pdf' });
|
|
2229
|
-
* await storage.deleteFile({ fileId: '123' });
|
|
2230
|
-
* ```
|
|
2231
|
-
*
|
|
2232
|
-
* @returns StorageServiceImpl with automatic error handling
|
|
2233
|
-
*/
|
|
2234
|
-
getStorage() {
|
|
2235
|
-
const self = this;
|
|
2236
|
-
return new Proxy({}, {
|
|
2237
|
-
get(_, prop) {
|
|
2238
|
-
if (typeof prop === "symbol") {
|
|
2239
|
-
return void 0;
|
|
2240
|
-
}
|
|
2241
|
-
const storage = self.getRawStorage();
|
|
2242
|
-
const value = storage[prop];
|
|
2243
|
-
if (typeof value !== "function") {
|
|
2244
|
-
return value;
|
|
2245
|
-
}
|
|
2246
|
-
return (...args) => {
|
|
2247
|
-
try {
|
|
2248
|
-
const result = value.apply(storage, args);
|
|
2249
|
-
if (result instanceof Promise) {
|
|
2250
|
-
return result.catch((error) => {
|
|
2251
|
-
self.emitStorageError(error, prop, { recoverable: true });
|
|
2252
|
-
throw error;
|
|
2253
|
-
});
|
|
2254
|
-
}
|
|
2255
|
-
return result;
|
|
2256
|
-
} catch (error) {
|
|
2257
|
-
self.emitStorageError(error, prop, { recoverable: true });
|
|
2258
|
-
throw error;
|
|
2259
|
-
}
|
|
2260
|
-
};
|
|
2261
|
-
}
|
|
2262
|
-
});
|
|
2263
|
-
}
|
|
2264
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2265
|
-
// Health Check (special handling for response transformation)
|
|
2266
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2267
|
-
/**
|
|
2268
|
-
* Performs a health check on the storage service by checking all adapter health.
|
|
2269
|
-
* This method has special handling to transform the response format.
|
|
2270
|
-
*/
|
|
2271
|
-
async healthCheck() {
|
|
2272
|
-
const startTime = Date.now();
|
|
2273
|
-
try {
|
|
2274
|
-
const storage = this.getRawStorage();
|
|
2275
|
-
if (typeof storage.checkAllAdaptersHealth === "function") {
|
|
2276
|
-
await storage.checkAllAdaptersHealth();
|
|
2277
|
-
}
|
|
2278
|
-
const summary = typeof storage.getHealthSummary === "function" ? storage.getHealthSummary() : null;
|
|
2279
|
-
const responseTime = Date.now() - startTime;
|
|
2280
|
-
return {
|
|
2281
|
-
// Check if all adapters are healthy (healthy count equals total count)
|
|
2282
|
-
isHealthy: summary ? summary.healthy === summary.total : true,
|
|
2283
|
-
responseTime,
|
|
2284
|
-
error: void 0
|
|
2285
|
-
};
|
|
2286
|
-
} catch (error) {
|
|
2287
|
-
this.emitStorageError(error, "healthCheck", { recoverable: true });
|
|
2288
|
-
return {
|
|
2289
|
-
isHealthy: false,
|
|
2290
|
-
responseTime: Date.now() - startTime,
|
|
2291
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
2292
|
-
};
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2296
|
-
// Lifecycle
|
|
2297
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2298
|
-
/**
|
|
2299
|
-
* Gets the current configuration
|
|
2300
|
-
*/
|
|
2301
|
-
getConfig() {
|
|
2302
|
-
return this.config;
|
|
2303
|
-
}
|
|
2304
|
-
/**
|
|
2305
|
-
* Closes the storage service and cleans up resources
|
|
2306
|
-
*/
|
|
2307
|
-
async close() {
|
|
2308
|
-
if (this.storageService) {
|
|
2309
|
-
await this.storageService.destroy();
|
|
2310
|
-
}
|
|
2311
|
-
this.storageService = null;
|
|
2312
|
-
this.initialized = false;
|
|
2313
|
-
this.config = null;
|
|
2314
|
-
}
|
|
2315
|
-
/**
|
|
2316
|
-
* Creates a dedicated storage service instance (NOT the singleton)
|
|
2317
|
-
*
|
|
2318
|
-
* Use this when you need an isolated storage connection with its own configuration.
|
|
2319
|
-
*
|
|
2320
|
-
* @param config - Storage service configuration
|
|
2321
|
-
* @returns Promise that resolves to a new dedicated StorageService instance
|
|
2322
|
-
*/
|
|
2323
|
-
static async createInstance(config) {
|
|
2324
|
-
const dedicatedInstance = new _StorageService();
|
|
2325
|
-
dedicatedInstance.config = config;
|
|
2326
|
-
dedicatedInstance.storageService = new StorageService$1(config);
|
|
2327
|
-
dedicatedInstance.initialized = true;
|
|
2328
|
-
return dedicatedInstance;
|
|
2329
|
-
}
|
|
2330
|
-
};
|
|
2331
|
-
var NotificationService = class _NotificationService {
|
|
2332
|
-
constructor() {
|
|
2333
|
-
this.notificationService = null;
|
|
2334
|
-
this.config = null;
|
|
2335
|
-
this.initialized = false;
|
|
2336
|
-
}
|
|
2337
|
-
static {
|
|
2338
|
-
__name(this, "NotificationService");
|
|
2339
|
-
}
|
|
2340
|
-
static {
|
|
2341
|
-
this.instance = null;
|
|
2342
|
-
}
|
|
2343
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2344
|
-
// Error Handling
|
|
2345
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2346
|
-
/**
|
|
2347
|
-
* Emits a notification error event via CoreEventManager.
|
|
2348
|
-
* Called when notification operations fail to integrate with global error handling.
|
|
2349
|
-
*/
|
|
2350
|
-
emitNotificationError(error, operation, options) {
|
|
2351
|
-
const payload = {
|
|
2352
|
-
error,
|
|
2353
|
-
operation,
|
|
2354
|
-
recipientId: options?.recipientId,
|
|
2355
|
-
channel: options?.channel,
|
|
2356
|
-
recoverable: options?.recoverable ?? false
|
|
2357
|
-
};
|
|
2358
|
-
CoreEventManager.emit(CORE_EVENTS.NOTIFICATION.ERROR, payload);
|
|
2359
|
-
}
|
|
2360
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2361
|
-
// Singleton Management
|
|
2362
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2363
|
-
/**
|
|
2364
|
-
* Gets the singleton instance of NotificationService
|
|
2365
|
-
*/
|
|
2366
|
-
static getInstance() {
|
|
2367
|
-
_NotificationService.instance ??= new _NotificationService();
|
|
2368
|
-
return _NotificationService.instance;
|
|
2369
|
-
}
|
|
2370
|
-
/**
|
|
2371
|
-
* Checks if the notification service has been initialized
|
|
2372
|
-
*/
|
|
2373
|
-
static isInitialized() {
|
|
2374
|
-
return _NotificationService.instance?.initialized ?? false;
|
|
2375
|
-
}
|
|
2376
|
-
/**
|
|
2377
|
-
* Resets the notification service by clearing the singleton instance
|
|
2378
|
-
*/
|
|
2379
|
-
static async reset() {
|
|
2380
|
-
if (_NotificationService.instance) {
|
|
2381
|
-
_NotificationService.instance.notificationService = null;
|
|
2382
|
-
_NotificationService.instance.config = null;
|
|
2383
|
-
_NotificationService.instance.initialized = false;
|
|
2384
|
-
_NotificationService.instance = null;
|
|
2385
|
-
}
|
|
2386
|
-
}
|
|
2387
|
-
/**
|
|
2388
|
-
* Initializes the notification service
|
|
2431
|
+
* Disposes of the cache manager and cleans up resources.
|
|
2389
2432
|
*
|
|
2390
|
-
* @
|
|
2391
|
-
* @returns The initialized NotificationService instance
|
|
2433
|
+
* @returns Promise that resolves when cleanup is complete
|
|
2392
2434
|
*/
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
instance
|
|
2401
|
-
|
|
2435
|
+
async dispose() {
|
|
2436
|
+
await this.strategy.dispose?.();
|
|
2437
|
+
}
|
|
2438
|
+
};
|
|
2439
|
+
var CacheService = class _CacheService {
|
|
2440
|
+
/** Private constructor to enforce singleton */
|
|
2441
|
+
constructor() {
|
|
2442
|
+
/** Cache manager instance */
|
|
2443
|
+
this.cacheManager = null;
|
|
2444
|
+
/** Configuration used to initialize cache */
|
|
2445
|
+
this.config = null;
|
|
2446
|
+
/** Logger instance */
|
|
2447
|
+
this.logger = new CoreLogger({ service: "CacheService" });
|
|
2448
|
+
}
|
|
2449
|
+
static {
|
|
2450
|
+
__name(this, "CacheService");
|
|
2451
|
+
}
|
|
2452
|
+
static {
|
|
2453
|
+
/** Singleton instance */
|
|
2454
|
+
this.instance = null;
|
|
2402
2455
|
}
|
|
2403
2456
|
/**
|
|
2404
|
-
*
|
|
2405
|
-
*
|
|
2457
|
+
* Initialize the cache service with configuration
|
|
2458
|
+
*
|
|
2459
|
+
* @param config - Cache configuration
|
|
2460
|
+
* @throws {CorePackageError} If already initialized or config is invalid
|
|
2406
2461
|
*
|
|
2407
|
-
* @
|
|
2408
|
-
*
|
|
2462
|
+
* @example
|
|
2463
|
+
* ```typescript
|
|
2464
|
+
* await CacheService.initialize({
|
|
2465
|
+
* strategy: 'redis',
|
|
2466
|
+
* isEnabled: true,
|
|
2467
|
+
* ttl: 300,
|
|
2468
|
+
* prefix: 'app',
|
|
2469
|
+
* redis: { url: process.env.REDIS_URL },
|
|
2470
|
+
* });
|
|
2471
|
+
* ```
|
|
2409
2472
|
*/
|
|
2410
|
-
|
|
2411
|
-
if (
|
|
2412
|
-
throw new
|
|
2413
|
-
"
|
|
2414
|
-
|
|
2473
|
+
static async initialize(config) {
|
|
2474
|
+
if (this.instance?.cacheManager) {
|
|
2475
|
+
throw new CorePackageError(
|
|
2476
|
+
"CacheService is already initialized. Call reset() first if you need to reinitialize.",
|
|
2477
|
+
ERROR_CODES$1.VALIDATION_ERROR
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2480
|
+
const service = this.instance ?? new _CacheService();
|
|
2481
|
+
this.instance = service;
|
|
2482
|
+
try {
|
|
2483
|
+
service.logger.info("[CacheService] Initializing cache...", {
|
|
2484
|
+
strategy: config.strategy,
|
|
2485
|
+
isEnabled: config.isEnabled,
|
|
2486
|
+
ttl: config.ttl
|
|
2487
|
+
});
|
|
2488
|
+
service.validateConfig(config);
|
|
2489
|
+
const cacheConfig = {
|
|
2490
|
+
...config,
|
|
2491
|
+
ttl: config.ttl ?? TIME_CONSTANTS.DEFAULT_CACHE_TTL
|
|
2492
|
+
};
|
|
2493
|
+
service.cacheManager = new CacheManager(cacheConfig);
|
|
2494
|
+
service.config = config;
|
|
2495
|
+
service.logger.info("[CacheService] Cache initialized successfully", {
|
|
2496
|
+
strategy: config.strategy
|
|
2497
|
+
});
|
|
2498
|
+
} catch (error) {
|
|
2499
|
+
service.logger.error("[CacheService] Failed to initialize cache", { error });
|
|
2500
|
+
throw new CorePackageError(
|
|
2501
|
+
`Failed to initialize cache: ${error instanceof Error ? error.message : String(error)}`,
|
|
2502
|
+
ERROR_CODES$1.VALIDATION_ERROR,
|
|
2503
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
2415
2504
|
);
|
|
2416
2505
|
}
|
|
2417
|
-
return this.notificationService;
|
|
2418
2506
|
}
|
|
2419
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2420
|
-
// Service Access
|
|
2421
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2422
2507
|
/**
|
|
2423
|
-
*
|
|
2424
|
-
*
|
|
2425
|
-
*
|
|
2508
|
+
* Get the singleton instance
|
|
2509
|
+
*
|
|
2510
|
+
* @throws {CorePackageError} If not initialized
|
|
2511
|
+
* @returns CacheService singleton instance
|
|
2426
2512
|
*
|
|
2427
2513
|
* @example
|
|
2428
2514
|
* ```typescript
|
|
2429
|
-
* const
|
|
2430
|
-
*
|
|
2431
|
-
* await notifications.sendSMS({ to: '+1234567890', message: 'Hello!' });
|
|
2515
|
+
* const cacheService = CacheService.getInstance();
|
|
2516
|
+
* const cache = cacheService.getCacheManager();
|
|
2432
2517
|
* ```
|
|
2433
|
-
*
|
|
2434
|
-
* @returns NotificationServiceImpl with automatic error handling
|
|
2435
2518
|
*/
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
const value = notifications[prop];
|
|
2445
|
-
if (typeof value !== "function") {
|
|
2446
|
-
return value;
|
|
2447
|
-
}
|
|
2448
|
-
return (...args) => {
|
|
2449
|
-
try {
|
|
2450
|
-
const result = value.apply(notifications, args);
|
|
2451
|
-
if (result instanceof Promise) {
|
|
2452
|
-
return result.catch((error) => {
|
|
2453
|
-
self.emitNotificationError(error, prop, { recoverable: true });
|
|
2454
|
-
throw error;
|
|
2455
|
-
});
|
|
2456
|
-
}
|
|
2457
|
-
return result;
|
|
2458
|
-
} catch (error) {
|
|
2459
|
-
self.emitNotificationError(error, prop, { recoverable: true });
|
|
2460
|
-
throw error;
|
|
2461
|
-
}
|
|
2462
|
-
};
|
|
2463
|
-
}
|
|
2464
|
-
});
|
|
2519
|
+
static getInstance() {
|
|
2520
|
+
if (!this.instance) {
|
|
2521
|
+
throw new CorePackageError(
|
|
2522
|
+
"CacheService not initialized. Call CacheService.initialize() first.",
|
|
2523
|
+
ERROR_CODES$1.CLIENT_INITIALIZATION_FAILED
|
|
2524
|
+
);
|
|
2525
|
+
}
|
|
2526
|
+
return this.instance;
|
|
2465
2527
|
}
|
|
2466
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2467
|
-
// Health Check (special handling for response transformation)
|
|
2468
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2469
2528
|
/**
|
|
2470
|
-
*
|
|
2471
|
-
*
|
|
2529
|
+
* Get the cache manager instance
|
|
2530
|
+
*
|
|
2531
|
+
* @throws {CorePackageError} If cache not initialized
|
|
2532
|
+
* @returns CacheManager instance
|
|
2533
|
+
*
|
|
2534
|
+
* @example
|
|
2535
|
+
* ```typescript
|
|
2536
|
+
* const cache = CacheService.getInstance().getCacheManager();
|
|
2537
|
+
* await cache.set('user:123', userData);
|
|
2538
|
+
* const user = await cache.get('user:123');
|
|
2539
|
+
* ```
|
|
2472
2540
|
*/
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
providers: result.providers,
|
|
2480
|
-
// Use optional chaining since error may not exist on all health check results
|
|
2481
|
-
error: "error" in result ? result.error : void 0
|
|
2482
|
-
};
|
|
2483
|
-
} catch (error) {
|
|
2484
|
-
this.emitNotificationError(error, "healthCheck", { recoverable: true });
|
|
2485
|
-
return {
|
|
2486
|
-
isHealthy: false,
|
|
2487
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
2488
|
-
};
|
|
2541
|
+
getCacheManager() {
|
|
2542
|
+
if (!this.cacheManager) {
|
|
2543
|
+
throw new CorePackageError(
|
|
2544
|
+
"Cache manager not initialized. Call CacheService.initialize() first.",
|
|
2545
|
+
ERROR_CODES$1.CLIENT_INITIALIZATION_FAILED
|
|
2546
|
+
);
|
|
2489
2547
|
}
|
|
2548
|
+
return this.cacheManager;
|
|
2490
2549
|
}
|
|
2491
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2492
|
-
// Lifecycle
|
|
2493
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2494
2550
|
/**
|
|
2495
|
-
*
|
|
2551
|
+
* Get current cache configuration
|
|
2552
|
+
*
|
|
2553
|
+
* @returns Current cache configuration or null if not initialized
|
|
2496
2554
|
*/
|
|
2497
2555
|
getConfig() {
|
|
2498
2556
|
return this.config;
|
|
2499
2557
|
}
|
|
2500
2558
|
/**
|
|
2501
|
-
*
|
|
2559
|
+
* Check if cache is initialized and enabled
|
|
2560
|
+
*
|
|
2561
|
+
* @returns True if cache is ready to use
|
|
2502
2562
|
*/
|
|
2503
|
-
|
|
2504
|
-
this.
|
|
2505
|
-
this.initialized = false;
|
|
2506
|
-
this.config = null;
|
|
2563
|
+
isInitialized() {
|
|
2564
|
+
return this.cacheManager !== null && (this.config?.isEnabled ?? false);
|
|
2507
2565
|
}
|
|
2508
2566
|
/**
|
|
2509
|
-
*
|
|
2567
|
+
* Reset the cache service (useful for testing)
|
|
2510
2568
|
*
|
|
2511
|
-
*
|
|
2569
|
+
* @internal
|
|
2570
|
+
*/
|
|
2571
|
+
static reset() {
|
|
2572
|
+
if (this.instance?.cacheManager) {
|
|
2573
|
+
this.instance.logger.info("[CacheService] Resetting cache service");
|
|
2574
|
+
this.instance.cacheManager.clear().catch((error) => {
|
|
2575
|
+
this.instance?.logger.error("[CacheService] Error clearing cache during reset", { error });
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
2578
|
+
this.instance = null;
|
|
2579
|
+
}
|
|
2580
|
+
/**
|
|
2581
|
+
* Validate cache configuration
|
|
2512
2582
|
*
|
|
2513
|
-
* @param config -
|
|
2514
|
-
* @
|
|
2583
|
+
* @param config - Configuration to validate
|
|
2584
|
+
* @throws {CorePackageError} If configuration is invalid
|
|
2585
|
+
* @private
|
|
2515
2586
|
*/
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2587
|
+
// eslint-disable-next-line complexity
|
|
2588
|
+
validateConfig(config) {
|
|
2589
|
+
if (!CACHE_STRATEGIES.includes(config.strategy)) {
|
|
2590
|
+
throw new CorePackageError(
|
|
2591
|
+
`Invalid cache strategy: ${config.strategy}. Must be one of: ${CACHE_STRATEGIES.join(", ")}`,
|
|
2592
|
+
ERROR_CODES$1.VALIDATION_ERROR
|
|
2593
|
+
);
|
|
2594
|
+
}
|
|
2595
|
+
if (config.ttl !== void 0 && (config.ttl < 0 || !Number.isFinite(config.ttl))) {
|
|
2596
|
+
throw new CorePackageError(
|
|
2597
|
+
`Invalid TTL value: ${config.ttl}. Must be a positive number.`,
|
|
2598
|
+
ERROR_CODES$1.VALIDATION_ERROR
|
|
2599
|
+
);
|
|
2600
|
+
}
|
|
2601
|
+
if (config.strategy === "redis") {
|
|
2602
|
+
if (!config.redis?.url && !config.redis?.host) {
|
|
2603
|
+
throw new CorePackageError(
|
|
2604
|
+
"Redis strategy requires either redis.url or redis.host configuration",
|
|
2605
|
+
ERROR_CODES$1.VALIDATION_ERROR
|
|
2606
|
+
);
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
this.logger.debug("[CacheService] Configuration validated successfully");
|
|
2522
2610
|
}
|
|
2523
2611
|
};
|
|
2612
|
+
|
|
2613
|
+
// src/init/CoreInitializer.ts
|
|
2614
|
+
init_CoreEventManager();
|
|
2524
2615
|
var BaseDomainService = class {
|
|
2525
2616
|
// ─────────────────────────────────────────────────────────────────────────
|
|
2526
2617
|
// Constructor
|
|
@@ -2919,6 +3010,9 @@ var BaseDomainService = class {
|
|
|
2919
3010
|
);
|
|
2920
3011
|
}
|
|
2921
3012
|
};
|
|
3013
|
+
|
|
3014
|
+
// src/domain/base/BaseFrontendDomainService.ts
|
|
3015
|
+
init_CoreEventManager();
|
|
2922
3016
|
var BaseFrontendDomainService = class extends BaseDomainService {
|
|
2923
3017
|
// ─────────────────────────────────────────────────────────────────────────
|
|
2924
3018
|
// Constructor
|
|
@@ -3816,6 +3910,9 @@ var BaseFrontendDomainService = class extends BaseDomainService {
|
|
|
3816
3910
|
);
|
|
3817
3911
|
}
|
|
3818
3912
|
};
|
|
3913
|
+
|
|
3914
|
+
// src/events/index.ts
|
|
3915
|
+
init_CoreEventManager();
|
|
3819
3916
|
var BaseBackendDomainService = class extends BaseDomainService {
|
|
3820
3917
|
static {
|
|
3821
3918
|
__name(this, "BaseBackendDomainService");
|
|
@@ -5213,6 +5310,10 @@ function keysToSnake(obj) {
|
|
|
5213
5310
|
return result;
|
|
5214
5311
|
}
|
|
5215
5312
|
__name(keysToSnake, "keysToSnake");
|
|
5313
|
+
|
|
5314
|
+
// src/models/example/ExampleRepository.ts
|
|
5315
|
+
init_DbService();
|
|
5316
|
+
init_common();
|
|
5216
5317
|
var EXAMPLE_TABLE = "examples";
|
|
5217
5318
|
var DEFAULT_PAGINATION_LIMIT = 100;
|
|
5218
5319
|
var DUMMY_DATA = [
|
|
@@ -5390,31 +5491,99 @@ var ExampleRepository = class _ExampleRepository extends BaseRepository {
|
|
|
5390
5491
|
return super.delete(id, config);
|
|
5391
5492
|
}
|
|
5392
5493
|
// ─────────────────────────────────────────────────────────────────────────
|
|
5393
|
-
// Domain-Specific Query Methods
|
|
5494
|
+
// Domain-Specific Query Methods (using QueryBuilder fluent API)
|
|
5394
5495
|
// ─────────────────────────────────────────────────────────────────────────
|
|
5395
5496
|
/**
|
|
5396
|
-
* Find entities by status
|
|
5497
|
+
* Find entities by status using fluent QueryBuilder
|
|
5498
|
+
*
|
|
5499
|
+
* @example
|
|
5500
|
+
* ```typescript
|
|
5501
|
+
* const result = await repo.findByStatus('active');
|
|
5502
|
+
* ```
|
|
5397
5503
|
*/
|
|
5398
5504
|
async findByStatus(status) {
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5505
|
+
if (this.useDummyData) {
|
|
5506
|
+
return this.findMany({
|
|
5507
|
+
filter: { field: "status", operator: "eq", value: status }
|
|
5508
|
+
});
|
|
5509
|
+
}
|
|
5510
|
+
return this.query().where("status", "eq", status).execute();
|
|
5402
5511
|
}
|
|
5403
5512
|
/**
|
|
5404
|
-
* Find entities by owner
|
|
5513
|
+
* Find entities by owner using fluent QueryBuilder
|
|
5514
|
+
*
|
|
5515
|
+
* @example
|
|
5516
|
+
* ```typescript
|
|
5517
|
+
* const result = await repo.findByOwner('user-001');
|
|
5518
|
+
* ```
|
|
5405
5519
|
*/
|
|
5406
5520
|
async findByOwner(ownerId) {
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5521
|
+
if (this.useDummyData) {
|
|
5522
|
+
return this.findMany({
|
|
5523
|
+
filter: { field: "owner_id", operator: "eq", value: ownerId }
|
|
5524
|
+
});
|
|
5525
|
+
}
|
|
5526
|
+
return this.query().where("owner_id", "eq", ownerId).execute();
|
|
5410
5527
|
}
|
|
5411
5528
|
/**
|
|
5412
|
-
* Find visible entities only
|
|
5529
|
+
* Find visible entities only using fluent QueryBuilder
|
|
5530
|
+
*
|
|
5531
|
+
* @example
|
|
5532
|
+
* ```typescript
|
|
5533
|
+
* const result = await repo.findVisible();
|
|
5534
|
+
* ```
|
|
5413
5535
|
*/
|
|
5414
5536
|
async findVisible() {
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5537
|
+
if (this.useDummyData) {
|
|
5538
|
+
return this.findMany({
|
|
5539
|
+
filter: { field: "is_visible", operator: "eq", value: true }
|
|
5540
|
+
});
|
|
5541
|
+
}
|
|
5542
|
+
return this.query().where("is_visible", "eq", true).execute();
|
|
5543
|
+
}
|
|
5544
|
+
/**
|
|
5545
|
+
* Find active and visible entities using complex QueryBuilder
|
|
5546
|
+
*
|
|
5547
|
+
* @example
|
|
5548
|
+
* ```typescript
|
|
5549
|
+
* const items = await repo.findActiveAndVisible();
|
|
5550
|
+
* ```
|
|
5551
|
+
*/
|
|
5552
|
+
async findActiveAndVisible() {
|
|
5553
|
+
if (this.useDummyData) {
|
|
5554
|
+
return this.dummyStore.filter((r) => r.status === "active" && r.is_visible && !r.deleted_at);
|
|
5555
|
+
}
|
|
5556
|
+
return this.query().where("status", "eq", "active").andWhere("is_visible", "eq", true).whereNull("deleted_at").orderByDesc("created_at").getMany();
|
|
5557
|
+
}
|
|
5558
|
+
/**
|
|
5559
|
+
* Find entities by owner with status filter using QueryBuilder
|
|
5560
|
+
*
|
|
5561
|
+
* @example
|
|
5562
|
+
* ```typescript
|
|
5563
|
+
* const items = await repo.findByOwnerWithStatus('user-001', 'active');
|
|
5564
|
+
* ```
|
|
5565
|
+
*/
|
|
5566
|
+
async findByOwnerWithStatus(ownerId, status) {
|
|
5567
|
+
if (this.useDummyData) {
|
|
5568
|
+
return this.dummyStore.filter(
|
|
5569
|
+
(r) => r.owner_id === ownerId && r.status === status && !r.deleted_at
|
|
5570
|
+
);
|
|
5571
|
+
}
|
|
5572
|
+
return this.query().where("owner_id", "eq", ownerId).andWhere("status", "eq", status).orderByDesc("updated_at").getMany();
|
|
5573
|
+
}
|
|
5574
|
+
/**
|
|
5575
|
+
* Count entities by status using QueryBuilder
|
|
5576
|
+
*
|
|
5577
|
+
* @example
|
|
5578
|
+
* ```typescript
|
|
5579
|
+
* const count = await repo.countByStatus('active');
|
|
5580
|
+
* ```
|
|
5581
|
+
*/
|
|
5582
|
+
async countByStatus(status) {
|
|
5583
|
+
if (this.useDummyData) {
|
|
5584
|
+
return this.dummyStore.filter((r) => r.status === status && !r.deleted_at).length;
|
|
5585
|
+
}
|
|
5586
|
+
return this.query().where("status", "eq", status).count();
|
|
5418
5587
|
}
|
|
5419
5588
|
/**
|
|
5420
5589
|
* Check if entity exists by ID
|
|
@@ -5885,6 +6054,7 @@ var BackendExampleDomainService = class _BackendExampleDomainService extends Bas
|
|
|
5885
6054
|
// ─────────────────────────────────────────────────────────────────────────
|
|
5886
6055
|
};
|
|
5887
6056
|
new BackendExampleDomainService();
|
|
6057
|
+
init_CoreEventManager();
|
|
5888
6058
|
var DEFAULT_POLLING_INTERVAL_MS = 3e4;
|
|
5889
6059
|
var FrontendExampleDomainService = class _FrontendExampleDomainService extends BaseFrontendDomainService {
|
|
5890
6060
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -5934,6 +6104,7 @@ var FrontendExampleDomainService = class _FrontendExampleDomainService extends B
|
|
|
5934
6104
|
// store.setData(data);
|
|
5935
6105
|
// // Can also call domain-specific methods or other stores
|
|
5936
6106
|
// // store.selectItem(data.selectedId);
|
|
6107
|
+
// // store.selectItem(data.selectedId);
|
|
5937
6108
|
// },
|
|
5938
6109
|
// updateData: (store, data) => store.updateData(data),
|
|
5939
6110
|
// setLoading: (store, isLoading) => store.setLoading(isLoading),
|
|
@@ -6149,6 +6320,9 @@ var FrontendExampleDomainService = class _FrontendExampleDomainService extends B
|
|
|
6149
6320
|
// All events are emitted automatically with 'example:' prefix!
|
|
6150
6321
|
// ─────────────────────────────────────────────────────────────────────────
|
|
6151
6322
|
};
|
|
6323
|
+
|
|
6324
|
+
// src/base/observability/BaseAdapter.ts
|
|
6325
|
+
init_common();
|
|
6152
6326
|
var BaseAdapter = class {
|
|
6153
6327
|
constructor() {
|
|
6154
6328
|
this._isInitialized = false;
|
|
@@ -6371,6 +6545,9 @@ var NoopAdapter = class extends BaseAdapter {
|
|
|
6371
6545
|
async doFlush() {
|
|
6372
6546
|
}
|
|
6373
6547
|
};
|
|
6548
|
+
|
|
6549
|
+
// src/base/observability/LoggerAdapter.ts
|
|
6550
|
+
init_common();
|
|
6374
6551
|
var LoggerAdapter = class extends BaseAdapter {
|
|
6375
6552
|
constructor() {
|
|
6376
6553
|
super(...arguments);
|
|
@@ -6520,6 +6697,12 @@ var LoggerAdapter = class extends BaseAdapter {
|
|
|
6520
6697
|
async doFlush() {
|
|
6521
6698
|
}
|
|
6522
6699
|
};
|
|
6700
|
+
|
|
6701
|
+
// src/base/observability/CompositeAdapter.ts
|
|
6702
|
+
init_common();
|
|
6703
|
+
|
|
6704
|
+
// src/base/observability/DatadogAdapter.ts
|
|
6705
|
+
init_common();
|
|
6523
6706
|
var DEFAULT_ADAPTER_TIMEOUT_MS = 5e3;
|
|
6524
6707
|
var ObservabilityService = class {
|
|
6525
6708
|
constructor() {
|
|
@@ -7272,10 +7455,11 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
7272
7455
|
if (!dbConfig) {
|
|
7273
7456
|
return void 0;
|
|
7274
7457
|
}
|
|
7458
|
+
const { DbService: DbService2 } = await Promise.resolve().then(() => (init_DbService(), DbService_exports));
|
|
7275
7459
|
let dbInstance;
|
|
7276
7460
|
if (isDedicated && dbConfig) {
|
|
7277
7461
|
try {
|
|
7278
|
-
dbInstance = await
|
|
7462
|
+
dbInstance = await DbService2.createInstance(dbConfig);
|
|
7279
7463
|
_ServiceRegistry.logger.debug(
|
|
7280
7464
|
`Created dedicated database instance for service '${serviceKey}'`
|
|
7281
7465
|
);
|
|
@@ -7289,7 +7473,7 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
7289
7473
|
}
|
|
7290
7474
|
} else {
|
|
7291
7475
|
try {
|
|
7292
|
-
dbInstance =
|
|
7476
|
+
dbInstance = DbService2.getInstance();
|
|
7293
7477
|
} catch {
|
|
7294
7478
|
_ServiceRegistry.logger.debug(
|
|
7295
7479
|
`Global DB not initialized, service '${serviceKey}' will not have DB`
|
|
@@ -7390,10 +7574,11 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
7390
7574
|
if (!storageConfig) {
|
|
7391
7575
|
return void 0;
|
|
7392
7576
|
}
|
|
7577
|
+
const { StorageService: StorageService2 } = await Promise.resolve().then(() => (init_StorageService(), StorageService_exports));
|
|
7393
7578
|
let storageInstance;
|
|
7394
7579
|
if (isDedicated && storageConfig) {
|
|
7395
7580
|
try {
|
|
7396
|
-
storageInstance = await
|
|
7581
|
+
storageInstance = await StorageService2.createInstance(storageConfig);
|
|
7397
7582
|
_ServiceRegistry.logger.debug(
|
|
7398
7583
|
`Created dedicated storage instance for service '${serviceKey}'`
|
|
7399
7584
|
);
|
|
@@ -7405,7 +7590,7 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
7405
7590
|
}
|
|
7406
7591
|
} else {
|
|
7407
7592
|
try {
|
|
7408
|
-
storageInstance =
|
|
7593
|
+
storageInstance = StorageService2.getInstance();
|
|
7409
7594
|
} catch {
|
|
7410
7595
|
_ServiceRegistry.logger.debug(
|
|
7411
7596
|
`Global storage not initialized, service '${serviceKey}' will not have storage`
|
|
@@ -7435,10 +7620,11 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
7435
7620
|
if (!notificationsConfig) {
|
|
7436
7621
|
return void 0;
|
|
7437
7622
|
}
|
|
7623
|
+
const { NotificationService: NotificationService2 } = await Promise.resolve().then(() => (init_NotificationService(), NotificationService_exports));
|
|
7438
7624
|
let notificationsInstance;
|
|
7439
7625
|
if (isDedicated && notificationsConfig) {
|
|
7440
7626
|
try {
|
|
7441
|
-
notificationsInstance = await
|
|
7627
|
+
notificationsInstance = await NotificationService2.createInstance(notificationsConfig);
|
|
7442
7628
|
_ServiceRegistry.logger.debug(
|
|
7443
7629
|
`Created dedicated notifications instance for service '${serviceKey}'`
|
|
7444
7630
|
);
|
|
@@ -7450,7 +7636,7 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
7450
7636
|
}
|
|
7451
7637
|
} else {
|
|
7452
7638
|
try {
|
|
7453
|
-
notificationsInstance =
|
|
7639
|
+
notificationsInstance = NotificationService2.getInstance();
|
|
7454
7640
|
} catch {
|
|
7455
7641
|
_ServiceRegistry.logger.debug(
|
|
7456
7642
|
`Global notifications not initialized, service '${serviceKey}' will not have notifications`
|
|
@@ -8028,8 +8214,9 @@ var Core = class _Core {
|
|
|
8028
8214
|
await _Core.initService(
|
|
8029
8215
|
"storage",
|
|
8030
8216
|
async () => {
|
|
8031
|
-
await
|
|
8032
|
-
|
|
8217
|
+
const { StorageService: StorageService2 } = await Promise.resolve().then(() => (init_StorageService(), StorageService_exports));
|
|
8218
|
+
await StorageService2.initialize(storageConfig);
|
|
8219
|
+
_Core._coreServices.storage = StorageService2.getInstance();
|
|
8033
8220
|
_Core.log("Storage service initialized", verbose);
|
|
8034
8221
|
},
|
|
8035
8222
|
verbose
|
|
@@ -8053,8 +8240,9 @@ var Core = class _Core {
|
|
|
8053
8240
|
await _Core.initService(
|
|
8054
8241
|
"notifications",
|
|
8055
8242
|
async () => {
|
|
8056
|
-
await
|
|
8057
|
-
|
|
8243
|
+
const { NotificationService: NotificationService2 } = await Promise.resolve().then(() => (init_NotificationService(), NotificationService_exports));
|
|
8244
|
+
await NotificationService2.initialize(notificationsConfig);
|
|
8245
|
+
_Core._coreServices.notifications = NotificationService2.getInstance();
|
|
8058
8246
|
_Core.log("Notifications service initialized", verbose);
|
|
8059
8247
|
},
|
|
8060
8248
|
verbose
|
|
@@ -8619,11 +8807,13 @@ var Core = class _Core {
|
|
|
8619
8807
|
_Core._observabilityConfig = {};
|
|
8620
8808
|
if (_Core._coreServices.storage) {
|
|
8621
8809
|
await _Core._coreServices.storage.close();
|
|
8622
|
-
await
|
|
8810
|
+
const { StorageService: StorageService2 } = await Promise.resolve().then(() => (init_StorageService(), StorageService_exports));
|
|
8811
|
+
await StorageService2.reset();
|
|
8623
8812
|
}
|
|
8624
8813
|
if (_Core._coreServices.notifications) {
|
|
8625
8814
|
await _Core._coreServices.notifications.close();
|
|
8626
|
-
await
|
|
8815
|
+
const { NotificationService: NotificationService2 } = await Promise.resolve().then(() => (init_NotificationService(), NotificationService_exports));
|
|
8816
|
+
await NotificationService2.reset();
|
|
8627
8817
|
}
|
|
8628
8818
|
if (_Core._errorHandler) {
|
|
8629
8819
|
_Core._errorHandler.destroy();
|
|
@@ -8674,20 +8864,19 @@ var Core = class _Core {
|
|
|
8674
8864
|
*/
|
|
8675
8865
|
static shouldSkipDbService(skipDb, verbose) {
|
|
8676
8866
|
if (skipDb === true) {
|
|
8677
|
-
|
|
8678
|
-
console.log("[Core] Database service skipped (skipDb: true)");
|
|
8679
|
-
}
|
|
8867
|
+
_Core.log("Database service skipped (skipDb: true)", verbose);
|
|
8680
8868
|
return true;
|
|
8681
8869
|
}
|
|
8682
8870
|
const isFrontend = _Core.isFrontend;
|
|
8683
8871
|
if (isFrontend) {
|
|
8684
8872
|
if (skipDb === false) {
|
|
8685
|
-
|
|
8686
|
-
"
|
|
8873
|
+
_Core.logger.warn(
|
|
8874
|
+
"DbService cannot be initialized on frontend runtime. DbService is backend-only and requires Node.js. Use API calls from frontend instead. Skipping database initialization."
|
|
8687
8875
|
);
|
|
8688
8876
|
} else if (verbose) {
|
|
8689
|
-
|
|
8690
|
-
"
|
|
8877
|
+
_Core.log(
|
|
8878
|
+
"Database service skipped (frontend runtime detected). DbService is only available on backend.",
|
|
8879
|
+
verbose
|
|
8691
8880
|
);
|
|
8692
8881
|
}
|
|
8693
8882
|
return true;
|
|
@@ -8699,10 +8888,9 @@ var Core = class _Core {
|
|
|
8699
8888
|
* Config validation is handled by DbService
|
|
8700
8889
|
*/
|
|
8701
8890
|
static async initializeDb(config, verbose) {
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
return DbService.initialize({
|
|
8891
|
+
_Core.log(`Initializing database with adapter: ${config?.adapter ?? "drizzle"}`, verbose);
|
|
8892
|
+
const { DbService: DbService2 } = await Promise.resolve().then(() => (init_DbService(), DbService_exports));
|
|
8893
|
+
return DbService2.initialize({
|
|
8706
8894
|
adapter: "drizzle",
|
|
8707
8895
|
...config
|
|
8708
8896
|
});
|
|
@@ -8714,9 +8902,7 @@ var Core = class _Core {
|
|
|
8714
8902
|
static async initializeApi(config, globalEnvironment, verbose) {
|
|
8715
8903
|
const { env: apiEnv, setAsDefault, ...apiClientOptions } = config ?? {};
|
|
8716
8904
|
const resolvedEnv = apiEnv ?? globalEnvironment ?? "development";
|
|
8717
|
-
|
|
8718
|
-
console.log(`[Core] Initializing API client for environment: ${resolvedEnv}`);
|
|
8719
|
-
}
|
|
8905
|
+
_Core.log(`Initializing API client for environment: ${resolvedEnv}`, verbose);
|
|
8720
8906
|
await ApiClientService.init(
|
|
8721
8907
|
{
|
|
8722
8908
|
env: resolvedEnv,
|
|
@@ -8730,9 +8916,7 @@ var Core = class _Core {
|
|
|
8730
8916
|
* Config validation is handled by CacheService
|
|
8731
8917
|
*/
|
|
8732
8918
|
static async initializeCache(config, verbose) {
|
|
8733
|
-
|
|
8734
|
-
console.log(`[Core] Initializing cache with strategy: ${config.strategy}`);
|
|
8735
|
-
}
|
|
8919
|
+
_Core.log(`Initializing cache with strategy: ${config.strategy}`, verbose);
|
|
8736
8920
|
await CacheService.initialize(config);
|
|
8737
8921
|
}
|
|
8738
8922
|
/**
|
|
@@ -8740,9 +8924,7 @@ var Core = class _Core {
|
|
|
8740
8924
|
* Always includes LoggerAdapter as failover for console output.
|
|
8741
8925
|
*/
|
|
8742
8926
|
static async initializeObservability(config, verbose, environment) {
|
|
8743
|
-
|
|
8744
|
-
console.log(`[Core] Initializing observability with provider: ${config.provider ?? "auto"}`);
|
|
8745
|
-
}
|
|
8927
|
+
_Core.log(`Initializing observability with provider: ${config.provider ?? "auto"}`, verbose);
|
|
8746
8928
|
const env = config.environment ?? environment ?? "development";
|
|
8747
8929
|
_Core._observabilityConfig = { ...config, environment: env };
|
|
8748
8930
|
const adapters = [];
|
|
@@ -8766,11 +8948,10 @@ var Core = class _Core {
|
|
|
8766
8948
|
flushInterval: config.flushInterval,
|
|
8767
8949
|
adapterTimeout: config.flushInterval
|
|
8768
8950
|
});
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
|
|
8773
|
-
}
|
|
8951
|
+
_Core.log(
|
|
8952
|
+
`ObservabilityService initialized with ${adapters.length} adapters (env: ${env})`,
|
|
8953
|
+
verbose
|
|
8954
|
+
);
|
|
8774
8955
|
_Core._coreServices.observability = observability;
|
|
8775
8956
|
ServiceRegistry.setObservabilityInstance(observability);
|
|
8776
8957
|
}
|
|
@@ -8781,10 +8962,10 @@ var Core = class _Core {
|
|
|
8781
8962
|
static initializeRootStore(config, verbose) {
|
|
8782
8963
|
const isFrontend = typeof window !== "undefined";
|
|
8783
8964
|
if (isFrontend) {
|
|
8784
|
-
|
|
8965
|
+
_Core.log("Using frontend root store (Zustand)", verbose);
|
|
8785
8966
|
_Core._rootStore = useRootStore;
|
|
8786
8967
|
} else {
|
|
8787
|
-
|
|
8968
|
+
_Core.log("Creating backend composite store (in-memory)", verbose);
|
|
8788
8969
|
const backendErrorStore = ServerErrorMiddleware.createErrorStore({
|
|
8789
8970
|
maxErrors: config?.maxErrors ?? DEFAULT_MAX_ERRORS,
|
|
8790
8971
|
onErrorAdded: config?.onError
|
|
@@ -8832,11 +9013,11 @@ var Core = class _Core {
|
|
|
8832
9013
|
// eslint-disable-next-line complexity
|
|
8833
9014
|
static async initializeErrorHandler(config, verbose) {
|
|
8834
9015
|
if (config?.enabled === false) {
|
|
8835
|
-
|
|
9016
|
+
_Core.log("Error handler disabled by configuration", verbose);
|
|
8836
9017
|
return;
|
|
8837
9018
|
}
|
|
8838
9019
|
_Core._errorConfig = config ?? {};
|
|
8839
|
-
|
|
9020
|
+
_Core.log("Initializing global error handler...", verbose);
|
|
8840
9021
|
initializeErrorSystem({
|
|
8841
9022
|
defaultLocale: _Core._errorConfig.localization?.defaultLocale,
|
|
8842
9023
|
fallbackLocale: _Core._errorConfig.localization?.fallbackLocale,
|
|
@@ -8851,23 +9032,22 @@ var Core = class _Core {
|
|
|
8851
9032
|
_Core.buildErrorHandlerConfig(_Core._errorConfig)
|
|
8852
9033
|
);
|
|
8853
9034
|
const errorEventCleanup = CoreEventManager.on(
|
|
8854
|
-
CORE_EVENTS.SYSTEM.ERROR,
|
|
9035
|
+
CORE_EVENTS$1.SYSTEM.ERROR,
|
|
8855
9036
|
(event) => {
|
|
8856
9037
|
if (!_Core._rootStore) return;
|
|
8857
9038
|
try {
|
|
8858
9039
|
const { errors } = event.data;
|
|
8859
9040
|
if (errors && errors.length > 0) {
|
|
8860
9041
|
_Core._rootStore.getState().errors.addErrors(errors);
|
|
8861
|
-
|
|
9042
|
+
_Core.log(`Added ${errors.length} error(s) to store`, verbose);
|
|
8862
9043
|
}
|
|
8863
9044
|
} catch (e) {
|
|
8864
|
-
|
|
9045
|
+
_Core.logger.error("Failed to handle error event", { error: e });
|
|
8865
9046
|
}
|
|
8866
9047
|
}
|
|
8867
9048
|
);
|
|
8868
9049
|
_Core._eventCleanupFns.push(errorEventCleanup);
|
|
8869
|
-
|
|
8870
|
-
console.log("[Core] Global error handler initialized with CoreEventManager integration");
|
|
9050
|
+
_Core.log("Global error handler initialized with CoreEventManager integration", verbose);
|
|
8871
9051
|
if (_Core._errorConfig.httpHandler !== false) {
|
|
8872
9052
|
_Core.createHttpErrorHandler(_Core._errorConfig, verbose);
|
|
8873
9053
|
}
|
|
@@ -8889,11 +9069,11 @@ var Core = class _Core {
|
|
|
8889
9069
|
switch (runtime) {
|
|
8890
9070
|
case "express":
|
|
8891
9071
|
_Core._httpErrorHandler = ServerErrorMiddleware.createHttpErrorHandler(httpConfig);
|
|
8892
|
-
|
|
9072
|
+
_Core.log("Created Express error handler middleware", verbose);
|
|
8893
9073
|
break;
|
|
8894
9074
|
case "nestjs":
|
|
8895
9075
|
_Core._httpErrorHandler = ServerErrorMiddleware.createNestJsExceptionFilter(httpConfig);
|
|
8896
|
-
|
|
9076
|
+
_Core.log("Created NestJS exception filter", verbose);
|
|
8897
9077
|
break;
|
|
8898
9078
|
case "nextjs":
|
|
8899
9079
|
_Core._httpErrorHandler = {
|
|
@@ -8905,7 +9085,7 @@ var Core = class _Core {
|
|
|
8905
9085
|
/** For Pages Router: export default createNextApiErrorHandler()(handler) */
|
|
8906
9086
|
createNextApiErrorHandler: /* @__PURE__ */ __name((overrideConfig = {}) => ServerErrorMiddleware.createNextApiErrorHandler({ ...httpConfig, ...overrideConfig }), "createNextApiErrorHandler")
|
|
8907
9087
|
};
|
|
8908
|
-
|
|
9088
|
+
_Core.log("Created Next.js error handlers", verbose);
|
|
8909
9089
|
break;
|
|
8910
9090
|
case "node":
|
|
8911
9091
|
case "bun":
|
|
@@ -8922,15 +9102,15 @@ var Core = class _Core {
|
|
|
8922
9102
|
/** Create middleware for use with native http */
|
|
8923
9103
|
createMiddleware: /* @__PURE__ */ __name(() => ServerErrorMiddleware.createHttpErrorHandler(httpConfig), "createMiddleware")
|
|
8924
9104
|
};
|
|
8925
|
-
|
|
9105
|
+
_Core.log(`Created ${runtime} error handler`, verbose);
|
|
8926
9106
|
break;
|
|
8927
9107
|
case "browser":
|
|
8928
9108
|
_Core._httpErrorHandler = null;
|
|
8929
|
-
|
|
9109
|
+
_Core.log("Skipping HTTP handler for browser runtime", verbose);
|
|
8930
9110
|
break;
|
|
8931
9111
|
default:
|
|
8932
9112
|
_Core._httpErrorHandler = ServerErrorMiddleware.createHttpErrorHandler(httpConfig);
|
|
8933
|
-
|
|
9113
|
+
_Core.log(`Created generic HTTP error handler for ${runtime}`, verbose);
|
|
8934
9114
|
}
|
|
8935
9115
|
}
|
|
8936
9116
|
/**
|
|
@@ -8944,38 +9124,36 @@ var Core = class _Core {
|
|
|
8944
9124
|
if (!_Core._errorHandler) {
|
|
8945
9125
|
return;
|
|
8946
9126
|
}
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
}
|
|
8950
|
-
const cleanupSystemError = CoreEventManager.on(CORE_EVENTS.SYSTEM.ERROR, (event) => {
|
|
9127
|
+
_Core.log("Subscribing to error events...", verbose);
|
|
9128
|
+
const cleanupSystemError = CoreEventManager.on(CORE_EVENTS$1.SYSTEM.ERROR, (event) => {
|
|
8951
9129
|
if (_Core._errorHandler && event.data?.errors?.length) {
|
|
8952
9130
|
for (const err of event.data.errors) {
|
|
8953
9131
|
_Core._errorHandler.captureError(err, "system");
|
|
8954
9132
|
}
|
|
8955
9133
|
}
|
|
8956
9134
|
});
|
|
8957
|
-
const cleanupEntityError = CoreEventManager.on(CORE_EVENTS.ENTITY.ERROR, (event) => {
|
|
9135
|
+
const cleanupEntityError = CoreEventManager.on(CORE_EVENTS$1.ENTITY.ERROR, (event) => {
|
|
8958
9136
|
if (_Core._errorHandler && event.data) {
|
|
8959
9137
|
_Core._errorHandler.captureError(event.data.error, "entity");
|
|
8960
9138
|
}
|
|
8961
9139
|
});
|
|
8962
|
-
const cleanupApiError = CoreEventManager.on(CORE_EVENTS.API.REQUEST_ERROR, (event) => {
|
|
9140
|
+
const cleanupApiError = CoreEventManager.on(CORE_EVENTS$1.API.REQUEST_ERROR, (event) => {
|
|
8963
9141
|
if (_Core._errorHandler && event.data) {
|
|
8964
9142
|
_Core._errorHandler.captureError(event.data.error, "api");
|
|
8965
9143
|
}
|
|
8966
9144
|
});
|
|
8967
|
-
const cleanupValidationError = CoreEventManager.on(CORE_EVENTS.VALIDATION.FAILED, (event) => {
|
|
9145
|
+
const cleanupValidationError = CoreEventManager.on(CORE_EVENTS$1.VALIDATION.FAILED, (event) => {
|
|
8968
9146
|
if (_Core._errorHandler && event.data) {
|
|
8969
9147
|
_Core._errorHandler.captureError(event.data.error, "validation");
|
|
8970
9148
|
}
|
|
8971
9149
|
});
|
|
8972
|
-
const cleanupAuthUnauthorized = CoreEventManager.on(CORE_EVENTS.AUTH.UNAUTHORIZED, (event) => {
|
|
9150
|
+
const cleanupAuthUnauthorized = CoreEventManager.on(CORE_EVENTS$1.AUTH.UNAUTHORIZED, (event) => {
|
|
8973
9151
|
if (_Core._errorHandler && event.data?.error) {
|
|
8974
9152
|
_Core._errorHandler.captureError(event.data.error, "auth");
|
|
8975
9153
|
}
|
|
8976
9154
|
});
|
|
8977
9155
|
const cleanupAuthSessionExpired = CoreEventManager.on(
|
|
8978
|
-
CORE_EVENTS.AUTH.SESSION_EXPIRED,
|
|
9156
|
+
CORE_EVENTS$1.AUTH.SESSION_EXPIRED,
|
|
8979
9157
|
(event) => {
|
|
8980
9158
|
if (_Core._errorHandler && event.data?.error) {
|
|
8981
9159
|
_Core._errorHandler.captureError(event.data.error, "auth");
|
|
@@ -8991,20 +9169,20 @@ var Core = class _Core {
|
|
|
8991
9169
|
cleanupAuthSessionExpired
|
|
8992
9170
|
);
|
|
8993
9171
|
if (_Core.isRuntimeCompatible("backend")) {
|
|
8994
|
-
const cleanupDatabaseError = CoreEventManager.on(CORE_EVENTS.DATABASE.ERROR, (event) => {
|
|
9172
|
+
const cleanupDatabaseError = CoreEventManager.on(CORE_EVENTS$1.DATABASE.ERROR, (event) => {
|
|
8995
9173
|
if (_Core._errorHandler && event.data) {
|
|
8996
9174
|
_Core._errorHandler.captureError(event.data.error, "database");
|
|
8997
9175
|
}
|
|
8998
9176
|
});
|
|
8999
9177
|
_Core._eventCleanupFns.push(cleanupDatabaseError);
|
|
9000
|
-
const cleanupStorageError = CoreEventManager.on(CORE_EVENTS.STORAGE.ERROR, (event) => {
|
|
9178
|
+
const cleanupStorageError = CoreEventManager.on(CORE_EVENTS$1.STORAGE.ERROR, (event) => {
|
|
9001
9179
|
if (_Core._errorHandler && event.data) {
|
|
9002
9180
|
_Core._errorHandler.captureError(event.data.error, "storage");
|
|
9003
9181
|
}
|
|
9004
9182
|
});
|
|
9005
9183
|
_Core._eventCleanupFns.push(cleanupStorageError);
|
|
9006
9184
|
const cleanupNotificationError = CoreEventManager.on(
|
|
9007
|
-
CORE_EVENTS.NOTIFICATION.ERROR,
|
|
9185
|
+
CORE_EVENTS$1.NOTIFICATION.ERROR,
|
|
9008
9186
|
(event) => {
|
|
9009
9187
|
if (_Core._errorHandler && event.data) {
|
|
9010
9188
|
_Core._errorHandler.captureError(event.data.error, "notification");
|
|
@@ -9012,21 +9190,17 @@ var Core = class _Core {
|
|
|
9012
9190
|
}
|
|
9013
9191
|
);
|
|
9014
9192
|
_Core._eventCleanupFns.push(cleanupNotificationError);
|
|
9015
|
-
|
|
9016
|
-
console.log("[Core] Subscribed to backend error events (database, storage, notification)");
|
|
9017
|
-
}
|
|
9193
|
+
_Core.log("Subscribed to backend error events (database, storage, notification)", verbose);
|
|
9018
9194
|
}
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
eventTypes.push("database", "storage", "notification");
|
|
9023
|
-
}
|
|
9024
|
-
console.log(`[Core] Subscribed to error events: ${eventTypes.join(", ")}`);
|
|
9195
|
+
const eventTypes = ["system", "entity", "api", "validation", "auth"];
|
|
9196
|
+
if (_Core.isRuntimeCompatible("backend")) {
|
|
9197
|
+
eventTypes.push("database", "storage", "notification");
|
|
9025
9198
|
}
|
|
9199
|
+
_Core.log(`Subscribed to error events: ${eventTypes.join(", ")}`, verbose);
|
|
9026
9200
|
}
|
|
9027
9201
|
/** Handle fetch flags error and return empty flags */
|
|
9028
9202
|
static handleFetchFlagsError(error, config, verbose) {
|
|
9029
|
-
if (verbose)
|
|
9203
|
+
if (verbose) _Core.logger.error("Failed to fetch feature flags", { error });
|
|
9030
9204
|
const packageError = error instanceof BaseError ? error : new CorePackageError(
|
|
9031
9205
|
error instanceof Error ? error.message : String(error),
|
|
9032
9206
|
ERROR_CODES.CORE_FEATURE_FLAG_PROVIDER_ERROR,
|
|
@@ -9059,7 +9233,7 @@ var Core = class _Core {
|
|
|
9059
9233
|
*/
|
|
9060
9234
|
static async initializeFeatureFlags(config, verbose) {
|
|
9061
9235
|
if (config?.enabled === false) {
|
|
9062
|
-
|
|
9236
|
+
_Core.log("Feature flags disabled by configuration", verbose);
|
|
9063
9237
|
return;
|
|
9064
9238
|
}
|
|
9065
9239
|
if (!_Core._rootStore) {
|
|
@@ -9069,7 +9243,7 @@ var Core = class _Core {
|
|
|
9069
9243
|
);
|
|
9070
9244
|
}
|
|
9071
9245
|
_Core._flagConfig = config ?? {};
|
|
9072
|
-
|
|
9246
|
+
_Core.log("Initializing feature flags from root store...", verbose);
|
|
9073
9247
|
const state = _Core._rootStore.getState();
|
|
9074
9248
|
await state.featureFlags.initialize({
|
|
9075
9249
|
defaults: _Core._flagConfig.defaults,
|
|
@@ -9078,9 +9252,12 @@ var Core = class _Core {
|
|
|
9078
9252
|
onFlagChange: _Core._flagConfig.onFlagChange,
|
|
9079
9253
|
onError: _Core._flagConfig.onError
|
|
9080
9254
|
});
|
|
9081
|
-
|
|
9255
|
+
_Core.log("Feature flags initialized", verbose);
|
|
9082
9256
|
}
|
|
9083
9257
|
};
|
|
9258
|
+
|
|
9259
|
+
// src/entry-frontend.ts
|
|
9260
|
+
init_CoreEventManager();
|
|
9084
9261
|
var errorContainerStyle = {
|
|
9085
9262
|
padding: "20px",
|
|
9086
9263
|
color: "red",
|
|
@@ -9124,6 +9301,9 @@ function InitializationLoading({ loading }) {
|
|
|
9124
9301
|
return /* @__PURE__ */ jsx("div", { style: loadingContainerStyle, children: /* @__PURE__ */ jsx("p", { style: loadingMessageStyle, children: "Initializing..." }) });
|
|
9125
9302
|
}
|
|
9126
9303
|
__name(InitializationLoading, "InitializationLoading");
|
|
9304
|
+
|
|
9305
|
+
// src/engine/featureFlags/engine.ts
|
|
9306
|
+
init_common();
|
|
9127
9307
|
var FeatureFlagContextBuilder = class _FeatureFlagContextBuilder {
|
|
9128
9308
|
constructor() {
|
|
9129
9309
|
this.context = {};
|
|
@@ -10242,11 +10422,19 @@ var ApiFeatureFlagProvider = class extends FeatureFlagProvider {
|
|
|
10242
10422
|
return this.engine.getRules();
|
|
10243
10423
|
}
|
|
10244
10424
|
};
|
|
10425
|
+
init_DbService();
|
|
10426
|
+
|
|
10427
|
+
// src/models/featureFlags/FeatureFlagRepository.ts
|
|
10428
|
+
init_common();
|
|
10429
|
+
init_DbService();
|
|
10245
10430
|
({
|
|
10246
10431
|
flagsTable: FEATURE_FLAG_TABLE.FeatureFlags,
|
|
10247
10432
|
rulesTable: FEATURE_FLAG_TABLE.FeatureFlagRules,
|
|
10248
10433
|
evaluationsTable: FEATURE_FLAG_TABLE.FeatureFlagEvaluations,
|
|
10249
10434
|
overridesTable: FEATURE_FLAG_TABLE.FeatureFlagOverrides});
|
|
10435
|
+
|
|
10436
|
+
// src/domain/featureFlags/FrontendFeatureFlagDomainService.ts
|
|
10437
|
+
init_CoreEventManager();
|
|
10250
10438
|
var DEFAULT_CACHE_TTL_SECONDS2 = 300;
|
|
10251
10439
|
var DEFAULT_REFRESH_INTERVAL_MS = 6e4;
|
|
10252
10440
|
var FrontendFeatureFlagDomainService = class _FrontendFeatureFlagDomainService extends BaseFrontendDomainService {
|
|
@@ -10725,6 +10913,7 @@ function ApiProvider({
|
|
|
10725
10913
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
10726
10914
|
}
|
|
10727
10915
|
__name(ApiProvider, "ApiProvider");
|
|
10916
|
+
init_CoreEventManager();
|
|
10728
10917
|
var PlyazContext = createContext(null);
|
|
10729
10918
|
var FeatureFlagStore = class {
|
|
10730
10919
|
constructor(config = {}) {
|