@hiliosai/sdk 0.1.12 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/index.d.ts +904 -0
  2. package/dist/index.js +1809 -0
  3. package/package.json +11 -2
  4. package/src/configs/constants.ts +0 -135
  5. package/src/configs/index.ts +0 -2
  6. package/src/configs/moleculer/bulkhead.ts +0 -8
  7. package/src/configs/moleculer/channels.ts +0 -102
  8. package/src/configs/moleculer/circuit-breaker.ts +0 -17
  9. package/src/configs/moleculer/index.ts +0 -98
  10. package/src/configs/moleculer/logger.ts +0 -17
  11. package/src/configs/moleculer/metrics.ts +0 -20
  12. package/src/configs/moleculer/registry.ts +0 -7
  13. package/src/configs/moleculer/retry-policy.ts +0 -17
  14. package/src/configs/moleculer/tracing.ts +0 -6
  15. package/src/configs/moleculer/tracking.ts +0 -6
  16. package/src/configs/permissions.ts +0 -109
  17. package/src/datasources/base.datasource.ts +0 -111
  18. package/src/datasources/extensions/index.ts +0 -11
  19. package/src/datasources/extensions/retry.extension.ts +0 -91
  20. package/src/datasources/extensions/soft-delete.extension.ts +0 -114
  21. package/src/datasources/extensions/tenant.extension.ts +0 -105
  22. package/src/datasources/index.ts +0 -3
  23. package/src/datasources/prisma.datasource.ts +0 -317
  24. package/src/env.ts +0 -12
  25. package/src/errors/auth.error.ts +0 -33
  26. package/src/errors/index.ts +0 -2
  27. package/src/errors/permission.error.ts +0 -17
  28. package/src/index.ts +0 -10
  29. package/src/middlewares/context-helpers.middleware.ts +0 -162
  30. package/src/middlewares/datasource.middleware.ts +0 -73
  31. package/src/middlewares/health.middleware.ts +0 -134
  32. package/src/middlewares/index.ts +0 -5
  33. package/src/middlewares/memoize.middleware.ts +0 -33
  34. package/src/middlewares/permissions.middleware.ts +0 -162
  35. package/src/mixins/datasource.mixin.ts +0 -111
  36. package/src/mixins/index.ts +0 -1
  37. package/src/service/define-integration.ts +0 -404
  38. package/src/service/define-service.ts +0 -58
  39. package/src/types/channels.ts +0 -60
  40. package/src/types/context.ts +0 -64
  41. package/src/types/datasource.ts +0 -23
  42. package/src/types/index.ts +0 -9
  43. package/src/types/integration.ts +0 -28
  44. package/src/types/message.ts +0 -128
  45. package/src/types/platform.ts +0 -39
  46. package/src/types/service.ts +0 -209
  47. package/src/types/tenant.ts +0 -4
  48. package/src/types/user.ts +0 -16
  49. package/src/utils/context-cache.ts +0 -70
  50. package/src/utils/index.ts +0 -8
  51. package/src/utils/permission-calculator.ts +0 -62
  52. package/tsconfig.json +0 -13
  53. package/tsup.config.ts +0 -5
