@ductape/sdk 0.0.6 → 0.0.7

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 (35) hide show
  1. package/dist/apps/services/app.service.js +14 -5
  2. package/dist/apps/services/app.service.js.map +1 -1
  3. package/dist/apps/utils/string.utils.d.ts +7 -0
  4. package/dist/apps/utils/string.utils.js +33 -1
  5. package/dist/apps/utils/string.utils.js.map +1 -1
  6. package/dist/apps/validators/joi-validators/create.appEnv.validator.js +1 -0
  7. package/dist/apps/validators/joi-validators/create.appEnv.validator.js.map +1 -1
  8. package/dist/apps/validators/joi-validators/update.appEnv.validator.js +1 -0
  9. package/dist/apps/validators/joi-validators/update.appEnv.validator.js.map +1 -1
  10. package/dist/brokers/brokers.service.d.ts +4 -1
  11. package/dist/brokers/brokers.service.js +104 -20
  12. package/dist/brokers/brokers.service.js.map +1 -1
  13. package/dist/database/databases.service.d.ts +15 -0
  14. package/dist/database/databases.service.js +183 -14
  15. package/dist/database/databases.service.js.map +1 -1
  16. package/dist/graph/graphs.service.d.ts +6 -0
  17. package/dist/graph/graphs.service.js +155 -35
  18. package/dist/graph/graphs.service.js.map +1 -1
  19. package/dist/index.d.ts +28 -10
  20. package/dist/index.js +88 -10
  21. package/dist/index.js.map +1 -1
  22. package/dist/processor/services/processor.service.d.ts +15 -2
  23. package/dist/processor/services/processor.service.js +246 -28
  24. package/dist/processor/services/processor.service.js.map +1 -1
  25. package/dist/products/services/products.service.js +23 -24
  26. package/dist/products/services/products.service.js.map +1 -1
  27. package/dist/types/appBuilder.types.d.ts +6 -0
  28. package/dist/vector/vector-database.service.d.ts +6 -0
  29. package/dist/vector/vector-database.service.js +138 -31
  30. package/dist/vector/vector-database.service.js.map +1 -1
  31. package/dist/workflows/workflow-executor.js +99 -44
  32. package/dist/workflows/workflow-executor.js.map +1 -1
  33. package/dist/workflows/workflows.service.js +63 -20
  34. package/dist/workflows/workflows.service.js.map +1 -1
  35. package/package.json +1 -1
@@ -67,6 +67,17 @@ const logs_service_1 = __importDefault(require("../logs/logs.service"));
67
67
  const logs_types_1 = require("../logs/logs.types");
68
68
  const processor_utils_1 = require("../processor/utils/processor.utils");
69
69
  const secrets_1 = require("../secrets");
70
+ /** Module-level: last time we logged connect initiated/success/failed per key, to avoid duplicate logs across multiple DatabaseService instances */
71
+ const lastConnectLogAt = new Map();
72
+ const CONNECT_LOG_DEBOUNCE_MS = 15000;
73
+ function getConnectLogKey(product, database, env) {
74
+ return `${product !== null && product !== void 0 ? product : ''}:${database}:${env}`;
75
+ }
76
+ const sharedConnectionRegistry = new Map();
77
+ const sharedConnectInFlight = new Map();
78
+ function getSharedConnectionKey(workspaceId, product, database, env) {
79
+ return `${workspaceId}:${product}:${database}:${env}`;
80
+ }
70
81
  /**
71
82
  * Main Database Service class
72
83
  * Provides unified ORM interface for all supported databases
@@ -96,6 +107,8 @@ class DatabaseService {
96
107
  this.privateKeys = new Map();
97
108
  /** Local cache for cache configurations to avoid repeated API calls (5 minute TTL) */
98
109
  this.cacheConfigCache = new Map();
110
+ /** In-flight connect() promises per contextKey to deduplicate concurrent connects and avoid duplicate logs */
111
+ this.connectPromises = new Map();
99
112
  this.config = config || null;
100
113
  this.adapterFactory = new adapter_factory_1.AdapterFactory();
101
114
  this.transactionManager = new transaction_manager_1.TransactionManager();
