@hiliosai/sdk 0.1.16 → 0.1.18

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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Context, ServiceBroker, ServiceSchema as ServiceSchema$1, ServiceSettingSchema, Service, BrokerOptions, ActionSchema, ServiceEvents, ServiceMethods, ServiceHooks, Middleware } from 'moleculer';
1
+ import { Context, ServiceBroker, ServiceSchema as ServiceSchema$1, ServiceSettingSchema, Service, ActionSchema, ServiceEvents, ServiceMethods, ServiceHooks, Middleware, BrokerOptions } from 'moleculer';
2
2
  import env from '@ltv/env';
3
3
  export { default as env } from '@ltv/env';
4
4
 
@@ -466,8 +466,6 @@ declare const PERMISSIONS: {
466
466
  };
467
467
  declare const ROLE_PERMISSIONS: Record<string, string[]>;
468
468
 
469
- declare const configs: BrokerOptions;
470
-
471
469
  declare enum IntegrationPlatform {
472
470
  WHATSAPP = "whatsapp",
473
471
  TELEGRAM = "telegram",
@@ -860,6 +858,8 @@ declare function defineService<TSettings = unknown, TDatasourceConstructors exte
860
858
 
861
859
  declare function defineIntegration<TSettings = unknown, TDatasourceConstructors extends DatasourceConstructorRegistry = DatasourceConstructorRegistry>(config: IntegrationServiceConfig<TSettings, TDatasourceConstructors>): IntegrationServiceSchema<TSettings>;
862
860
 
861
+ declare const configs: BrokerOptions;
862
+
863
863
  declare const nodeEnv: string;
864
864
  declare const isDev: boolean;
865
865
  declare const isTest: boolean;
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import env3 from '@ltv/env';
1
+ import env4 from '@ltv/env';
2
2
  import http from 'http';
3
+ import crypto from 'crypto';
3
4
  import os from 'os';
4
5
  import { Middleware } from '@moleculer/channels';
5
- import crypto from 'crypto';
6
6
 
7
7
  // src/middlewares/datasource.middleware.ts
8
8
  function initializeDatasources(constructorRegistry) {
@@ -52,12 +52,12 @@ function MemoizeMixin(options) {
52
52
  }
53
53
  };
54
54
  }