package/dist/index.js ADDED
@@ -0,0 +1,1809 @@
1
+ import env3 from '@ltv/env';
2
+ import http from 'http';
3
+ import os from 'os';
4
+ import { Middleware } from '@moleculer/channels';
5
+ import crypto from 'crypto';
6
+
7
+ // src/middlewares/datasource.middleware.ts
8
+ function initializeDatasources(constructorRegistry) {
9
+ const initializedDatasources = {};
10
+ for (const [key, DatasourceClass] of Object.entries(constructorRegistry)) {
11
+ initializedDatasources[key] = new DatasourceClass();
12
+ }
13
+ return initializedDatasources;
14
+ }
15
+ function createDatasourceMiddleware(datasources) {
16
+ const initializedDatasources = initializeDatasources(datasources);
17
+ return {
18
+ localAction(handler) {
19
+ return function DatasourceWrapper(ctx) {
20
+ ctx.datasources = initializedDatasources;
21
+ return handler.call(this, ctx);
22
+ };
23
+ },
24
+ remoteAction(handler) {
25
+ return function DatasourceWrapper(ctx) {
26
+ ctx.datasources = initializedDatasources;
27
+ return handler.call(this, ctx);
28
+ };
29
+ }
30
+ };
31
+ }
32
+
33
+ // src/middlewares/memoize.middleware.ts
34
+ function MemoizeMixin(options) {
35
+ return {
36
+ name: "",
37
+ methods: {
38
+ async memoize(name, params, fn) {
39
+ if (!this.broker.cacher) return fn();
40
+ const key = this.broker.cacher.defaultKeygen(
41
+ `${this.name}:memoize-${name}`,
42
+ params,
43
+ {},
44
+ []
45
+ );
46
+ let res = await this.broker.cacher.get(key);
47
+ if (res) return res;
48
+ res = await fn();
49
+ this.broker.cacher.set(key, res, options?.ttl);
50
+ return res;
51
+ }
52
+ }
53
+ };
54
+ }
55
+ var HEALTH_CHECK_PORT = env3.int("HEALTH_CHECK_PORT", 3301);
56
+ var HEALTH_CHECK_READINESS_PATH = env3.string(
57
+ "HEALTH_CHECK_READINESS_PATH",
58
+ "/readyz"
59
+ );
60
+ var HEALTH_CHECK_LIVENESS_PATH = env3.string(
61
+ "HEALTH_CHECK_LIVENESS_PATH",
62
+ "/livez"
63
+ );
64
+ var HEALTH_CHECK_DEFAULTS = {
65
+ PORT: HEALTH_CHECK_PORT,
66
+ READINESS_PATH: HEALTH_CHECK_READINESS_PATH,
67
+ LIVENESS_PATH: HEALTH_CHECK_LIVENESS_PATH
68
+ };
69
+ function CreateHealthCheckMiddleware(opts = {}) {
70
+ const config = {
71
+ port: opts.port ?? HEALTH_CHECK_DEFAULTS.PORT,
72
+ readiness: {
73
+ path: opts.readiness?.path ?? HEALTH_CHECK_DEFAULTS.READINESS_PATH
74
+ },
75
+ liveness: {
76
+ path: opts.liveness?.path ?? HEALTH_CHECK_DEFAULTS.LIVENESS_PATH
77
+ }
78
+ };
79
+ let state = "down";
80
+ let server;
81
+ function handler(req, res) {
82
+ if (res.headersSent) {
83
+ return;
84
+ }
85
+ if (req.url === config.readiness.path || req.url === config.liveness.path) {
86
+ const resHeader = {
87
+ "Content-Type": "application/json; charset=utf-8"
88
+ };
89
+ const content = {
90
+ state,
91
+ uptime: process.uptime(),
92
+ timestamp: Date.now()
93
+ };
94
+ if (req.url === config.readiness.path) {
95
+ res.writeHead(state === "up" ? 200 : 503, resHeader);
96
+ } else {
97
+ res.writeHead(state !== "down" ? 200 : 503, resHeader);
98
+ }
99
+ res.end(JSON.stringify(content, null, 2));
100
+ } else {
101
+ res.writeHead(404, http.STATUS_CODES[404] ?? "Not Found", {});
102
+ res.end();
103
+ }
104
+ }
105
+ return {
106
+ created(broker) {
107
+ state = "starting";
108
+ server = http.createServer(handler);
109
+ server.listen(config.port, (err) => {
110
+ if (err) {
111
+ return broker.logger.error(
112
+ "Unable to start health-check server",
113
+ err
114
+ );
115
+ }
116
+ broker.logger.info("");
117
+ broker.logger.info("K8s health-check server listening on");
118
+ broker.logger.info(
119
+ ` http://localhost:${config.port}${config.readiness.path}`
120
+ );
121
+ broker.logger.info(
122
+ ` http://localhost:${config.port}${config.liveness.path}`
123
+ );
124
+ broker.logger.info("");
125
+ });
126
+ },
127
+ // After broker started
128
+ started() {
129
+ state = "up";
130
+ },
131
+ // Before broker stopping
132
+ stopping() {
133
+ state = "stopping";
134
+ },
135
+ // After broker stopped
136
+ stopped() {
137
+ state = "down";
138
+ server.close();
139
+ }
140
+ };
141
+ }
142
+
143
+ // src/configs/permissions.ts
144
+ var PERMISSIONS = {
145
+ // Authentication required
146
+ AUTHENTICATED: "authenticated",
147
+ // Role-based permissions
148
+ OWNER: "OWNER",
149
+ ADMIN: "ADMIN",
150
+ MANAGER: "MANAGER",
151
+ AGENT: "AGENT",
152
+ VIEWER: "VIEWER",
153
+ DEVELOPER: "DEVELOPER",
154
+ // Entity-specific permissions
155
+ TENANT_OWNER: "tenant.owner",
156
+ TENANT_MEMBER: "tenant.member",
157
+ // Resource permissions
158
+ "users.read": "users.read",
159
+ "users.write": "users.write",
160
+ "users.delete": "users.delete",
161
+ "tenants.read": "tenants.read",
162
+ "tenants.write": "tenants.write",
163
+ "tenants.delete": "tenants.delete",
164
+ "conversations.read": "conversations.read",
165
+ "conversations.write": "conversations.write",
166
+ "conversations.delete": "conversations.delete",
167
+ "messages.read": "messages.read",
168
+ "messages.write": "messages.write",
169
+ "settings.read": "settings.read",
170
+ "settings.write": "settings.write",
171
+ "config.read": "config.read",
172
+ "config.write": "config.write",
173
+ "billing.read": "billing.read",
174
+ "billing.write": "billing.write"
175
+ };
176
+ var ROLE_PERMISSIONS = {
177
+ [PERMISSIONS.OWNER]: [
178
+ PERMISSIONS.AUTHENTICATED,
179
+ PERMISSIONS["users.read"],
180
+ PERMISSIONS["users.write"],
181
+ PERMISSIONS["users.delete"],
182
+ PERMISSIONS["tenants.read"],
183
+ PERMISSIONS["tenants.write"],
184
+ PERMISSIONS["tenants.delete"],
185
+ PERMISSIONS["conversations.read"],
186
+ PERMISSIONS["conversations.write"],
187
+ PERMISSIONS["conversations.delete"],
188
+ PERMISSIONS["messages.read"],
189
+ PERMISSIONS["messages.write"],
190
+ PERMISSIONS["settings.read"],
191
+ PERMISSIONS["settings.write"],
192
+ PERMISSIONS["config.read"],
193
+ PERMISSIONS["config.write"],
194
+ PERMISSIONS["billing.read"],
195
+ PERMISSIONS["billing.write"]
196
+ ],
197
+ [PERMISSIONS.ADMIN]: [
198
+ PERMISSIONS.AUTHENTICATED,
199
+ PERMISSIONS["users.read"],
200
+ PERMISSIONS["users.write"],
201
+ PERMISSIONS["users.delete"],
202
+ PERMISSIONS["tenants.read"],
203
+ PERMISSIONS["tenants.write"],
204
+ PERMISSIONS["tenants.delete"],
205
+ PERMISSIONS["conversations.read"],
206
+ PERMISSIONS["conversations.write"],
207
+ PERMISSIONS["conversations.delete"],
208
+ PERMISSIONS["messages.read"],
209
+ PERMISSIONS["messages.write"],
210
+ PERMISSIONS["settings.read"],
211
+ PERMISSIONS["settings.write"],
212
+ PERMISSIONS["config.read"],
213
+ PERMISSIONS["config.write"],
214
+ PERMISSIONS["billing.read"]
215
+ ],
216
+ [PERMISSIONS.MANAGER]: [
217
+ PERMISSIONS.AUTHENTICATED,
218
+ PERMISSIONS["users.read"],
219
+ PERMISSIONS["users.write"],
220
+ PERMISSIONS["conversations.read"],
221
+ PERMISSIONS["conversations.write"],
222
+ PERMISSIONS["messages.read"],
223
+ PERMISSIONS["messages.write"],
224
+ PERMISSIONS["settings.read"],
225
+ PERMISSIONS["settings.write"]
226
+ ],
227
+ [PERMISSIONS.AGENT]: [
228
+ PERMISSIONS.AUTHENTICATED,
229
+ PERMISSIONS["conversations.read"],
230
+ PERMISSIONS["conversations.write"],
231
+ PERMISSIONS["messages.read"],
232
+ PERMISSIONS["messages.write"]
233
+ ],
234
+ [PERMISSIONS.VIEWER]: [
235
+ PERMISSIONS.AUTHENTICATED,
236
+ PERMISSIONS["conversations.read"],
237
+ PERMISSIONS["messages.read"]
238
+ ],
239
+ [PERMISSIONS.DEVELOPER]: [
240
+ PERMISSIONS.AUTHENTICATED,
241
+ // Only specific debug/development permissions, not full access
242
+ "system.debug",
243
+ "health.check",
244
+ PERMISSIONS["config.read"]
245
+ ]
246
+ };
247
+
248
+ // src/configs/moleculer/bulkhead.ts
249
+ var bulkheadConfig = {
250
+ // Enable feature.
251
+ enabled: false,
252
+ // Maximum concurrent executions.
253
+ concurrency: 10,
254
+ // Maximum size of queue
255
+ maxQueueSize: 100
256
+ };
257
+ var NAMESPACE = env3.string("NAMESPACE", "hios").toLowerCase();
258
+ var CHANNELS = {
259
+ // Webhook processing channels
260
+ WEBHOOK: {
261
+ // Pattern: hios.webhook.{tenantId}.{platform}
262
+ PATTERN: `${NAMESPACE}.webhook.*.*`,
263
+ PREFIX: `${NAMESPACE}.webhook`,
264
+ build: (tenantId, platform) => `${NAMESPACE}.webhook.${tenantId}.${platform}`
265
+ },
266
+ // Message processing channels
267
+ PROCESSING: {
268
+ // Pattern: hios.processing.{tenantId}.{messageType}
269
+ PATTERN: `${NAMESPACE}.processing.*.*`,
270
+ PREFIX: `${NAMESPACE}.processing`,
271
+ build: (tenantId, messageType) => `${NAMESPACE}.processing.${tenantId}.${messageType}`
272
+ },
273
+ // Response/outbound message channels
274
+ RESPONSE: {
275
+ // Pattern: hios.response.{tenantId}.{platform}
276
+ PATTERN: `${NAMESPACE}.response.*.*`,
277
+ PREFIX: `${NAMESPACE}.response`,
278
+ build: (tenantId, platform) => `${NAMESPACE}.response.${tenantId}.${platform}`
279
+ },
280
+ // System channels
281
+ SYSTEM: {
282
+ // Error handling
283
+ ERRORS: `${NAMESPACE}.system.errors`,
284
+ // Metrics and monitoring
285
+ METRICS: `${NAMESPACE}.system.metrics`,
286
+ // Health checks
287
+ HEALTH: `${NAMESPACE}.system.health`,
288
+ // Integration lifecycle events
289
+ INTEGRATION_REGISTERED: `${NAMESPACE}.system.integration.registered`,
290
+ INTEGRATION_UNREGISTERED: `${NAMESPACE}.system.integration.unregistered`
291
+ },
292
+ // Dead letter queues
293
+ DLQ: {
294
+ // Failed webhook processing
295
+ WEBHOOK_FAILED: `${NAMESPACE}.dlq.webhook.failed`,
296
+ // Failed message sends
297
+ SEND_FAILED: `${NAMESPACE}.dlq.send.failed`,
298
+ // Failed processing
299
+ PROCESSING_FAILED: `${NAMESPACE}.dlq.processing.failed`,
300
+ // Build DLQ name for specific integration
301
+ buildSendFailed: (platform) => `${NAMESPACE}.dlq.send.${platform}.failed`
302
+ }
303
+ };
304
+ var INTEGRATION_CHANNELS = {
305
+ // Message events
306
+ MESSAGE_RECEIVED: `${NAMESPACE}.processing.message.received`,
307
+ MESSAGE_SENT: `${NAMESPACE}.processing.message.sent`,
308
+ MESSAGE_FAILED: `${NAMESPACE}.processing.message.failed`
309
+ };
310
+ var CHANNEL_CONFIG = {
311
+ // Default settings for message channels
312
+ DEFAULTS: {
313
+ maxInFlight: 10,
314
+ maxRetries: 3,
315
+ deadLettering: {
316
+ enabled: true
317
+ }
318
+ },
319
+ // High-priority channels (webhooks)
320
+ HIGH_PRIORITY: {
321
+ maxInFlight: 50,
322
+ maxRetries: 5,
323
+ deadLettering: {
324
+ enabled: true
325
+ }
326
+ },
327
+ // Low-priority channels (metrics, logs)
328
+ LOW_PRIORITY: {
329
+ maxInFlight: 5,
330
+ maxRetries: 1,
331
+ deadLettering: {
332
+ enabled: false
333
+ }
334
+ }
335
+ };
336
+ var SUBJECTS = {
337
+ // All webhook subjects
338
+ WEBHOOK_ALL: `${NAMESPACE}.webhook.>`,
339
+ // All processing subjects
340
+ PROCESSING_ALL: `${NAMESPACE}.processing.>`,
341
+ // All response subjects
342
+ RESPONSE_ALL: `${NAMESPACE}.response.>`,
343
+ // All system subjects
344
+ SYSTEM_ALL: `${NAMESPACE}.system.>`,
345
+ // All DLQ subjects
346
+ DLQ_ALL: `${NAMESPACE}.dlq.>`};
347
+
348
+ // src/configs/moleculer/channels.ts
349
+ var NAMESPACE2 = NAMESPACE.toUpperCase();
350
+ var middleware = Middleware({
351
+ adapter: {
352
+ type: "NATS",
353
+ options: {
354
+ nats: {
355
+ url: env3.string("NATS_URL", "nats://localhost:4222"),
356
+ /** Connection options for reliability */
357
+ connectionOptions: {
358
+ name: "hios",
359
+ timeout: 1e4,
360
+ reconnect: true,
361
+ maxReconnectAttempts: 10,
362
+ reconnectTimeWait: 2e3,
363
+ maxReconnectTimeWait: 3e4,
364
+ pingInterval: 2e4,
365
+ maxPingOut: 2
366
+ },
367
+ /**
368
+ * Stream configuration for multi-tenant messaging
369
+ *
370
+ * Environment variables for production:
371
+ * - NATS_MAX_MESSAGES: Default 100K (dev) -> 10M+ (prod)
372
+ * - NATS_MAX_BYTES_GB: Default 1GB (dev) -> 100GB+ (prod)
373
+ * - NATS_MAX_AGE_DAYS: Default 7 (dev) -> 30+ (prod)
374
+ * - NATS_MAX_MSG_SIZE_MB: Default 1MB (dev) -> 5MB (prod)
375
+ * - NATS_REPLICAS: Default 1 (dev) -> 3 (prod)
376
+ */
377
+ streamConfig: {
378
+ name: `${NAMESPACE2}_MESSAGES`,
379
+ subjects: [
380
+ SUBJECTS.WEBHOOK_ALL,
381
+ SUBJECTS.PROCESSING_ALL,
382
+ SUBJECTS.RESPONSE_ALL,
383
+ SUBJECTS.SYSTEM_ALL,
384
+ SUBJECTS.DLQ_ALL
385
+ ],
386
+ retention: "limits",
387
+ max_msgs: env3.int("NATS_MAX_MESSAGES", 1e5),
388
+ // 100K for dev, 10M+ for prod
389
+ max_bytes: env3.int("NATS_MAX_BYTES_GB", 1) * 1024 * 1024 * 1024,
390
+ // 1GB for dev, 100GB+ for prod
391
+ max_age: env3.int("NATS_MAX_AGE_DAYS", 7) * 24 * 60 * 60 * 1e9,
392
+ // 7 days dev, 30+ days prod
393
+ max_msg_size: env3.int("NATS_MAX_MSG_SIZE_MB", 1) * 1024 * 1024,
394
+ // 1MB dev, 5MB prod
395
+ storage: "file",
396
+ // Persistent storage
397
+ num_replicas: env3.int("NATS_REPLICAS", 1),
398
+ // 1 for dev, 3 for prod
399
+ discard: "old",
400
+ // Remove old messages when limits hit
401
+ duplicate_window: 2 * 60 * 1e9
402
+ // 2 minutes dedup window
403
+ },
404
+ /** Consumer options optimized for LLM processing */
405
+ consumerOptions: {
406
+ config: {
407
+ // Start with new messages (don't replay old ones on restart)
408
+ deliver_policy: "new",
409
+ // Explicit acknowledgment required (critical for LLM processing)
410
+ ack_policy: "explicit",
411
+ // Allow 5 unacknowledged messages per consumer (rate limiting)
412
+ max_ack_pending: 5,
413
+ // Acknowledgment timeout for LLM processing (2 minutes)
414
+ ack_wait: 120 * 1e9,
415
+ // 2 minutes in nanoseconds
416
+ // Maximum delivery attempts before dead letter
417
+ max_deliver: 3,
418
+ // Backoff for failed message retries
419
+ backoff: [
420
+ 1e9,
421
+ // 1 second
422
+ 5e9,
423
+ // 5 seconds
424
+ 3e10
425
+ // 30 seconds
426
+ ]
427
+ }
428
+ }
429
+ },
430
+ /** Application-level flow control */
431
+ maxInFlight: 5,
432
+ // Limit concurrent LLM requests per service
433
+ maxRetries: 2,
434
+ // App-level retries (NATS handles delivery retries)
435
+ /** Dead letter queue for failed messages */
436
+ deadLettering: {
437
+ enabled: true,
438
+ queueName: "FAILED_MESSAGES"
439
+ // Send to dead letter after NATS max_deliver attempts
440
+ }
441
+ }
442
+ }
443
+ });
444
+ var ChannelsMiddleware = {
445
+ ...middleware
446
+ };
447
+
448
+ // src/configs/moleculer/circuit-breaker.ts
449
+ var circuitBreakerConfig = {
450
+ // Enable feature
451
+ enabled: false,
452
+ // Threshold value. 0.5 means that 50% should be failed for tripping.
453
+ threshold: 0.5,
454
+ // Minimum request count. Below it, CB does not trip.
455
+ minRequestCount: 20,
456
+ // Number of seconds for time window.
457
+ windowTime: 60,
458
+ // Number of milliseconds to switch from open to half-open state
459
+ halfOpenTime: 10 * 1e3,
460
+ // A function to check failed requests.
461
+ check: (err) => err.code >= 500
462
+ };
463
+
464
+ // src/configs/moleculer/logger.ts
465
+ var loggerConfig = {
466
+ type: "Console",
467
+ options: {
468
+ // Using colors on the output
469
+ colors: true,
470
+ // Print module names with different colors (like docker-compose for containers)
471
+ moduleColors: false,
472
+ // Line formatter. It can be "json", "short", "simple", "full", a `Function` or a template string like "{timestamp} {level} {nodeID}/{mod}: {msg}"
473
+ formatter: "full",
474
+ // Custom object printer. If not defined, it uses the `util.inspect` method.
475
+ objectPrinter: null,
476
+ // Auto-padding the module name in order to messages begin at the same column.
477
+ autoPadding: false
478
+ }
479
+ };
480
+ var logger_default = loggerConfig;
481
+
482
+ // src/configs/moleculer/metrics.ts
483
+ var metricsConfig = {
484
+ enabled: false,
485
+ // Available built-in reporters: "Console", "CSV", "Event", "Prometheus", "Datadog", "StatsD"
486
+ reporter: {
487
+ type: "Prometheus",
488
+ options: {
489
+ // HTTP port
490
+ port: 3030,
491
+ // HTTP URL path
492
+ path: "/metrics",
493
+ // Default labels which are appended to all metrics labels
494
+ defaultLabels: (registry) => ({
495
+ namespace: registry.broker.namespace,
496
+ nodeID: registry.broker.nodeID
497
+ })
498
+ }
499
+ }
500
+ };
501
+
502
+ // src/configs/moleculer/registry.ts
503
+ var registryConfig = {
504
+ // Define balancing strategy. More info: https://moleculer.services/docs/0.14/balancing.html
505
+ // Available values: "RoundRobin", "Random", "CpuUsage", "Latency", "Shard"
506
+ strategy: "RoundRobin",
507
+ // Enable local action call preferring. Always call the local action instance if available.
508
+ preferLocal: true
509
+ };
510
+
511
+ // src/configs/moleculer/retry-policy.ts
512
+ var retryPolicyConfig = {
513
+ // Enable feature
514
+ enabled: false,
515
+ // Count of retries
516
+ retries: 5,
517
+ // First delay in milliseconds.
518
+ delay: 100,
519
+ // Maximum delay in milliseconds.
520
+ maxDelay: 1e3,
521
+ // Backoff factor for delay. 2 means exponential backoff.
522
+ factor: 2,
523
+ // A function to check failed requests.
524
+ check: (err) => !!err.retryable
525
+ };
526
+
527
+ // src/configs/moleculer/tracing.ts
528
+ var tracingConfig = {
529
+ enabled: true,
530
+ exporter: "Console",
531
+ events: true,
532
+ stackTrace: true
533
+ };
534
+
535
+ // src/configs/moleculer/tracking.ts
536
+ var trackingConfig = {
537
+ // Enable feature
538
+ enabled: false,
539
+ // Number of milliseconds to wait before shuting down the process.
540
+ shutdownTimeout: 5e3
541
+ };
542
+
543
+ // src/configs/moleculer/index.ts
544
+ var pkgNm = env3.string("NAMESPACE", "hios");
545
+ var nodeID = env3.string("NODE_ID") ?? `${pkgNm}-${os.hostname()}-${process.pid}`;
546
+ var configs = {
547
+ namespace: pkgNm,
548
+ nodeID,
549
+ metadata: {},
550
+ logger: logger_default,
551
+ // Default log level for built-in console logger. It can be overwritten in logger options above.
552
+ // Available values: trace, debug, info, warn, error, fatal
553
+ logLevel: "info",
554
+ cacher: env3.string("REDIS_URL", "Memory"),
555
+ // Define a serializer.
556
+ // Available values: "JSON", "Avro", "ProtoBuf", "MsgPack", "Notepack", "Thrift".
557
+ // More info: https://moleculer.services/docs/0.14/networking.html#Serialization
558
+ serializer: "JSON",
559
+ // Number of milliseconds to wait before reject a request with a RequestTimeout error. Disabled: 0
560
+ requestTimeout: 10 * 1e3,
561
+ // Retry policy settings. More info: https://moleculer.services/docs/0.14/fault-tolerance.html#Retry
562
+ retryPolicy: retryPolicyConfig,
563
+ // Limit of calling level. If it reaches the limit, broker will throw an MaxCallLevelError error. (Infinite loop protection)
564
+ maxCallLevel: 100,
565
+ // Number of seconds to send heartbeat packet to other nodes.
566
+ heartbeatInterval: 10,
567
+ // Number of seconds to wait before setting node to unavailable status.
568
+ heartbeatTimeout: 30,
569
+ // Cloning the params of context if enabled. High performance impact, use it with caution!
570
+ contextParamsCloning: false,
571
+ // Tracking requests and waiting for running requests before shuting down. More info: https://moleculer.services/docs/0.14/context.html#Context-tracking
572
+ tracking: trackingConfig,
573
+ // Disable built-in request & emit balancer. (Transporter must support it, as well.). More info: https://moleculer.services/docs/0.14/networking.html#Disabled-balancer
574
+ disableBalancer: false,
575
+ // Settings of Service Registry. More info: https://moleculer.services/docs/0.14/registry.html
576
+ registry: registryConfig,
577
+ // Settings of Circuit Breaker. More info: https://moleculer.services/docs/0.14/fault-tolerance.html#Circuit-Breaker
578
+ circuitBreaker: circuitBreakerConfig,
579
+ // Settings of bulkhead feature. More info: https://moleculer.services/docs/0.14/fault-tolerance.html#Bulkhead
580
+ bulkhead: bulkheadConfig,
581
+ // Enable action & event parameter validation. More info: https://moleculer.services/docs/0.14/validating.html
582
+ validator: "Fastest",
583
+ // errorHandler: null,
584
+ transporter: env3.string("TRANSPORTER_URL"),
585
+ // Enable/disable built-in metrics function. More info: https://moleculer.services/docs/0.14/metrics.html
586
+ metrics: metricsConfig,
587
+ // Enable built-in tracing function. More info: https://moleculer.services/docs/0.14/tracing.html
588
+ tracing: tracingConfig,
589
+ middlewares: [
590
+ ChannelsMiddleware,
591
+ PermissionsMiddleware,
592
+ ContextHelpersMiddleware
593
+ ]
594
+ };
595
+ var moleculer_default = configs;
596
+ var nodeEnv = env3.string("NODE_ENV", "development");
597
+ var isDev = nodeEnv === "development";
598
+ var isTest = nodeEnv === "test";
599
+ var isProd = nodeEnv === "production";
600
+ var REDIS_URL = env3.string("REDIS_URL");
601
+ var env_default = env3;
602
+
603
+ // src/errors/permission.error.ts
604
+ var PermissionError = class _PermissionError extends Error {
605
+ constructor(message, data) {
606
+ super(message);
607
+ this.code = "PERMISSION_DENIED";
608
+ this.statusCode = 403;
609
+ this.name = "PermissionError";
610
+ this.data = data;
611
+ if (Error.captureStackTrace) {
612
+ Error.captureStackTrace(this, _PermissionError);
613
+ }
614
+ }
615
+ };
616
+
617
+ // src/errors/auth.error.ts
618
+ var AuthenticationError = class _AuthenticationError extends Error {
619
+ constructor(message, data) {
620
+ super(message);
621
+ this.code = "AUTH_REQUIRED";
622
+ this.statusCode = 401;
623
+ this.name = "AuthenticationError";
624
+ this.data = data;
625
+ if (Error.captureStackTrace) {
626
+ Error.captureStackTrace(this, _AuthenticationError);
627
+ }
628
+ }
629
+ };
630
+ var TenantError = class _TenantError extends Error {
631
+ constructor(message, data) {
632
+ super(message);
633
+ this.code = "TENANT_REQUIRED";
634
+ this.statusCode = 401;
635
+ this.name = "TenantError";
636
+ this.data = data;
637
+ if (Error.captureStackTrace) {
638
+ Error.captureStackTrace(this, _TenantError);
639
+ }
640
+ }
641
+ };
642
+
643
+ // src/middlewares/permissions.middleware.ts
644
+ var permissionHandlers = {
645
+ [PERMISSIONS.AUTHENTICATED]: async (ctx) => !!ctx.meta.user?.id,
646
+ [PERMISSIONS.TENANT_OWNER]: async (ctx) => ctx.meta.user?.roles.includes(PERMISSIONS.OWNER) ?? false,
647
+ [PERMISSIONS.TENANT_MEMBER]: async (ctx) => !!(ctx.meta.user?.tenantId && ctx.meta.tenantId && ctx.meta.user.tenantId === ctx.meta.tenantId)
648
+ };
649
+ var PermissionsMiddleware = {
650
+ // Wrap local action handlers
651
+ localAction(handler, action) {
652
+ if (!action.permissions) {
653
+ return handler;
654
+ }
655
+ const permissions = Array.isArray(action.permissions) ? action.permissions : [action.permissions];
656
+ const permissionNames = [];
657
+ const permissionFunctions = [];
658
+ permissions.forEach((permission) => {
659
+ if (typeof permission === "function") {
660
+ permissionFunctions.push(permission);
661
+ return;
662
+ }
663
+ if (typeof permission === "string") {
664
+ if (permission in permissionHandlers) {
665
+ const handler2 = permissionHandlers[permission];
666
+ permissionFunctions.push(handler2);
667
+ return;
668
+ }
669
+ permissionNames.push(permission);
670
+ return;
671
+ }
672
+ });
673
+ return async function CheckPermissionsMiddleware(ctx) {
674
+ let hasAccess = false;
675
+ if (ctx.meta.user?.roles.includes(PERMISSIONS.OWNER)) {
676
+ hasAccess = true;
677
+ }
678
+ if (!hasAccess && permissionFunctions.length > 0) {
679
+ const results = await Promise.allSettled(
680
+ permissionFunctions.map((fn) => fn(ctx, action))
681
+ );
682
+ hasAccess = results.some(
683
+ (result) => result.status === "fulfilled" && !!result.value
684
+ );
685
+ const failures = results.filter((r) => r.status === "rejected");
686
+ if (failures.length > 0) {
687
+ ctx.broker.logger.warn(
688
+ `${failures.length} permission functions failed`,
689
+ {
690
+ action: action.name,
691
+ userId: ctx.meta.user?.id
692
+ }
693
+ );
694
+ }
695
+ }
696
+ if (!hasAccess && permissionNames.length > 0) {
697
+ const userRoles = ctx.meta.user?.roles ?? [];
698
+ hasAccess = userRoles.some((role) => {
699
+ const rolePermissions = ROLE_PERMISSIONS[role] ?? [];
700
+ return permissionNames.some(
701
+ (permName) => rolePermissions.includes(permName)
702
+ );
703
+ });
704
+ }
705
+ if (!hasAccess) {
706
+ const user = ctx.meta.user;
707
+ ctx.broker.logger.warn("Access denied:", {
708
+ action: action.name,
709
+ userId: user?.id,
710
+ userRoles: user?.roles,
711
+ tenantId: ctx.meta.tenantId,
712
+ requiredPermissions: permissions
713
+ });
714
+ const errorDetails = isDev ? {
715
+ action: action.name,
716
+ requiredPermissions: permissions.map(
717
+ (p) => typeof p === "function" ? "[Function]" : String(p)
718
+ ),
719
+ userRoles: user?.roles ?? [],
720
+ userId: user?.id,
721
+ tenantId: ctx.meta.tenantId
722
+ } : {
723
+ action: action.name
724
+ };
725
+ throw new PermissionError(
726
+ "You do not have permission to perform this action",
727
+ errorDetails
728
+ );
729
+ }
730
+ return handler.call(this, ctx);
731
+ };
732
+ }
733
+ };
734
+
735
+ // src/utils/context-cache.ts
736
+ var ContextCache = class _ContextCache {
737
+ constructor() {
738
+ this.memoryCache = /* @__PURE__ */ new Map();
739
+ this.TTL = 5 * 60 * 1e3;
740
+ this.cleanupInterval = setInterval(() => this.cleanup(), 60 * 1e3);
741
+ }
742
+ static getInstance() {
743
+ if (!_ContextCache.instance) {
744
+ _ContextCache.instance = new _ContextCache();
745
+ }
746
+ return _ContextCache.instance;
747
+ }
748
+ async get(key, factory) {
749
+ const cached = this.memoryCache.get(key);
750
+ if (cached && Date.now() - cached.timestamp < this.TTL) {
751
+ return cached.value;
752
+ }
753
+ const value = await factory();
754
+ this.memoryCache.set(key, { value, timestamp: Date.now() });
755
+ return value;
756
+ }
757
+ set(key, value) {
758
+ this.memoryCache.set(key, { value, timestamp: Date.now() });
759
+ }
760
+ delete(key) {
761
+ return this.memoryCache.delete(key);
762
+ }
763
+ clear() {
764
+ this.memoryCache.clear();
765
+ }
766
+ cleanup() {
767
+ const now = Date.now();
768
+ const keysToDelete = [];
769
+ this.memoryCache.forEach((entry, key) => {
770
+ if (now - entry.timestamp > this.TTL) {
771
+ keysToDelete.push(key);
772
+ }
773
+ });
774
+ keysToDelete.forEach((key) => this.memoryCache.delete(key));
775
+ }
776
+ destroy() {
777
+ if (this.cleanupInterval) {
778
+ clearInterval(this.cleanupInterval);
779
+ }
780
+ this.clear();
781
+ }
782
+ };
783
+
784
+ // src/utils/permission-calculator.ts
785
+ var PermissionCalculator = class {
786
+ static calculateUserPermissions(user) {
787
+ if (!user || !Array.isArray(user.roles)) {
788
+ return [];
789
+ }
790
+ const explicitPermissions = Array.isArray(user.permissions) ? user.permissions : [];
791
+ const rolePermissions = user.roles.flatMap((role) => {
792
+ return ROLE_PERMISSIONS[role] ?? [];
793
+ });
794
+ const allPermissions = [...explicitPermissions, ...rolePermissions];
795
+ const uniquePermissions = [];
796
+ allPermissions.forEach((permission) => {
797
+ if (!uniquePermissions.includes(permission)) {
798
+ uniquePermissions.push(permission);
799
+ }
800
+ });
801
+ return uniquePermissions;
802
+ }
803
+ static hasPermission(user, permission) {
804
+ if (typeof permission !== "string" || !permission.trim()) {
805
+ return false;
806
+ }
807
+ if (!user || typeof user !== "object") {
808
+ return false;
809
+ }
810
+ if (!Array.isArray(user.roles)) {
811
+ return false;
812
+ }
813
+ if (Array.isArray(user.permissions) && user.permissions.includes(permission)) {
814
+ return true;
815
+ }
816
+ return user.roles.some((role) => {
817
+ const rolePermissions = ROLE_PERMISSIONS[role] ?? [];
818
+ return rolePermissions.includes(permission);
819
+ });
820
+ }
821
+ };
822
+
823
+ // src/middlewares/context-helpers.middleware.ts
824
+ var ContextHelpersMiddleware = {
825
+ // Add helper functions to context before action handlers
826
+ localAction(handler) {
827
+ return function ContextHelpersWrapper(ctx) {
828
+ const cache = ContextCache.getInstance();
829
+ const memoizedPermissions = /* @__PURE__ */ new Map();
830
+ ctx.hasPermission = function(permission) {
831
+ if (memoizedPermissions.has(permission)) {
832
+ const cachedResult = memoizedPermissions.get(permission);
833
+ return cachedResult === true;
834
+ }
835
+ const user = ctx.meta.user;
836
+ if (!user) {
837
+ memoizedPermissions.set(permission, false);
838
+ return false;
839
+ }
840
+ const result = PermissionCalculator.hasPermission(user, permission);
841
+ memoizedPermissions.set(permission, result);
842
+ return result;
843
+ };
844
+ ctx.getUserPermissions = async function() {
845
+ const user = ctx.meta.user;
846
+ if (!user) return [];
847
+ const cacheKey = `permissions:${user.id}:${JSON.stringify(user.roles)}`;
848
+ return cache.get(cacheKey, () => {
849
+ return PermissionCalculator.calculateUserPermissions(user);
850
+ });
851
+ };
852
+ ctx.hasRole = function(role) {
853
+ const user = ctx.ensureUser();
854
+ return Array.isArray(user.roles) && user.roles.includes(role);
855
+ };
856
+ ctx.isTenantMember = function() {
857
+ const user = ctx.ensureUser();
858
+ return !!(user.tenantId && ctx.meta.tenantId && user.tenantId === ctx.meta.tenantId);
859
+ };
860
+ ctx.isTenantOwner = function() {
861
+ return ctx.isTenantMember() && ctx.hasRole("OWNER");
862
+ };
863
+ ctx.ensureUser = () => {
864
+ if (!ctx.meta.user) {
865
+ ctx.broker.logger.error("Authentication required", {
866
+ action: ctx.action?.name,
867
+ requestId: ctx.meta.requestId,
868
+ userAgent: ctx.meta.userAgent,
869
+ ip: ctx.meta.clientIP
870
+ });
871
+ throw new AuthenticationError("Authentication required", {
872
+ code: "AUTH_REQUIRED",
873
+ statusCode: 401,
874
+ requestId: ctx.meta.requestId
875
+ });
876
+ }
877
+ return ctx.meta.user;
878
+ };
879
+ ctx.ensureTenant = () => {
880
+ if (!ctx.meta.tenantId) {
881
+ ctx.broker.logger.error("Tenant required", {
882
+ action: ctx.action?.name,
883
+ userId: ctx.meta.user?.id,
884
+ requestId: ctx.meta.requestId
885
+ });
886
+ throw new TenantError("Tenant required", {
887
+ code: "TENANT_REQUIRED",
888
+ statusCode: 401,
889
+ tenantId: ctx.meta.tenantId,
890
+ requestId: ctx.meta.requestId
891
+ });
892
+ }
893
+ return {
894
+ id: ctx.meta.tenantId,
895
+ name: ctx.meta.tenantName ?? ""
896
+ };
897
+ };
898
+ ctx.auditLog = function(action, resource, metadata) {
899
+ ctx.broker.logger.info("Audit log", {
900
+ action,
901
+ resource: resource ? {
902
+ type: typeof resource,
903
+ id: resource.id
904
+ } : void 0,
905
+ userId: ctx.meta.user?.id,
906
+ tenantId: ctx.meta.tenantId,
907
+ requestId: ctx.meta.requestId,
908
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
909
+ ...metadata
910
+ });
911
+ };
912
+ ctx.createError = function(message, code, statusCode = 400) {
913
+ const errorData = {
914
+ code,
915
+ statusCode,
916
+ userId: ctx.meta.user?.id,
917
+ tenantId: ctx.meta.tenantId,
918
+ requestId: ctx.meta.requestId,
919
+ action: ctx.action?.name,
920
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
921
+ };
922
+ ctx.broker.logger.warn("Context error created", {
923
+ message,
924
+ ...errorData
925
+ });
926
+ if (code === "AUTH_REQUIRED") {
927
+ return new AuthenticationError(message, errorData);
928
+ }
929
+ if (code === "TENANT_REQUIRED") {
930
+ return new TenantError(message, errorData);
931
+ }
932
+ return new Error(message);
933
+ };
934
+ return handler.call(this, ctx);
935
+ };
936
+ }
937
+ };
938
+
939
+ // src/mixins/datasource.mixin.ts
940
+ function DatasourceMixin(datasourceConstructors = {}) {
941
+ const datasourceInstances = {};
942
+ for (const [key, DatasourceClass] of Object.entries(datasourceConstructors)) {
943
+ datasourceInstances[key] = new DatasourceClass();
944
+ }
945
+ return {
946
+ /**
947
+ * Service created lifecycle hook
948
+ * Initialize datasources and store on service
949
+ */
950
+ async created() {
951
+ for (const [, datasource] of Object.entries(datasourceInstances)) {
952
+ datasource.broker = this.broker;
953
+ }
954
+ for (const [, datasource] of Object.entries(datasourceInstances)) {
955
+ if (typeof datasource.init === "function") {
956
+ await datasource.init();
957
+ }
958
+ }
959
+ this.$datasources = datasourceInstances;
960
+ },
961
+ /**
962
+ * Service started lifecycle hook
963
+ * Connect datasources that have connect method
964
+ */
965
+ async started() {
966
+ for (const [, datasource] of Object.entries(datasourceInstances)) {
967
+ if (typeof datasource.connect === "function") {
968
+ await datasource.connect();
969
+ }
970
+ }
971
+ },
972
+ /**
973
+ * Service stopped lifecycle hook
974
+ * Disconnect datasources that have disconnect method
975
+ */
976
+ async stopped() {
977
+ for (const [, datasource] of Object.entries(datasourceInstances)) {
978
+ if (typeof datasource.disconnect === "function") {
979
+ await datasource.disconnect();
980
+ }
981
+ }
982
+ },
983
+ /**
984
+ * Hooks to inject datasources into context
985
+ */
986
+ hooks: {
987
+ before: {
988
+ "*": function injectDatasources(ctx) {
989
+ const datasources = this.$datasources ?? {};
990
+ for (const [, datasource] of Object.entries(datasources)) {
991
+ datasource.context = ctx;
992
+ }
993
+ ctx.datasources = datasources;
994
+ }
995
+ }
996
+ }
997
+ };
998
+ }
999
+
1000
+ // src/utils/index.ts
1001
+ function omit(obj, keys) {
1002
+ const result = { ...obj };
1003
+ keys.forEach((key) => delete result[key]);
1004
+ return result;
1005
+ }
1006
+
1007
+ // src/service/define-service.ts
1008
+ function defineService(config) {
1009
+ const propsToOmit = ["datasources"];
1010
+ const serviceSchema = omit(
1011
+ config,
1012
+ propsToOmit
1013
+ );
1014
+ return {
1015
+ ...serviceSchema,
1016
+ mixins: [
1017
+ DatasourceMixin(config.datasources),
1018
+ MemoizeMixin(),
1019
+ ...serviceSchema.mixins ?? []
1020
+ ]
1021
+ };
1022
+ }
1023
+ var SecurityHelpers = {
1024
+ /**
1025
+ * Secure comparison using Node.js crypto.timingSafeEqual
1026
+ */
1027
+ secureCompare(a, b) {
1028
+ try {
1029
+ return crypto.timingSafeEqual(
1030
+ Buffer.from(a, "utf8"),
1031
+ Buffer.from(b, "utf8")
1032
+ );
1033
+ } catch {
1034
+ return false;
1035
+ }
1036
+ },
1037
+ /**
1038
+ * Validate webhook timestamp to prevent replay attacks
1039
+ */
1040
+ validateTimestamp(timestamp, maxAgeMs = 5 * 60 * 1e3) {
1041
+ return Date.now() - timestamp <= maxAgeMs;
1042
+ }
1043
+ };
1044
+ async function executeWithRetry(operation, maxRetries = 3, baseDelayMs = 1e3, context = "operation") {
1045
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1046
+ try {
1047
+ return await operation();
1048
+ } catch (error) {
1049
+ if (attempt === maxRetries) {
1050
+ const err = error instanceof Error ? error : new Error(String(error));
1051
+ throw new Error(
1052
+ `${context} failed after ${maxRetries} retries: ${err.message}`
1053
+ );
1054
+ }
1055
+ const delay = baseDelayMs * Math.pow(2, attempt);
1056
+ const jitter = Math.random() * 1e3;
1057
+ await new Promise((resolve) => setTimeout(resolve, delay + jitter));
1058
+ }
1059
+ }
1060
+ throw new Error("Retry logic error");
1061
+ }
1062
+ function defineIntegration(config) {
1063
+ const actions = {
1064
+ i_receiveWebhook: {
1065
+ rest: {
1066
+ method: "POST",
1067
+ path: "/:channelId"
1068
+ },
1069
+ params: {
1070
+ channelId: "string",
1071
+ payload: "object",
1072
+ headers: "object",
1073
+ timestamp: "number"
1074
+ },
1075
+ async handler(ctx) {
1076
+ const { channelId, payload, headers, timestamp } = ctx.params;
1077
+ const webhook = {
1078
+ tenantId: ctx.meta.tenantId ?? "unknown",
1079
+ // Should come from channel lookup
1080
+ channelId,
1081
+ platform: config.spec.platform,
1082
+ payload,
1083
+ // Raw webhook payload from gateway
1084
+ headers,
1085
+ timestamp
1086
+ };
1087
+ if (!SecurityHelpers.validateTimestamp(webhook.timestamp)) {
1088
+ throw new Error("Webhook timestamp too old - possible replay attack");
1089
+ }
1090
+ if (config.validateWebhook) {
1091
+ const isValid = await config.validateWebhook(webhook);
1092
+ if (!isValid) {
1093
+ throw new Error("Invalid webhook payload format");
1094
+ }
1095
+ }
1096
+ if (config.validateSignature) {
1097
+ const isValidSignature = config.validateSignature(webhook);
1098
+ if (!isValidSignature) {
1099
+ throw new Error("Webhook signature validation failed");
1100
+ }
1101
+ }
1102
+ const normalizedMessages = await config.normalize(webhook);
1103
+ const results = await Promise.allSettled(
1104
+ normalizedMessages.map(async (message) => {
1105
+ const payload2 = {
1106
+ tenantId: webhook.tenantId,
1107
+ channelId: webhook.channelId,
1108
+ platform: webhook.platform,
1109
+ message,
1110
+ timestamp: Date.now()
1111
+ };
1112
+ return ctx.broker.sendToChannel(
1113
+ INTEGRATION_CHANNELS.MESSAGE_RECEIVED,
1114
+ payload2,
1115
+ CHANNEL_CONFIG.HIGH_PRIORITY
1116
+ );
1117
+ })
1118
+ );
1119
+ const successful = results.filter(
1120
+ (result) => result.status === "fulfilled"
1121
+ ).length;
1122
+ const failed = results.filter(
1123
+ (result) => result.status === "rejected"
1124
+ ).length;
1125
+ if (failed > 0) {
1126
+ const failures = results.filter((result) => result.status === "rejected").map((result) => result.reason);
1127
+ ctx.broker.logger.warn(
1128
+ `Webhook processing partial failure: ${failed}/${normalizedMessages.length} messages failed`,
1129
+ { failures }
1130
+ );
1131
+ }
1132
+ return { success: true, messages: successful, failed };
1133
+ }
1134
+ },
1135
+ i_sendMessage: {
1136
+ rest: {
1137
+ method: "POST",
1138
+ path: "/send"
1139
+ },
1140
+ params: {
1141
+ message: "object",
1142
+ channelId: "string"
1143
+ },
1144
+ async handler(ctx) {
1145
+ const { message, channelId } = ctx.params;
1146
+ const integrationConfig = await config.getChannelConfig(ctx, channelId);
1147
+ if (!integrationConfig) {
1148
+ throw new Error(`Channel configuration not found: ${channelId}`);
1149
+ }
1150
+ const result = await executeWithRetry(
1151
+ () => config.sendMessage(ctx, message, integrationConfig),
1152
+ 3,
1153
+ 1e3,
1154
+ `Send message via ${config.spec.platform}`
1155
+ );
1156
+ try {
1157
+ if (result.success) {
1158
+ const sentPayload = {
1159
+ tenantId: integrationConfig.tenantId,
1160
+ channelId,
1161
+ platform: config.spec.platform,
1162
+ messageId: result.messageId,
1163
+ metadata: result.metadata,
1164
+ timestamp: Date.now()
1165
+ };
1166
+ await ctx.broker.sendToChannel(
1167
+ INTEGRATION_CHANNELS.MESSAGE_SENT,
1168
+ sentPayload,
1169
+ CHANNEL_CONFIG.DEFAULTS
1170
+ );
1171
+ } else {
1172
+ const failedPayload = {
1173
+ tenantId: integrationConfig.tenantId,
1174
+ channelId,
1175
+ platform: config.spec.platform,
1176
+ error: result.error?.message ?? "Unknown error",
1177
+ message,
1178
+ timestamp: Date.now()
1179
+ };
1180
+ await ctx.broker.sendToChannel(
1181
+ INTEGRATION_CHANNELS.MESSAGE_FAILED,
1182
+ failedPayload,
1183
+ CHANNEL_CONFIG.DEFAULTS
1184
+ );
1185
+ }
1186
+ } catch (channelError) {
1187
+ const err = channelError instanceof Error ? channelError : new Error(String(channelError));
1188
+ ctx.broker.logger.warn("Failed to send message event to channel", {
1189
+ error: err.message,
1190
+ messageId: result.messageId,
1191
+ platform: config.spec.platform
1192
+ });
1193
+ }
1194
+ return result;
1195
+ }
1196
+ },
1197
+ i_healthCheck: {
1198
+ rest: {
1199
+ method: "GET",
1200
+ path: "/health"
1201
+ },
1202
+ params: {
1203
+ config: { type: "object", optional: true }
1204
+ },
1205
+ async handler(ctx) {
1206
+ try {
1207
+ if (config.checkHealth) {
1208
+ const integrationConfig = ctx.params.config;
1209
+ if (integrationConfig) {
1210
+ return await config.checkHealth(ctx, integrationConfig);
1211
+ }
1212
+ }
1213
+ return {
1214
+ status: "healthy",
1215
+ message: `${config.spec.name} integration is running`,
1216
+ details: {
1217
+ id: config.spec.id,
1218
+ version: config.spec.version,
1219
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1220
+ capabilities: config.spec.capabilities
1221
+ }
1222
+ };
1223
+ } catch (error) {
1224
+ const err = error instanceof Error ? error : new Error(String(error));
1225
+ return {
1226
+ status: "unhealthy",
1227
+ message: err.message,
1228
+ details: {
1229
+ id: config.spec.id,
1230
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1231
+ }
1232
+ };
1233
+ }
1234
+ }
1235
+ },
1236
+ i_verifyWebhook: {
1237
+ rest: {
1238
+ method: "GET",
1239
+ path: "/:tenantId"
1240
+ },
1241
+ params: {
1242
+ tenantId: "string",
1243
+ mode: "string",
1244
+ token: "string",
1245
+ challenge: "string"
1246
+ },
1247
+ handler(ctx) {
1248
+ if (config.verifyWebhook) {
1249
+ const result = config.verifyWebhook(ctx.params);
1250
+ return result ?? "";
1251
+ }
1252
+ return ctx.params.challenge;
1253
+ }
1254
+ },
1255
+ i_validateCredentials: {
1256
+ params: {
1257
+ credentials: "object"
1258
+ },
1259
+ async handler(ctx) {
1260
+ if (config.validateCredentials) {
1261
+ return await config.validateCredentials(ctx.params.credentials);
1262
+ }
1263
+ return true;
1264
+ }
1265
+ }
1266
+ };
1267
+ if (config.actions) {
1268
+ Object.assign(actions, config.actions);
1269
+ }
1270
+ const baseService = defineService({
1271
+ name: config.name,
1272
+ version: config.version,
1273
+ settings: config.settings,
1274
+ dependencies: config.dependencies,
1275
+ datasources: config.datasources,
1276
+ metadata: {
1277
+ ...config.metadata,
1278
+ spec: config.spec
1279
+ },
1280
+ actions,
1281
+ events: config.events ?? {},
1282
+ methods: {
1283
+ ...config.methods ?? {},
1284
+ // Add integration-specific methods to the service methods (filter out undefined)
1285
+ // Required methods - no need for conditional
1286
+ normalize: config.normalize,
1287
+ getChannelConfig: config.getChannelConfig,
1288
+ sendMessage: config.sendMessage,
1289
+ // Optional methods
1290
+ ...config.validateWebhook && { validateWebhook: config.validateWebhook },
1291
+ ...config.verifyWebhook && { verifyWebhook: config.verifyWebhook },
1292
+ ...config.checkHealth && { checkHealth: config.checkHealth },
1293
+ ...config.validateCredentials && {
1294
+ validateCredentials: config.validateCredentials
1295
+ },
1296
+ ...config.validateSignature && {
1297
+ validateSignature: config.validateSignature
1298
+ }
1299
+ },
1300
+ created: config.created,
1301
+ started: config.started,
1302
+ stopped: config.stopped
1303
+ });
1304
+ return {
1305
+ ...baseService,
1306
+ // Only add the integration spec
1307
+ spec: config.spec
1308
+ };
1309
+ }
1310
+
1311
+ // src/types/platform.ts
1312
+ var IntegrationPlatform = /* @__PURE__ */ ((IntegrationPlatform2) => {
1313
+ IntegrationPlatform2["WHATSAPP"] = "whatsapp";
1314
+ IntegrationPlatform2["TELEGRAM"] = "telegram";
1315
+ IntegrationPlatform2["SLACK"] = "slack";
1316
+ IntegrationPlatform2["EMAIL"] = "email";
1317
+ IntegrationPlatform2["SMS"] = "sms";
1318
+ IntegrationPlatform2["INSTAGRAM"] = "instagram";
1319
+ IntegrationPlatform2["FACEBOOK"] = "facebook";
1320
+ IntegrationPlatform2["DISCORD"] = "discord";
1321
+ IntegrationPlatform2["WEBCHAT"] = "webchat";
1322
+ IntegrationPlatform2["CUSTOM"] = "custom";
1323
+ return IntegrationPlatform2;
1324
+ })(IntegrationPlatform || {});
1325
+ var IntegrationStatus = /* @__PURE__ */ ((IntegrationStatus2) => {
1326
+ IntegrationStatus2["CONFIGURED"] = "CONFIGURED";
1327
+ IntegrationStatus2["ACTIVE"] = "ACTIVE";
1328
+ IntegrationStatus2["INACTIVE"] = "INACTIVE";
1329
+ IntegrationStatus2["ERROR"] = "ERROR";
1330
+ IntegrationStatus2["SUSPENDED"] = "SUSPENDED";
1331
+ return IntegrationStatus2;
1332
+ })(IntegrationStatus || {});
1333
+ var IntegrationCapability = /* @__PURE__ */ ((IntegrationCapability2) => {
1334
+ IntegrationCapability2["SEND_MESSAGE"] = "send_message";
1335
+ IntegrationCapability2["RECEIVE_MESSAGE"] = "receive_message";
1336
+ IntegrationCapability2["SEND_IMAGE"] = "send_image";
1337
+ IntegrationCapability2["SEND_VIDEO"] = "send_video";
1338
+ IntegrationCapability2["SEND_AUDIO"] = "send_audio";
1339
+ IntegrationCapability2["SEND_FILE"] = "send_file";
1340
+ IntegrationCapability2["SEND_LOCATION"] = "send_location";
1341
+ IntegrationCapability2["SEND_BUTTONS"] = "send_buttons";
1342
+ IntegrationCapability2["SEND_CAROUSEL"] = "send_carousel";
1343
+ IntegrationCapability2["TYPING_INDICATOR"] = "typing_indicator";
1344
+ IntegrationCapability2["READ_RECEIPT"] = "read_receipt";
1345
+ IntegrationCapability2["GROUP_CHAT"] = "group_chat";
1346
+ IntegrationCapability2["REACTIONS"] = "reactions";
1347
+ IntegrationCapability2["THREADS"] = "threads";
1348
+ IntegrationCapability2["VOICE_CALL"] = "voice_call";
1349
+ IntegrationCapability2["VIDEO_CALL"] = "video_call";
1350
+ return IntegrationCapability2;
1351
+ })(IntegrationCapability || {});
1352
+
1353
+ // src/types/message.ts
1354
+ var MessageContentType = /* @__PURE__ */ ((MessageContentType2) => {
1355
+ MessageContentType2["TEXT"] = "text";
1356
+ MessageContentType2["IMAGE"] = "image";
1357
+ MessageContentType2["VIDEO"] = "video";
1358
+ MessageContentType2["AUDIO"] = "audio";
1359
+ MessageContentType2["FILE"] = "file";
1360
+ MessageContentType2["LOCATION"] = "location";
1361
+ MessageContentType2["BUTTONS"] = "buttons";
1362
+ MessageContentType2["CAROUSEL"] = "carousel";
1363
+ MessageContentType2["REACTION"] = "reaction";
1364
+ return MessageContentType2;
1365
+ })(MessageContentType || {});
1366
+
1367
+ // src/types/user.ts
1368
+ var UserRole = {
1369
+ OWNER: "OWNER",
1370
+ ADMIN: "ADMIN",
1371
+ MANAGER: "MANAGER",
1372
+ AGENT: "AGENT",
1373
+ VIEWER: "VIEWER"
1374
+ };
1375
+
1376
+ // src/datasources/base.datasource.ts
1377
+ var AbstractDatasource = class {
1378
+ /**
1379
+ * Default health check - always returns true
1380
+ * Override for custom health check logic
1381
+ */
1382
+ async healthCheck() {
1383
+ this.broker.logger.info(`Health check for datasource - ${this.name}`);
1384
+ return true;
1385
+ }
1386
+ /**
1387
+ * Default clear method - does nothing
1388
+ * Override to implement clearing logic
1389
+ */
1390
+ async clear() {
1391
+ }
1392
+ };
1393
+
1394
+ // src/datasources/prisma.datasource.ts
1395
+ var PrismaDatasource = class extends AbstractDatasource {
1396
+ constructor(prismaClient) {
1397
+ super();
1398
+ this.name = "prisma";
1399
+ this._client = null;
1400
+ this.providedClient = null;
1401
+ this.providedClient = prismaClient ?? null;
1402
+ }
1403
+ /**
1404
+ * Get Prisma client instance (singleton pattern)
1405
+ */
1406
+ get client() {
1407
+ this._client ?? (this._client = this.initializePrismaClient());
1408
+ this.updateTenantFromContext();
1409
+ return this._client;
1410
+ }
1411
+ /**
1412
+ * Initialize Prisma client using singleton pattern or provided instance
1413
+ */
1414
+ initializePrismaClient() {
1415
+ if (this.providedClient) {
1416
+ this.broker.logger.info("Using provided PrismaClient instance");
1417
+ return this.providedClient;
1418
+ }
1419
+ if (!globalThis.__prisma) {
1420
+ this.broker.logger.info("Creating new PrismaClient singleton");
1421
+ const baseClient = this.createClient();
1422
+ globalThis.__prisma = this.applyExtensions(baseClient);
1423
+ } else {
1424
+ this.broker.logger.info("Using existing PrismaClient singleton");
1425
+ }
1426
+ return globalThis.__prisma;
1427
+ }
1428
+ /**
1429
+ * Create a new Prisma client instance
1430
+ * Override this method in subclasses to provide the actual PrismaClient
1431
+ */
1432
+ createClient() {
1433
+ throw new Error(
1434
+ "createClient() must be implemented by subclass or provide PrismaClient in constructor. Example: class MyPrismaDataSource extends PrismaDatasource { protected createClient() { return new PrismaClient(); } }"
1435
+ );
1436
+ }
1437
+ /**
1438
+ * Apply extensions to the Prisma client
1439
+ * Override this method to add production extensions like soft delete, audit trails, etc.
1440
+ */
1441
+ applyExtensions(client) {
1442
+ return client;
1443
+ }
1444
+ /**
1445
+ * Get extended client with all applied extensions
1446
+ */
1447
+ get extendedClient() {
1448
+ return this.applyExtensions(this.client);
1449
+ }
1450
+ /**
1451
+ * Initialize datasource - called after broker injection
1452
+ */
1453
+ async init() {
1454
+ this.broker.logger.info("Initializing Prisma datasource");
1455
+ }
1456
+ /**
1457
+ * Called automatically when context is injected
1458
+ * Sets tenant context from service context if available
1459
+ */
1460
+ updateTenantFromContext() {
1461
+ const tenantId = this.context?.meta?.tenantId;
1462
+ if (tenantId && typeof tenantId === "string") {
1463
+ this.setTenantContext(tenantId);
1464
+ }
1465
+ }
1466
+ /**
1467
+ * Connect to database - called when service starts
1468
+ */
1469
+ async connect() {
1470
+ try {
1471
+ this.broker.logger.info("Connecting to database via Prisma");
1472
+ await this.client.$connect();
1473
+ this.broker.logger.info("Successfully connected to database");
1474
+ } catch (error) {
1475
+ this.broker.logger.error("Failed to connect to database:", error);
1476
+ throw error;
1477
+ }
1478
+ }
1479
+ /**
1480
+ * Disconnect from database - called when service stops
1481
+ */
1482
+ async disconnect() {
1483
+ try {
1484
+ this.broker.logger.info("Disconnecting from database");
1485
+ await this.client.$disconnect();
1486
+ this.broker.logger.info("Successfully disconnected from database");
1487
+ } catch (error) {
1488
+ this.broker.logger.error("Error disconnecting from database:", error);
1489
+ throw error;
1490
+ }
1491
+ }
1492
+ /**
1493
+ * Health check - verify database connectivity
1494
+ */
1495
+ async healthCheck() {
1496
+ try {
1497
+ this.broker.logger.info("Running Prisma health check");
1498
+ await this.client.$queryRaw`SELECT 1`;
1499
+ this.broker.logger.info("Prisma health check passed");
1500
+ return true;
1501
+ } catch (error) {
1502
+ this.broker.logger.error("Prisma health check failed:", error);
1503
+ return false;
1504
+ }
1505
+ }
1506
+ /**
1507
+ * Clear/reset data - useful for testing
1508
+ */
1509
+ async clear() {
1510
+ if (isTest || isDev) {
1511
+ this.broker.logger.warn(
1512
+ "Clearing database (only allowed in test/dev mode)"
1513
+ );
1514
+ const modelNames = Object.keys(this.client).filter(
1515
+ (key) => !key.startsWith("_") && !key.startsWith("$")
1516
+ );
1517
+ for (const modelName of modelNames.reverse()) {
1518
+ try {
1519
+ const model = this.client[modelName];
1520
+ if (model.deleteMany) {
1521
+ await model.deleteMany();
1522
+ }
1523
+ } catch (error) {
1524
+ this.broker.logger.debug(`Could not clear ${modelName}:`, error);
1525
+ }
1526
+ }
1527
+ } else {
1528
+ throw new Error(
1529
+ "Database clear is only allowed in test/development environments"
1530
+ );
1531
+ }
1532
+ }
1533
+ /**
1534
+ * Transaction wrapper with proper error handling
1535
+ */
1536
+ async transaction(fn, options) {
1537
+ try {
1538
+ return await this.client.$transaction(
1539
+ (tx) => fn(tx),
1540
+ options
1541
+ );
1542
+ } catch (error) {
1543
+ this.broker.logger.error("Transaction failed:", error);
1544
+ throw error;
1545
+ }
1546
+ }
1547
+ /**
1548
+ * Set tenant context for multi-tenant applications
1549
+ * Requires tenant extension to be applied
1550
+ */
1551
+ setTenantContext(tenantId) {
1552
+ const tenantClient = this.client;
1553
+ if (tenantClient.$setTenant) {
1554
+ tenantClient.$setTenant(tenantId);
1555
+ this.broker.logger.debug("Tenant context set:", { tenantId });
1556
+ } else {
1557
+ this.broker.logger.warn(
1558
+ "Tenant extension not available - setTenantContext ignored"
1559
+ );
1560
+ }
1561
+ }
1562
+ /**
1563
+ * Get current tenant context
1564
+ */
1565
+ getCurrentTenant() {
1566
+ const tenantClient = this.client;
1567
+ if (tenantClient.$getCurrentTenant) {
1568
+ return tenantClient.$getCurrentTenant();
1569
+ }
1570
+ return null;
1571
+ }
1572
+ /**
1573
+ * Execute with tenant context (automatically restores previous context)
1574
+ */
1575
+ async withTenant(tenantId, fn) {
1576
+ const previousTenant = this.getCurrentTenant();
1577
+ try {
1578
+ this.setTenantContext(tenantId);
1579
+ return await fn();
1580
+ } finally {
1581
+ if (previousTenant) {
1582
+ this.setTenantContext(previousTenant);
1583
+ } else {
1584
+ const tenantClient = this.client;
1585
+ if (tenantClient.$clearTenant) {
1586
+ tenantClient.$clearTenant();
1587
+ }
1588
+ }
1589
+ }
1590
+ }
1591
+ };
1592
+
1593
+ // src/datasources/extensions/soft-delete.extension.ts
1594
+ var softDeleteExtension = {
1595
+ name: "SoftDelete",
1596
+ query: {
1597
+ // Apply to all models
1598
+ $allModels: {
1599
+ // Override findMany to exclude soft deleted records
1600
+ async findMany({ args, query }) {
1601
+ args.where ?? (args.where = {});
1602
+ if (args.where.deletedAt === void 0) {
1603
+ args.where.deletedAt = null;
1604
+ }
1605
+ return query(args);
1606
+ },
1607
+ // Override findUnique to exclude soft deleted records
1608
+ async findUnique({ args, query }) {
1609
+ args.where ?? (args.where = {});
1610
+ if (args.where.deletedAt === void 0) {
1611
+ args.where.deletedAt = null;
1612
+ }
1613
+ return query(args);
1614
+ },
1615
+ // Override findFirst to exclude soft deleted records
1616
+ async findFirst({ args, query }) {
1617
+ args.where ?? (args.where = {});
1618
+ if (args.where.deletedAt === void 0) {
1619
+ args.where.deletedAt = null;
1620
+ }
1621
+ return query(args);
1622
+ },
1623
+ // Override delete to set deletedAt instead
1624
+ async delete({ args, query }) {
1625
+ return query({
1626
+ ...args,
1627
+ data: { deletedAt: /* @__PURE__ */ new Date() }
1628
+ });
1629
+ },
1630
+ // Override deleteMany to set deletedAt instead
1631
+ async deleteMany({ args, query }) {
1632
+ return query({
1633
+ ...args,
1634
+ data: { deletedAt: /* @__PURE__ */ new Date() }
1635
+ });
1636
+ }
1637
+ }
1638
+ },
1639
+ model: {
1640
+ $allModels: {
1641
+ // Add restore method to all models
1642
+ async restore(where) {
1643
+ const context = globalThis.Prisma?.getExtensionContext?.(this) ?? this;
1644
+ return context.update({
1645
+ where,
1646
+ data: { deletedAt: null }
1647
+ });
1648
+ },
1649
+ // Add findWithDeleted method to include soft deleted records
1650
+ async findWithDeleted(args) {
1651
+ const context = globalThis.Prisma?.getExtensionContext?.(this) ?? this;
1652
+ return context.findMany({
1653
+ ...args,
1654
+ where: {
1655
+ ...args.where
1656
+ // Don't filter by deletedAt
1657
+ }
1658
+ });
1659
+ },
1660
+ // Add findDeleted method to find only soft deleted records
1661
+ async findDeleted(args) {
1662
+ const context = globalThis.Prisma?.getExtensionContext?.(this) ?? this;
1663
+ return context.findMany({
1664
+ ...args,
1665
+ where: {
1666
+ ...args.where,
1667
+ deletedAt: { not: null }
1668
+ }
1669
+ });
1670
+ }
1671
+ }
1672
+ }
1673
+ };
1674
+
1675
+ // src/datasources/extensions/tenant.extension.ts
1676
+ function createTenantExtension() {
1677
+ const tenantContext = {
1678
+ currentTenantId: null
1679
+ };
1680
+ return {
1681
+ name: "Tenant",
1682
+ client: {
1683
+ // Set tenant context
1684
+ $setTenant(tenantId) {
1685
+ tenantContext.currentTenantId = tenantId;
1686
+ },
1687
+ // Get current tenant
1688
+ $getCurrentTenant() {
1689
+ return tenantContext.currentTenantId;
1690
+ },
1691
+ // Clear tenant context
1692
+ $clearTenant() {
1693
+ tenantContext.currentTenantId = null;
1694
+ }
1695
+ },
1696
+ query: {
1697
+ $allModels: {
1698
+ // Automatically add tenantId filter to all read operations
1699
+ async findMany({ args, query }) {
1700
+ var _a;
1701
+ if (tenantContext.currentTenantId) {
1702
+ args.where ?? (args.where = {});
1703
+ (_a = args.where).tenantId ?? (_a.tenantId = tenantContext.currentTenantId);
1704
+ }
1705
+ return query(args);
1706
+ },
1707
+ async findUnique({ args, query }) {
1708
+ var _a;
1709
+ if (tenantContext.currentTenantId) {
1710
+ args.where ?? (args.where = {});
1711
+ (_a = args.where).tenantId ?? (_a.tenantId = tenantContext.currentTenantId);
1712
+ }
1713
+ return query(args);
1714
+ },
1715
+ async findFirst({ args, query }) {
1716
+ var _a;
1717
+ if (tenantContext.currentTenantId) {
1718
+ args.where ?? (args.where = {});
1719
+ (_a = args.where).tenantId ?? (_a.tenantId = tenantContext.currentTenantId);
1720
+ }
1721
+ return query(args);
1722
+ },
1723
+ // Automatically add tenantId to create operations
1724
+ async create({ args, query }) {
1725
+ var _a;
1726
+ if (tenantContext.currentTenantId) {
1727
+ args.data ?? (args.data = {});
1728
+ (_a = args.data).tenantId ?? (_a.tenantId = tenantContext.currentTenantId);
1729
+ }
1730
+ return query(args);
1731
+ },
1732
+ // Add tenantId filter to update operations
1733
+ async update({ args, query }) {
1734
+ var _a;
1735
+ if (tenantContext.currentTenantId) {
1736
+ args.where ?? (args.where = {});
1737
+ (_a = args.where).tenantId ?? (_a.tenantId = tenantContext.currentTenantId);
1738
+ }
1739
+ return query(args);
1740
+ },
1741
+ // Add tenantId filter to delete operations
1742
+ async delete({ args, query }) {
1743
+ var _a;
1744
+ if (tenantContext.currentTenantId) {
1745
+ args.where ?? (args.where = {});
1746
+ (_a = args.where).tenantId ?? (_a.tenantId = tenantContext.currentTenantId);
1747
+ }
1748
+ return query(args);
1749
+ }
1750
+ }
1751
+ }
1752
+ };
1753
+ }
1754
+
1755
+ // src/datasources/extensions/retry.extension.ts
1756
+ var retryExtension = {
1757
+ name: "Retry",
1758
+ client: {
1759
+ async $retryTransaction(fn, options = {}) {
1760
+ const {
1761
+ maxRetries = 3,
1762
+ baseDelay = 100,
1763
+ maxDelay = 5e3,
1764
+ retryableErrors = [
1765
+ "P2034",
1766
+ // Transaction failed due to a write conflict
1767
+ "P2002",
1768
+ // Unique constraint failed
1769
+ "P5000"
1770
+ // Raw query failed
1771
+ ]
1772
+ } = options;
1773
+ let lastError;
1774
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1775
+ try {
1776
+ return await this.$transaction(fn, {
1777
+ timeout: 1e4,
1778
+ maxWait: 5e3
1779
+ });
1780
+ } catch (error) {
1781
+ lastError = error;
1782
+ const isRetryable = retryableErrors.some(
1783
+ (code) => error.code === code || error.message?.includes(code)
1784
+ );
1785
+ if (!isRetryable || attempt === maxRetries) {
1786
+ throw error;
1787
+ }
1788
+ const delay = Math.min(
1789
+ baseDelay * Math.pow(2, attempt) + Math.random() * 100,
1790
+ maxDelay
1791
+ );
1792
+ if (typeof globalThis !== "undefined" && globalThis.console) {
1793
+ globalThis.console.warn(
1794
+ `Transaction retry ${attempt + 1}/${maxRetries} after ${delay}ms:`,
1795
+ {
1796
+ error: error.message,
1797
+ code: error.code
1798
+ }
1799
+ );
1800
+ }
1801
+ await new Promise((resolve) => setTimeout(resolve, delay));
1802
+ }
1803
+ }
1804
+ throw lastError ?? new Error("Transaction failed after maximum retries");
1805
+ }
1806
+ }
1807
+ };
1808
+
1809
+ export { AbstractDatasource, CHANNELS, ContextHelpersMiddleware, CreateHealthCheckMiddleware, DatasourceMixin, HEALTH_CHECK_DEFAULTS, INTEGRATION_CHANNELS, IntegrationCapability, IntegrationPlatform, IntegrationStatus, MemoizeMixin, MessageContentType, NAMESPACE, PERMISSIONS, PermissionsMiddleware, PrismaDatasource, REDIS_URL, ROLE_PERMISSIONS, UserRole, createDatasourceMiddleware, createTenantExtension, defineIntegration, defineService, env_default as env, isDev, isProd, isTest, moleculer_default as moleculer, nodeEnv, omit, retryExtension, softDeleteExtension };