@@ -597,25 +610,54 @@ class DatabaseService {
597
610
  * await db.triggers.create({ tag: 'my-trigger', ... });
598
611
  */
599
612
  async connect(config) {
600
- var _a, _b, _c, _d, _e, _f;
613
+ var _a, _b, _c;
601
614
  const process_id = (0, processor_utils_1.generateObjectId)();
602
615
  const start = Date.now();
603
616
  const contextKey = this.buildContextKey(config.database, config.env);
617
+ const workspaceId = (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.workspace_id) !== null && _b !== void 0 ? _b : '';
618
+ const product = (_c = config.product) !== null && _c !== void 0 ? _c : '';
619
+ // Use shared registry only when we have an authenticated scope (workspace + product).
620
+ // Connections are never shared across workspaces or products.
621
+ const useShared = workspaceId !== '' && product !== '';
622
+ if (useShared) {
623
+ const sharedKey = getSharedConnectionKey(workspaceId, product, config.database, config.env);
624
+ const existing = sharedConnectionRegistry.get(sharedKey);
625
+ if (existing === null || existing === void 0 ? void 0 : existing.context.connected) {
626
+ this.adapters.set(contextKey, existing.adapter);
627
+ this.connectionContexts.set(contextKey, existing.context);
628
+ return new DatabaseConnection(this, config.database, config.env, config.product || '');
629
+ }
630
+ const inFlight = sharedConnectInFlight.get(sharedKey);
631
+ if (inFlight) {
632
+ const entry = await inFlight;
633
+ this.adapters.set(contextKey, entry.adapter);
634
+ this.connectionContexts.set(contextKey, entry.context);
635
+ return new DatabaseConnection(this, config.database, config.env, config.product || '');
636
+ }
637
+ const createPromise = this.connectAndRegisterShared(sharedKey, config, contextKey, process_id, start);
638
+ sharedConnectInFlight.set(sharedKey, createPromise);
639
+ try {
640
+ const entry = await createPromise;
641
+ this.adapters.set(contextKey, entry.adapter);
642
+ this.connectionContexts.set(contextKey, entry.context);
643
+ return new DatabaseConnection(this, config.database, config.env, config.product || '');
644
+ }
645
+ finally {
646
+ sharedConnectInFlight.delete(sharedKey);
647
+ }
648
+ }
649
+ // Per-instance path when shared registry is not used (no workspace/product scope)
604
650
  let adapter = this.adapters.get(contextKey);
605
651
  let context = this.connectionContexts.get(contextKey);
606
- // If not found locally, fetch from the remote API
607
652
  if (!adapter || !context) {
608
653
  if (!config.product) {
609
654
  throw new database_error_1.DatabaseError(`Database '${config.database}' not found locally. Please provide 'product' to fetch from remote.`, enums_1.DatabaseErrorType.NOT_FOUND);
610
655
  }
611
- // Initialize product builder
612
656
  await this.getProductBuilder(config.product);
613
- // Fetch the database config from the API (already decrypted)
614
657
  const dbConfig = await this.fetchDatabase(config.product, config.database);
615
658
  if (!dbConfig) {
616
659
  throw new database_error_1.DatabaseError(`Database '${config.database}' not found for product '${config.product}'.`, enums_1.DatabaseErrorType.NOT_FOUND);
617
660
  }
618
- // Register the database locally (config is already decrypted from API)
619
661
  this.createAdapter({
620
662
  tag: config.database,
621
663
  name: dbConfig.name,
@@ -633,7 +675,109 @@ class DatabaseService {
633
675
  if (!adapter || !context) {
634
676
  throw new database_error_1.DatabaseError(`Failed to initialize adapter for ${contextKey}`, enums_1.DatabaseErrorType.CONNECTION_ERROR);
635
677
  }
636
- // Initialize logging
678
+ if (context.connected) {
679
+ return new DatabaseConnection(this, config.database, config.env, config.product || '');
680
+ }
681
+ const inFlight = this.connectPromises.get(contextKey);
682
+ if (inFlight) {
683
+ return inFlight;
684
+ }
685
+ const connectPromise = this.runConnect(config, contextKey, adapter, context, process_id, start);
686
+ this.connectPromises.set(contextKey, connectPromise);
687
+ try {
688
+ return await connectPromise;
689
+ }
690
+ finally {
691
+ this.connectPromises.delete(contextKey);
692
+ }
693
+ }
694
+ /**
695
+ * Disconnect any existing connection to this resource from the SDK (shared registry and this instance) before creating a fresh one.
696
+ */
697
+ async disconnectExistingForResource(sharedKey, contextKey, workspaceId, product, database, env) {
698
+ const existingInRegistry = sharedConnectionRegistry.get(sharedKey);
699
+ if (existingInRegistry) {
700
+ try {
701
+ await existingInRegistry.adapter.disconnect();
702
+ existingInRegistry.context.connected = false;
703
+ }
704
+ catch (_a) {
705
+ // Non-fatal
706
+ }
707
+ sharedConnectionRegistry.delete(sharedKey);
708
+ }
709
+ const context = this.connectionContexts.get(contextKey);
710
+ if (context === null || context === void 0 ? void 0 : context.connected) {
711
+ const adapter = this.adapters.get(contextKey);
712
+ if (adapter) {
713
+ try {
714
+ await adapter.disconnect();
715
+ }
716
+ catch (_b) {
717
+ // Non-fatal
718
+ }
719
+ context.connected = false;
720
+ if (workspaceId && product) {
721
+ sharedConnectionRegistry.delete(getSharedConnectionKey(workspaceId, product, database, env));
722
+ }
723
+ }
724
+ }
725
+ }
726
+ /**
727
+ * Get or create adapter/context, connect, and register in shared registry.
728
+ * Only used when connect() is scoped with workspace_id and product (no cross-tenant sharing).
729
+ */
730
+ async connectAndRegisterShared(sharedKey, config, contextKey, process_id, start) {
731
+ var _a, _b, _c;
732
+ const workspaceId = (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.workspace_id) !== null && _b !== void 0 ? _b : '';
733
+ const product = (_c = config.product) !== null && _c !== void 0 ? _c : '';
734
+ await this.disconnectExistingForResource(sharedKey, contextKey, workspaceId, product, config.database, config.env);
735
+ let adapter = this.adapters.get(contextKey);
736
+ let context = this.connectionContexts.get(contextKey);
737
+ if (!adapter || !context) {
738
+ if (!config.product) {
739
+ throw new database_error_1.DatabaseError(`Database '${config.database}' not found locally. Please provide 'product' to fetch from remote.`, enums_1.DatabaseErrorType.NOT_FOUND);
740
+ }
741
+ await this.getProductBuilder(config.product);
742
+ const dbConfig = await this.fetchDatabase(config.product, config.database);
743
+ if (!dbConfig) {
744
+ throw new database_error_1.DatabaseError(`Database '${config.database}' not found for product '${config.product}'.`, enums_1.DatabaseErrorType.NOT_FOUND);
745
+ }
746
+ this.createAdapter({
747
+ tag: config.database,
748
+ name: dbConfig.name,
749
+ type: dbConfig.type,
750
+ description: dbConfig.description,
751
+ envs: dbConfig.envs.map(e => ({
752
+ slug: e.slug,
753
+ connection_url: e.connection_url,
754
+ description: e.description,
755
+ })),
756
+ });
757
+ adapter = this.adapters.get(contextKey);
758
+ context = this.connectionContexts.get(contextKey);
759
+ }
760
+ await this.runConnect(config, contextKey, adapter, context, process_id, start);
761
+ const entry = { adapter, context };
762
+ const raceExisting = sharedConnectionRegistry.get(sharedKey);
763
+ if (raceExisting) {
764
+ try {
765
+ await raceExisting.adapter.disconnect();
766
+ raceExisting.context.connected = false;
767
+ }
768
+ catch (_d) {
769
+ // Non-fatal
770
+ }
771
+ sharedConnectionRegistry.delete(sharedKey);
772
+ }
773
+ sharedConnectionRegistry.set(sharedKey, entry);
774
+ return entry;
775
+ }
776
+ /**
777
+ * Performs the actual connect (resolve URL, adapter.connect(), log). Called once per contextKey when not already connected.
778
+ */
779
+ async runConnect(config, contextKey, adapter, context, process_id, start) {
780
+ var _a, _b, _c, _d;
637
781
  this.initializeLogService();
638
782
  const envValue = config.env || '';
639
783
  const baseLogs = {
@@ -647,7 +791,13 @@ class DatabaseService {
647
791
  child_tag: 'connect',
648
792
  data: { database: config.database, env: envValue, operation: 'connect' },
649
793
  };
650
- (_b = this.logService) === null || _b === void 0 ? void 0 : _b.add(Object.assign(Object.assign({}, baseLogs), { start, message: 'Database connect - initiated', status: logs_types_1.LogEventStatus.PROCESSING }));
794
+ const logKey = getConnectLogKey(config.product, config.database, config.env);
795
+ const now = Date.now();
796
+ const last = (_b = lastConnectLogAt.get(logKey)) !== null && _b !== void 0 ? _b : {};
797
+ if (this.logService && (!last.initiated || now - last.initiated >= CONNECT_LOG_DEBOUNCE_MS)) {
798
+ lastConnectLogAt.set(logKey, Object.assign(Object.assign({}, last), { initiated: now }));
799
+ this.logService.add(Object.assign(Object.assign({}, baseLogs), { start, message: 'Database connect - initiated', status: logs_types_1.LogEventStatus.PROCESSING }));
800
+ }
651
801
  try {
652
802
  // Resolve secret reference in connection URL if needed
653
803
  let connectionUrl = context.connectionUrl;
@@ -669,17 +819,24 @@ class DatabaseService {
669
819
  context.product = config.product;
670
820
  this.currentContext = context;
671
821
  const end = Date.now();
672
- (_c = this.logService) === null || _c === void 0 ? void 0 : _c.add(Object.assign(Object.assign({}, baseLogs), { start,
673
- end, message: 'Database connect - success', successful_execution: true, status: logs_types_1.LogEventStatus.SUCCESS }));
674
- (_d = this.logService) === null || _d === void 0 ? void 0 : _d.publish();
675
- // Return a DatabaseConnection object for scoped operations
822
+ const lastSuccess = (_c = lastConnectLogAt.get(logKey)) !== null && _c !== void 0 ? _c : {};
823
+ if (this.logService && (!lastSuccess.success || end - lastSuccess.success >= CONNECT_LOG_DEBOUNCE_MS)) {
824
+ lastConnectLogAt.set(logKey, Object.assign(Object.assign({}, lastSuccess), { success: end }));
825
+ this.logService.add(Object.assign(Object.assign({}, baseLogs), { start,
826
+ end, message: 'Database connect - success', successful_execution: true, status: logs_types_1.LogEventStatus.SUCCESS }));
827
+ this.logService.publish();
828
+ }
676
829
  return new DatabaseConnection(this, config.database, config.env, config.product || '');
677
830
  }
678
831
  catch (error) {
679
832
  const end = Date.now();
680
- (_e = this.logService) === null || _e === void 0 ? void 0 : _e.add(Object.assign(Object.assign({}, baseLogs), { start,
681
- end, message: 'Database connect - failed', failed_execution: true, data: { database: config.database, env: config.env, error: String(error) }, status: logs_types_1.LogEventStatus.FAIL }));
682
- await ((_f = this.logService) === null || _f === void 0 ? void 0 : _f.publish());
833
+ const lastFail = (_d = lastConnectLogAt.get(logKey)) !== null && _d !== void 0 ? _d : {};
834
+ if (this.logService && (!lastFail.failed || end - lastFail.failed >= CONNECT_LOG_DEBOUNCE_MS)) {
835
+ lastConnectLogAt.set(logKey, Object.assign(Object.assign({}, lastFail), { failed: end }));
836
+ this.logService.add(Object.assign(Object.assign({}, baseLogs), { start,
837
+ end, message: 'Database connect - failed', failed_execution: true, data: { database: config.database, env: config.env, error: String(error) }, status: logs_types_1.LogEventStatus.FAIL }));
838
+ await this.logService.publish();
839
+ }
683
840
  throw new database_error_1.DatabaseError(`Failed to connect to database: ${error.message}`, enums_1.DatabaseErrorType.CONNECTION_ERROR, error);
684
841
  }
685
842
  }
@@ -731,12 +888,18 @@ class DatabaseService {
731
888
  * Disconnect from the current database
732
889
  */
733
890
  async disconnect() {
891
+ var _a, _b, _c;
734
892
  if (this.currentContext) {
735
893
  const contextKey = this.buildContextKey(this.currentContext.database, this.currentContext.env);
736
894
  const adapter = this.adapters.get(contextKey);
737
895
  if (adapter) {
738
896
  await adapter.disconnect();
739
897
  this.currentContext.connected = false;
898
+ const workspaceId = (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.workspace_id) !== null && _b !== void 0 ? _b : '';
899
+ const product = (_c = this.currentContext.product) !== null && _c !== void 0 ? _c : '';
900
+ if (workspaceId && product) {
901
+ sharedConnectionRegistry.delete(getSharedConnectionKey(workspaceId, product, this.currentContext.database, this.currentContext.env));
902
+ }
740
903
  }
741
904
  this.currentContext = null;
742
905
  }
@@ -748,12 +911,18 @@ class DatabaseService {
748
911
  * await ductape.database.closeAll();
749
912
  */
750
913
  async closeAll() {
914
+ var _a, _b, _c;
751
915
  const disconnectPromises = [];
916
+ const workspaceId = (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.workspace_id) !== null && _b !== void 0 ? _b : '';
752
917
  for (const [key, adapter] of this.adapters) {
753
918
  disconnectPromises.push(adapter.disconnect());
754
919
  const context = this.connectionContexts.get(key);
755
920
  if (context) {
756
921
  context.connected = false;
922
+ const product = (_c = context.product) !== null && _c !== void 0 ? _c : '';
923
+ if (workspaceId && product) {
924
+ sharedConnectionRegistry.delete(getSharedConnectionKey(workspaceId, product, context.database, context.env));
925
+ }
757
926
  }
758
927
  }
759
928
  await Promise.all(disconnectPromises);