55
- var HEALTH_CHECK_PORT = env3.int("HEALTH_CHECK_PORT", 3301);
56
- var HEALTH_CHECK_READINESS_PATH = env3.string(
55
+ var HEALTH_CHECK_PORT = env4.int("HEALTH_CHECK_PORT", 3301);
56
+ var HEALTH_CHECK_READINESS_PATH = env4.string(
57
57
  "HEALTH_CHECK_READINESS_PATH",
58
58
  "/readyz"
59
59
  );
60
- var HEALTH_CHECK_LIVENESS_PATH = env3.string(
60
+ var HEALTH_CHECK_LIVENESS_PATH = env4.string(
61
61
  "HEALTH_CHECK_LIVENESS_PATH",
62
62
  "/livez"
63
63
  );
@@ -244,6 +244,12 @@ var ROLE_PERMISSIONS = {
244
244
  PERMISSIONS["config.read"]
245
245
  ]
246
246
  };
247
+ var nodeEnv = env4.string("NODE_ENV", "development");
248
+ var isDev = nodeEnv === "development";
249
+ var isTest = nodeEnv === "test";
250
+ var isProd = nodeEnv === "production";
251
+ var REDIS_URL = env4.string("REDIS_URL");
252
+ var env_default = env4;
247
253
 
248
254
  // src/errors/permission.error.ts
249
255
  var PermissionError = class _PermissionError extends Error {
@@ -285,6 +291,98 @@ var TenantError = class _TenantError extends Error {
285
291
  }
286
292
  };
287
293
 
294
+ // src/middlewares/permissions.middleware.ts
295
+ var permissionHandlers = {
296
+ [PERMISSIONS.AUTHENTICATED]: async (ctx) => !!ctx.meta.user?.id,
297
+ [PERMISSIONS.TENANT_OWNER]: async (ctx) => ctx.meta.user?.roles.includes(PERMISSIONS.OWNER) ?? false,
298
+ [PERMISSIONS.TENANT_MEMBER]: async (ctx) => !!(ctx.meta.user?.tenantId && ctx.meta.tenantId && ctx.meta.user.tenantId === ctx.meta.tenantId)
299
+ };
300
+ var PermissionsMiddleware = {
301
+ // Wrap local action handlers
302
+ localAction(handler, action) {
303
+ if (!action.permissions) {
304
+ return handler;
305
+ }
306
+ const permissions = Array.isArray(action.permissions) ? action.permissions : [action.permissions];
307
+ const permissionNames = [];
308
+ const permissionFunctions = [];
309
+ permissions.forEach((permission) => {
310
+ if (typeof permission === "function") {
311
+ permissionFunctions.push(permission);
312
+ return;
313
+ }
314
+ if (typeof permission === "string") {
315
+ if (permission in permissionHandlers) {
316
+ const handler2 = permissionHandlers[permission];
317
+ permissionFunctions.push(handler2);
318
+ return;
319
+ }
320
+ permissionNames.push(permission);
321
+ return;
322
+ }
323
+ });
324
+ return async function CheckPermissionsMiddleware(ctx) {
325
+ let hasAccess = false;
326
+ if (ctx.meta.user?.roles.includes(PERMISSIONS.OWNER)) {
327
+ hasAccess = true;
328
+ }
329
+ if (!hasAccess && permissionFunctions.length > 0) {
330
+ const results = await Promise.allSettled(
331
+ permissionFunctions.map((fn) => fn(ctx, action))
332
+ );
333
+ hasAccess = results.some(
334
+ (result) => result.status === "fulfilled" && !!result.value
335
+ );
336
+ const failures = results.filter((r) => r.status === "rejected");
337
+ if (failures.length > 0) {
338
+ ctx.broker.logger.warn(
339
+ `${failures.length} permission functions failed`,
340
+ {
341
+ action: action.name,
342
+ userId: ctx.meta.user?.id
343
+ }
344
+ );
345
+ }
346
+ }
347
+ if (!hasAccess && permissionNames.length > 0) {
348
+ const userRoles = ctx.meta.user?.roles ?? [];
349
+ hasAccess = userRoles.some((role) => {
350
+ const rolePermissions = ROLE_PERMISSIONS[role] ?? [];
351
+ return permissionNames.some(
352
+ (permName) => rolePermissions.includes(permName)
353
+ );
354
+ });
355
+ }
356
+ if (!hasAccess) {
357
+ const user = ctx.meta.user;
358
+ ctx.broker.logger.warn("Access denied:", {
359
+ action: action.name,
360
+ userId: user?.id,
361
+ userRoles: user?.roles,
362
+ tenantId: ctx.meta.tenantId,
363
+ requiredPermissions: permissions
364
+ });
365
+ const errorDetails = isDev ? {
366
+ action: action.name,
367
+ requiredPermissions: permissions.map(
368
+ (p) => typeof p === "function" ? "[Function]" : String(p)
369
+ ),
370
+ userRoles: user?.roles ?? [],
371
+ userId: user?.id,
372
+ tenantId: ctx.meta.tenantId
373
+ } : {
374
+ action: action.name
375
+ };
376
+ throw new PermissionError(
377
+ "You do not have permission to perform this action",
378
+ errorDetails
379
+ );
380
+ }
381
+ return handler.call(this, ctx);
382
+ };
383
+ }
384
+ };
385
+
288
386
  // src/utils/context-cache.ts
289
387
  var ContextCache = class _ContextCache {
290
388
  constructor() {
@@ -489,16 +587,91 @@ var ContextHelpersMiddleware = {
489
587
  }
490
588
  };
491
589
 
492
- // src/configs/moleculer/bulkhead.ts
493
- var bulkheadConfig = {
494
- // Enable feature.
495
- enabled: false,
496
- // Maximum concurrent executions.
497
- concurrency: 10,
498
- // Maximum size of queue
499
- maxQueueSize: 100
500
- };
501
- var NAMESPACE = env3.string("NAMESPACE", "hios").toLowerCase();
590
+ // src/mixins/datasource.mixin.ts
591
+ function DatasourceMixin(datasourceConstructors = {}) {
592
+ const datasourceInstances = {};
593
+ for (const [key, DatasourceClass] of Object.entries(datasourceConstructors)) {
594
+ datasourceInstances[key] = new DatasourceClass();
595
+ }
596
+ return {
597
+ /**
598
+ * Service created lifecycle hook
599
+ * Initialize datasources and store on service
600
+ */
601
+ async created() {
602
+ for (const [, datasource] of Object.entries(datasourceInstances)) {
603
+ datasource.broker = this.broker;
604
+ }
605
+ for (const [, datasource] of Object.entries(datasourceInstances)) {
606
+ if (typeof datasource.init === "function") {
607
+ await datasource.init();
608
+ }
609
+ }
610
+ this.$datasources = datasourceInstances;
611
+ },
612
+ /**
613
+ * Service started lifecycle hook
614
+ * Connect datasources that have connect method
615
+ */
616
+ async started() {
617
+ for (const [, datasource] of Object.entries(datasourceInstances)) {
618
+ if (typeof datasource.connect === "function") {
619
+ await datasource.connect();
620
+ }
621
+ }
622
+ },
623
+ /**
624
+ * Service stopped lifecycle hook
625
+ * Disconnect datasources that have disconnect method
626
+ */
627
+ async stopped() {
628
+ for (const [, datasource] of Object.entries(datasourceInstances)) {
629
+ if (typeof datasource.disconnect === "function") {
630
+ await datasource.disconnect();
631
+ }
632
+ }
633
+ },
634
+ /**
635
+ * Hooks to inject datasources into context
636
+ */
637
+ hooks: {
638
+ before: {
639
+ "*": function injectDatasources(ctx) {
640
+ const datasources = this.$datasources ?? {};
641
+ for (const [, datasource] of Object.entries(datasources)) {
642
+ datasource.context = ctx;
643
+ }
644
+ ctx.datasources = datasources;
645
+ }
646
+ }
647
+ }
648
+ };
649
+ }
650
+
651
+ // src/utils/index.ts
652
+ function omit(obj, keys) {
653
+ const result = { ...obj };
654
+ keys.forEach((key) => delete result[key]);
655
+ return result;
656
+ }
657
+
658
+ // src/service/define-service.ts
659
+ function defineService(config) {
660
+ const propsToOmit = ["datasources"];
661
+ const serviceSchema = omit(
662
+ config,
663
+ propsToOmit
664
+ );
665
+ return {
666
+ ...serviceSchema,
667
+ mixins: [
668
+ DatasourceMixin(config.datasources),
669
+ MemoizeMixin(),
670
+ ...serviceSchema.mixins ?? []
671
+ ]
672
+ };
673
+ }
674
+ var NAMESPACE = env4.string("NAMESPACE", "hios").toLowerCase();
502
675
  var CHANNELS = {
503
676
  // Webhook processing channels
504
677
  WEBHOOK: {
@@ -589,437 +762,7 @@ var SUBJECTS = {
589
762
  // All DLQ subjects
590
763
  DLQ_ALL: `${NAMESPACE}.dlq.>`};
591
764
 
592
- // src/configs/moleculer/channels.ts
593
- var NAMESPACE2 = NAMESPACE.toUpperCase();
594
- var middleware = Middleware({
595
- adapter: {
596
- type: "NATS",
597
- options: {
598
- nats: {
599
- url: env3.string("NATS_URL", "nats://localhost:4222"),
600
- /** Connection options for reliability */
601
- connectionOptions: {
602
- name: "hios",
603
- timeout: 1e4,
604
- reconnect: true,
605
- maxReconnectAttempts: 10,
606
- reconnectTimeWait: 2e3,
607
- maxReconnectTimeWait: 3e4,
608
- pingInterval: 2e4,
609
- maxPingOut: 2
610
- },
611
- /**
612
- * Stream configuration for multi-tenant messaging
613
- *
614
- * Environment variables for production:
615
- * - NATS_MAX_MESSAGES: Default 100K (dev) -> 10M+ (prod)
616
- * - NATS_MAX_BYTES_GB: Default 1GB (dev) -> 100GB+ (prod)
617
- * - NATS_MAX_AGE_DAYS: Default 7 (dev) -> 30+ (prod)
618
- * - NATS_MAX_MSG_SIZE_MB: Default 1MB (dev) -> 5MB (prod)
619
- * - NATS_REPLICAS: Default 1 (dev) -> 3 (prod)
620
- */
621
- streamConfig: {
622
- name: `${NAMESPACE2}_MESSAGES`,
623
- subjects: [
624
- SUBJECTS.WEBHOOK_ALL,
625
- SUBJECTS.PROCESSING_ALL,
626
- SUBJECTS.RESPONSE_ALL,
627
- SUBJECTS.SYSTEM_ALL,
628
- SUBJECTS.DLQ_ALL
629
- ],
630
- retention: "limits",
631
- max_msgs: env3.int("NATS_MAX_MESSAGES", 1e5),
632
- // 100K for dev, 10M+ for prod
633
- max_bytes: env3.int("NATS_MAX_BYTES_GB", 1) * 1024 * 1024 * 1024,
634
- // 1GB for dev, 100GB+ for prod
635
- max_age: env3.int("NATS_MAX_AGE_DAYS", 7) * 24 * 60 * 60 * 1e9,
636
- // 7 days dev, 30+ days prod
637
- max_msg_size: env3.int("NATS_MAX_MSG_SIZE_MB", 1) * 1024 * 1024,
638
- // 1MB dev, 5MB prod
639
- storage: "file",
640
- // Persistent storage
641
- num_replicas: env3.int("NATS_REPLICAS", 1),
642
- // 1 for dev, 3 for prod
643
- discard: "old",
644
- // Remove old messages when limits hit
645
- duplicate_window: 2 * 60 * 1e9
646
- // 2 minutes dedup window
647
- },
648
- /** Consumer options optimized for LLM processing */
649
- consumerOptions: {
650
- config: {
651
- // Start with new messages (don't replay old ones on restart)
652
- deliver_policy: "new",
653
- // Explicit acknowledgment required (critical for LLM processing)
654
- ack_policy: "explicit",
655
- // Allow 5 unacknowledged messages per consumer (rate limiting)
656
- max_ack_pending: 5,
657
- // Acknowledgment timeout for LLM processing (2 minutes)
658
- ack_wait: 120 * 1e9,
659
- // 2 minutes in nanoseconds
660
- // Maximum delivery attempts before dead letter
661
- max_deliver: 3,
662
- // Backoff for failed message retries
663
- backoff: [
664
- 1e9,
665
- // 1 second
666
- 5e9,
667
- // 5 seconds
668
- 3e10
669
- // 30 seconds
670
- ]
671
- }
672
- }
673
- },
674
- /** Application-level flow control */
675
- maxInFlight: 5,
676
- // Limit concurrent LLM requests per service
677
- maxRetries: 2,
678
- // App-level retries (NATS handles delivery retries)
679
- /** Dead letter queue for failed messages */
680
- deadLettering: {
681
- enabled: true,
682
- queueName: "FAILED_MESSAGES"
683
- // Send to dead letter after NATS max_deliver attempts
684
- }
685
- }
686
- }
687
- });
688
- var ChannelsMiddleware = {
689
- ...middleware
690
- };
691
-
692
- // src/configs/moleculer/circuit-breaker.ts
693
- var circuitBreakerConfig = {
694
- // Enable feature
695
- enabled: false,
696
- // Threshold value. 0.5 means that 50% should be failed for tripping.
697
- threshold: 0.5,
698
- // Minimum request count. Below it, CB does not trip.
699
- minRequestCount: 20,
700
- // Number of seconds for time window.
701
- windowTime: 60,
702
- // Number of milliseconds to switch from open to half-open state
703
- halfOpenTime: 10 * 1e3,
704
- // A function to check failed requests.
705
- check: (err) => err.code >= 500
706
- };
707
-
708
- // src/configs/moleculer/logger.ts
709
- var loggerConfig = {
710
- type: "Console",
711
- options: {
712
- // Using colors on the output
713
- colors: true,
714
- // Print module names with different colors (like docker-compose for containers)
715
- moduleColors: false,
716
- // Line formatter. It can be "json", "short", "simple", "full", a `Function` or a template string like "{timestamp} {level} {nodeID}/{mod}: {msg}"
717
- formatter: "full",
718
- // Custom object printer. If not defined, it uses the `util.inspect` method.
719
- objectPrinter: null,
720
- // Auto-padding the module name in order to messages begin at the same column.
721
- autoPadding: false
722
- }
723
- };
724
- var logger_default = loggerConfig;
725
-
726
- // src/configs/moleculer/metrics.ts
727
- var metricsConfig = {
728
- enabled: false,
729
- // Available built-in reporters: "Console", "CSV", "Event", "Prometheus", "Datadog", "StatsD"
730
- reporter: {
731
- type: "Prometheus",
732
- options: {
733
- // HTTP port
734
- port: 3030,
735
- // HTTP URL path
736
- path: "/metrics",
737
- // Default labels which are appended to all metrics labels
738
- defaultLabels: (registry) => ({
739
- namespace: registry.broker.namespace,
740
- nodeID: registry.broker.nodeID
741
- })
742
- }
743
- }
744
- };
745
-
746
- // src/configs/moleculer/registry.ts
747
- var registryConfig = {
748
- // Define balancing strategy. More info: https://moleculer.services/docs/0.14/balancing.html
749
- // Available values: "RoundRobin", "Random", "CpuUsage", "Latency", "Shard"
750
- strategy: "RoundRobin",
751
- // Enable local action call preferring. Always call the local action instance if available.
752
- preferLocal: true
753
- };
754
-
755
- // src/configs/moleculer/retry-policy.ts
756
- var retryPolicyConfig = {
757
- // Enable feature
758
- enabled: false,
759
- // Count of retries
760
- retries: 5,
761
- // First delay in milliseconds.
762
- delay: 100,
763
- // Maximum delay in milliseconds.
764
- maxDelay: 1e3,
765
- // Backoff factor for delay. 2 means exponential backoff.
766
- factor: 2,
767
- // A function to check failed requests.
768
- check: (err) => !!err.retryable
769
- };
770
-
771
- // src/configs/moleculer/tracing.ts
772
- var tracingConfig = {
773
- enabled: true,
774
- exporter: "Console",
775
- events: true,
776
- stackTrace: true
777
- };
778
-
779
- // src/configs/moleculer/tracking.ts
780
- var trackingConfig = {
781
- // Enable feature
782
- enabled: false,
783
- // Number of milliseconds to wait before shuting down the process.
784
- shutdownTimeout: 5e3
785
- };
786
-
787
- // src/configs/moleculer/index.ts
788
- var pkgNm = env3.string("NAMESPACE", "hios");
789
- var nodeID = env3.string("NODE_ID") ?? `${pkgNm}-${os.hostname()}-${process.pid}`;
790
- var configs = {
791
- namespace: pkgNm,
792
- nodeID,
793
- metadata: {},
794
- logger: logger_default,
795
- // Default log level for built-in console logger. It can be overwritten in logger options above.
796
- // Available values: trace, debug, info, warn, error, fatal
797
- logLevel: "info",
798
- cacher: env3.string("REDIS_URL", "Memory"),
799
- // Define a serializer.
800
- // Available values: "JSON", "Avro", "ProtoBuf", "MsgPack", "Notepack", "Thrift".
801
- // More info: https://moleculer.services/docs/0.14/networking.html#Serialization
802
- serializer: "JSON",
803
- // Number of milliseconds to wait before reject a request with a RequestTimeout error. Disabled: 0
804
- requestTimeout: 10 * 1e3,
805
- // Retry policy settings. More info: https://moleculer.services/docs/0.14/fault-tolerance.html#Retry
806
- retryPolicy: retryPolicyConfig,
807
- // Limit of calling level. If it reaches the limit, broker will throw an MaxCallLevelError error. (Infinite loop protection)
808
- maxCallLevel: 100,
809
- // Number of seconds to send heartbeat packet to other nodes.
810
- heartbeatInterval: 10,
811
- // Number of seconds to wait before setting node to unavailable status.
812
- heartbeatTimeout: 30,
813
- // Cloning the params of context if enabled. High performance impact, use it with caution!
814
- contextParamsCloning: false,
815
- // Tracking requests and waiting for running requests before shuting down. More info: https://moleculer.services/docs/0.14/context.html#Context-tracking
816
- tracking: trackingConfig,
817
- // Disable built-in request & emit balancer. (Transporter must support it, as well.). More info: https://moleculer.services/docs/0.14/networking.html#Disabled-balancer
818
- disableBalancer: false,
819
- // Settings of Service Registry. More info: https://moleculer.services/docs/0.14/registry.html
820
- registry: registryConfig,
821
- // Settings of Circuit Breaker. More info: https://moleculer.services/docs/0.14/fault-tolerance.html#Circuit-Breaker
822
- circuitBreaker: circuitBreakerConfig,
823
- // Settings of bulkhead feature. More info: https://moleculer.services/docs/0.14/fault-tolerance.html#Bulkhead
824
- bulkhead: bulkheadConfig,
825
- // Enable action & event parameter validation. More info: https://moleculer.services/docs/0.14/validating.html
826
- validator: "Fastest",
827
- // errorHandler: null,
828
- transporter: env3.string("TRANSPORTER_URL"),
829
- // Enable/disable built-in metrics function. More info: https://moleculer.services/docs/0.14/metrics.html
830
- metrics: metricsConfig,
831
- // Enable built-in tracing function. More info: https://moleculer.services/docs/0.14/tracing.html
832
- tracing: tracingConfig,
833
- middlewares: [
834
- ChannelsMiddleware,
835
- PermissionsMiddleware,
836
- ContextHelpersMiddleware
837
- ]
838
- };
839
- var moleculer_default = configs;
840
- var nodeEnv = env3.string("NODE_ENV", "development");
841
- var isDev = nodeEnv === "development";
842
- var isTest = nodeEnv === "test";
843
- var isProd = nodeEnv === "production";
844
- var REDIS_URL = env3.string("REDIS_URL");
845
- var env_default = env3;
846
-
847
- // src/middlewares/permissions.middleware.ts
848
- var permissionHandlers = {
849
- [PERMISSIONS.AUTHENTICATED]: async (ctx) => !!ctx.meta.user?.id,
850
- [PERMISSIONS.TENANT_OWNER]: async (ctx) => ctx.meta.user?.roles.includes(PERMISSIONS.OWNER) ?? false,
851
- [PERMISSIONS.TENANT_MEMBER]: async (ctx) => !!(ctx.meta.user?.tenantId && ctx.meta.tenantId && ctx.meta.user.tenantId === ctx.meta.tenantId)
852
- };
853
- var PermissionsMiddleware = {
854
- // Wrap local action handlers
855
- localAction(handler, action) {
856
- if (!action.permissions) {
857
- return handler;
858
- }
859
- const permissions = Array.isArray(action.permissions) ? action.permissions : [action.permissions];
860
- const permissionNames = [];
861
- const permissionFunctions = [];
862
- permissions.forEach((permission) => {
863
- if (typeof permission === "function") {
864
- permissionFunctions.push(permission);
865
- return;
866
- }
867
- if (typeof permission === "string") {
868
- if (permission in permissionHandlers) {
869
- const handler2 = permissionHandlers[permission];
870
- permissionFunctions.push(handler2);
871
- return;
872
- }
873
- permissionNames.push(permission);
874
- return;
875
- }
876
- });
877
- return async function CheckPermissionsMiddleware(ctx) {
878
- let hasAccess = false;
879
- if (ctx.meta.user?.roles.includes(PERMISSIONS.OWNER)) {
880
- hasAccess = true;
881
- }
882
- if (!hasAccess && permissionFunctions.length > 0) {
883
- const results = await Promise.allSettled(
884
- permissionFunctions.map((fn) => fn(ctx, action))
885
- );
886
- hasAccess = results.some(
887
- (result) => result.status === "fulfilled" && !!result.value
888
- );
889
- const failures = results.filter((r) => r.status === "rejected");
890
- if (failures.length > 0) {
891
- ctx.broker.logger.warn(
892
- `${failures.length} permission functions failed`,
893
- {
894
- action: action.name,
895
- userId: ctx.meta.user?.id
896
- }
897
- );
898
- }
899
- }
900
- if (!hasAccess && permissionNames.length > 0) {
901
- const userRoles = ctx.meta.user?.roles ?? [];
902
- hasAccess = userRoles.some((role) => {
903
- const rolePermissions = ROLE_PERMISSIONS[role] ?? [];
904
- return permissionNames.some(
905
- (permName) => rolePermissions.includes(permName)
906
- );
907
- });
908
- }
909
- if (!hasAccess) {
910
- const user = ctx.meta.user;
911
- ctx.broker.logger.warn("Access denied:", {
912
- action: action.name,
913
- userId: user?.id,
914
- userRoles: user?.roles,
915
- tenantId: ctx.meta.tenantId,
916
- requiredPermissions: permissions
917
- });
918
- const errorDetails = isDev ? {
919
- action: action.name,
920
- requiredPermissions: permissions.map(
921
- (p) => typeof p === "function" ? "[Function]" : String(p)
922
- ),
923
- userRoles: user?.roles ?? [],
924
- userId: user?.id,
925
- tenantId: ctx.meta.tenantId
926
- } : {
927
- action: action.name
928
- };
929
- throw new PermissionError(
930
- "You do not have permission to perform this action",
931
- errorDetails
932
- );
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
- }
765
+ // src/service/define-integration.ts
1023
766
  var SecurityHelpers = {
1024
767
  /**
1025
768
  * Secure comparison using Node.js crypto.timingSafeEqual
@@ -1373,6 +1116,263 @@ var UserRole = {
1373
1116
  VIEWER: "VIEWER"
1374
1117
  };
1375
1118
 
1119
+ // src/configs/moleculer/bulkhead.ts
1120
+ var bulkheadConfig = {
1121
+ // Enable feature.
1122
+ enabled: false,
1123
+ // Maximum concurrent executions.
1124
+ concurrency: 10,
1125
+ // Maximum size of queue
1126
+ maxQueueSize: 100
1127
+ };
1128
+ var NAMESPACE2 = NAMESPACE.toUpperCase();
1129
+ var middleware = Middleware({
1130
+ adapter: {
1131
+ type: "NATS",
1132
+ options: {
1133
+ nats: {
1134
+ url: env4.string("NATS_URL", "nats://localhost:4222"),
1135
+ /** Connection options for reliability */
1136
+ connectionOptions: {
1137
+ name: "hios",
1138
+ timeout: 1e4,
1139
+ reconnect: true,
1140
+ maxReconnectAttempts: 10,
1141
+ reconnectTimeWait: 2e3,
1142
+ maxReconnectTimeWait: 3e4,
1143
+ pingInterval: 2e4,
1144
+ maxPingOut: 2
1145
+ },
1146
+ /**
1147
+ * Stream configuration for multi-tenant messaging
1148
+ *
1149
+ * Environment variables for production:
1150
+ * - NATS_MAX_MESSAGES: Default 100K (dev) -> 10M+ (prod)
1151
+ * - NATS_MAX_BYTES_GB: Default 1GB (dev) -> 100GB+ (prod)
1152
+ * - NATS_MAX_AGE_DAYS: Default 7 (dev) -> 30+ (prod)
1153
+ * - NATS_MAX_MSG_SIZE_MB: Default 1MB (dev) -> 5MB (prod)
1154
+ * - NATS_REPLICAS: Default 1 (dev) -> 3 (prod)
1155
+ */
1156
+ streamConfig: {
1157
+ name: `${NAMESPACE2}_MESSAGES`,
1158
+ subjects: [
1159
+ SUBJECTS.WEBHOOK_ALL,
1160
+ SUBJECTS.PROCESSING_ALL,
1161
+ SUBJECTS.RESPONSE_ALL,
1162
+ SUBJECTS.SYSTEM_ALL,
1163
+ SUBJECTS.DLQ_ALL
1164
+ ],
1165
+ retention: "limits",
1166
+ max_msgs: env4.int("NATS_MAX_MESSAGES", 1e5),
1167
+ // 100K for dev, 10M+ for prod
1168
+ max_bytes: env4.int("NATS_MAX_BYTES_GB", 1) * 1024 * 1024 * 1024,
1169
+ // 1GB for dev, 100GB+ for prod
1170
+ max_age: env4.int("NATS_MAX_AGE_DAYS", 7) * 24 * 60 * 60 * 1e9,
1171
+ // 7 days dev, 30+ days prod
1172
+ max_msg_size: env4.int("NATS_MAX_MSG_SIZE_MB", 1) * 1024 * 1024,
1173
+ // 1MB dev, 5MB prod
1174
+ storage: "file",
1175
+ // Persistent storage
1176
+ num_replicas: env4.int("NATS_REPLICAS", 1),
1177
+ // 1 for dev, 3 for prod
1178
+ discard: "old",
1179
+ // Remove old messages when limits hit
1180
+ duplicate_window: 2 * 60 * 1e9
1181
+ // 2 minutes dedup window
1182
+ },
1183
+ /** Consumer options optimized for LLM processing */
1184
+ consumerOptions: {
1185
+ config: {
1186
+ // Start with new messages (don't replay old ones on restart)
1187
+ deliver_policy: "new",
1188
+ // Explicit acknowledgment required (critical for LLM processing)
1189
+ ack_policy: "explicit",
1190
+ // Allow 5 unacknowledged messages per consumer (rate limiting)
1191
+ max_ack_pending: 5,
1192
+ // Acknowledgment timeout for LLM processing (2 minutes)
1193
+ ack_wait: 120 * 1e9,
1194
+ // 2 minutes in nanoseconds
1195
+ // Maximum delivery attempts before dead letter
1196
+ max_deliver: 3,
1197
+ // Backoff for failed message retries
1198
+ backoff: [
1199
+ 1e9,
1200
+ // 1 second
1201
+ 5e9,
1202
+ // 5 seconds
1203
+ 3e10
1204
+ // 30 seconds
1205
+ ]
1206
+ }
1207
+ }
1208
+ },
1209
+ /** Application-level flow control */
1210
+ maxInFlight: 5,
1211
+ // Limit concurrent LLM requests per service
1212
+ maxRetries: 2,
1213
+ // App-level retries (NATS handles delivery retries)
1214
+ /** Dead letter queue for failed messages */
1215
+ deadLettering: {
1216
+ enabled: true,
1217
+ queueName: "FAILED_MESSAGES"
1218
+ // Send to dead letter after NATS max_deliver attempts
1219
+ }
1220
+ }
1221
+ }
1222
+ });
1223
+ var ChannelsMiddleware = {
1224
+ ...middleware
1225
+ };
1226
+
1227
+ // src/configs/moleculer/circuit-breaker.ts
1228
+ var circuitBreakerConfig = {
1229
+ // Enable feature
1230
+ enabled: false,
1231
+ // Threshold value. 0.5 means that 50% should be failed for tripping.
1232
+ threshold: 0.5,
1233
+ // Minimum request count. Below it, CB does not trip.
1234
+ minRequestCount: 20,
1235
+ // Number of seconds for time window.
1236
+ windowTime: 60,
1237
+ // Number of milliseconds to switch from open to half-open state
1238
+ halfOpenTime: 10 * 1e3,
1239
+ // A function to check failed requests.
1240
+ check: (err) => err.code >= 500
1241
+ };
1242
+
1243
+ // src/configs/moleculer/logger.ts
1244
+ var loggerConfig = {
1245
+ type: "Console",
1246
+ options: {
1247
+ // Using colors on the output
1248
+ colors: true,
1249
+ // Print module names with different colors (like docker-compose for containers)
1250
+ moduleColors: false,
1251
+ // Line formatter. It can be "json", "short", "simple", "full", a `Function` or a template string like "{timestamp} {level} {nodeID}/{mod}: {msg}"
1252
+ formatter: "full",
1253
+ // Custom object printer. If not defined, it uses the `util.inspect` method.
1254
+ objectPrinter: null,
1255
+ // Auto-padding the module name in order to messages begin at the same column.
1256
+ autoPadding: false
1257
+ }
1258
+ };
1259
+ var logger_default = loggerConfig;
1260
+
1261
+ // src/configs/moleculer/metrics.ts
1262
+ var metricsConfig = {
1263
+ enabled: false,
1264
+ // Available built-in reporters: "Console", "CSV", "Event", "Prometheus", "Datadog", "StatsD"
1265
+ reporter: {
1266
+ type: "Prometheus",
1267
+ options: {
1268
+ // HTTP port
1269
+ port: 3030,
1270
+ // HTTP URL path
1271
+ path: "/metrics",
1272
+ // Default labels which are appended to all metrics labels
1273
+ defaultLabels: (registry) => ({
1274
+ namespace: registry.broker.namespace,
1275
+ nodeID: registry.broker.nodeID
1276
+ })
1277
+ }
1278
+ }
1279
+ };
1280
+
1281
+ // src/configs/moleculer/registry.ts
1282
+ var registryConfig = {
1283
+ // Define balancing strategy. More info: https://moleculer.services/docs/0.14/balancing.html
1284
+ // Available values: "RoundRobin", "Random", "CpuUsage", "Latency", "Shard"
1285
+ strategy: "RoundRobin",
1286
+ // Enable local action call preferring. Always call the local action instance if available.
1287
+ preferLocal: true
1288
+ };
1289
+
1290
+ // src/configs/moleculer/retry-policy.ts
1291
+ var retryPolicyConfig = {
1292
+ // Enable feature
1293
+ enabled: false,
1294
+ // Count of retries
1295
+ retries: 5,
1296
+ // First delay in milliseconds.
1297
+ delay: 100,
1298
+ // Maximum delay in milliseconds.
1299
+ maxDelay: 1e3,
1300
+ // Backoff factor for delay. 2 means exponential backoff.
1301
+ factor: 2,
1302
+ // A function to check failed requests.
1303
+ check: (err) => !!err.retryable
1304
+ };
1305
+
1306
+ // src/configs/moleculer/tracing.ts
1307
+ var tracingConfig = {
1308
+ enabled: true,
1309
+ exporter: "Console",
1310
+ events: true,
1311
+ stackTrace: true
1312
+ };
1313
+
1314
+ // src/configs/moleculer/tracking.ts
1315
+ var trackingConfig = {
1316
+ // Enable feature
1317
+ enabled: false,
1318
+ // Number of milliseconds to wait before shuting down the process.
1319
+ shutdownTimeout: 5e3
1320
+ };
1321
+
1322
+ // src/configs/moleculer/index.ts
1323
+ var pkgNm = env4.string("NAMESPACE", "hios");
1324
+ var nodeID = env4.string("NODE_ID") ?? `${pkgNm}-${os.hostname()}-${process.pid}`;
1325
+ var configs = {
1326
+ namespace: pkgNm,
1327
+ nodeID,
1328
+ metadata: {},
1329
+ logger: logger_default,
1330
+ // Default log level for built-in console logger. It can be overwritten in logger options above.
1331
+ // Available values: trace, debug, info, warn, error, fatal
1332
+ logLevel: "info",
1333
+ cacher: env4.string("REDIS_URL", "Memory"),
1334
+ // Define a serializer.
1335
+ // Available values: "JSON", "Avro", "ProtoBuf", "MsgPack", "Notepack", "Thrift".
1336
+ // More info: https://moleculer.services/docs/0.14/networking.html#Serialization
1337
+ serializer: "JSON",
1338
+ // Number of milliseconds to wait before reject a request with a RequestTimeout error. Disabled: 0
1339
+ requestTimeout: 10 * 1e3,
1340
+ // Retry policy settings. More info: https://moleculer.services/docs/0.14/fault-tolerance.html#Retry
1341
+ retryPolicy: retryPolicyConfig,
1342
+ // Limit of calling level. If it reaches the limit, broker will throw an MaxCallLevelError error. (Infinite loop protection)
1343
+ maxCallLevel: 100,
1344
+ // Number of seconds to send heartbeat packet to other nodes.
1345
+ heartbeatInterval: 10,
1346
+ // Number of seconds to wait before setting node to unavailable status.
1347
+ heartbeatTimeout: 30,
1348
+ // Cloning the params of context if enabled. High performance impact, use it with caution!
1349
+ contextParamsCloning: false,
1350
+ // Tracking requests and waiting for running requests before shuting down. More info: https://moleculer.services/docs/0.14/context.html#Context-tracking
1351
+ tracking: trackingConfig,
1352
+ // Disable built-in request & emit balancer. (Transporter must support it, as well.). More info: https://moleculer.services/docs/0.14/networking.html#Disabled-balancer
1353
+ disableBalancer: false,
1354
+ // Settings of Service Registry. More info: https://moleculer.services/docs/0.14/registry.html
1355
+ registry: registryConfig,
1356
+ // Settings of Circuit Breaker. More info: https://moleculer.services/docs/0.14/fault-tolerance.html#Circuit-Breaker
1357
+ circuitBreaker: circuitBreakerConfig,
1358
+ // Settings of bulkhead feature. More info: https://moleculer.services/docs/0.14/fault-tolerance.html#Bulkhead
1359
+ bulkhead: bulkheadConfig,
1360
+ // Enable action & event parameter validation. More info: https://moleculer.services/docs/0.14/validating.html
1361
+ validator: "Fastest",
1362
+ // errorHandler: null,
1363
+ transporter: env4.string("TRANSPORTER_URL"),
1364
+ // Enable/disable built-in metrics function. More info: https://moleculer.services/docs/0.14/metrics.html
1365
+ metrics: metricsConfig,
1366
+ // Enable built-in tracing function. More info: https://moleculer.services/docs/0.14/tracing.html
1367
+ tracing: tracingConfig,
1368
+ middlewares: [
1369
+ ChannelsMiddleware,
1370
+ PermissionsMiddleware,
1371
+ ContextHelpersMiddleware
1372
+ ]
1373
+ };
1374
+ var moleculer_default = configs;
1375
+
1376
1376
  // src/datasources/base.datasource.ts
1377
1377
  var AbstractDatasource = class {
1378
1378
  /**
@@ -1482,7 +1482,9 @@ var PrismaDatasource = class extends AbstractDatasource {
1482
1482
  async disconnect() {
1483
1483
  try {
1484
1484
  this.broker.logger.info("Disconnecting from database");
1485
- await this.client.$disconnect();
1485
+ if (this._client) {
1486
+ await this._client.$disconnect();
1487
+ }
1486
1488
  this.broker.logger.info("Successfully disconnected from database");
1487
1489
  } catch (error) {
1488
1490
  this.broker.logger.error("Error disconnecting from database:", error);
@@ -1549,7 +1551,8 @@ var PrismaDatasource = class extends AbstractDatasource {
1549
1551
  * Requires tenant extension to be applied
1550
1552
  */
1551
1553
  setTenantContext(tenantId) {
1552
- const tenantClient = this.client;
1554
+ this._client ?? (this._client = this.initializePrismaClient());
1555
+ const tenantClient = this._client;
1553
1556
  if (tenantClient.$setTenant) {
1554
1557
  tenantClient.$setTenant(tenantId);
1555
1558
  this.broker.logger.debug("Tenant context set:", { tenantId });
@@ -1563,7 +1566,10 @@ var PrismaDatasource = class extends AbstractDatasource {
1563
1566
  * Get current tenant context
1564
1567
  */
1565
1568
  getCurrentTenant() {
1566
- const tenantClient = this.client;
1569
+ if (!this._client) {
1570
+ return null;
1571
+ }
1572
+ const tenantClient = this._client;
1567
1573
  if (tenantClient.$getCurrentTenant) {
1568
1574
  return tenantClient.$getCurrentTenant();
1569
1575
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hiliosai/sdk",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "main": "./dist/index.js",