@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.
- package/dist/apps/services/app.service.js +14 -5
- package/dist/apps/services/app.service.js.map +1 -1
- package/dist/apps/utils/string.utils.d.ts +7 -0
- package/dist/apps/utils/string.utils.js +33 -1
- package/dist/apps/utils/string.utils.js.map +1 -1
- package/dist/apps/validators/joi-validators/create.appEnv.validator.js +1 -0
- package/dist/apps/validators/joi-validators/create.appEnv.validator.js.map +1 -1
- package/dist/apps/validators/joi-validators/update.appEnv.validator.js +1 -0
- package/dist/apps/validators/joi-validators/update.appEnv.validator.js.map +1 -1
- package/dist/brokers/brokers.service.d.ts +4 -1
- package/dist/brokers/brokers.service.js +104 -20
- package/dist/brokers/brokers.service.js.map +1 -1
- package/dist/database/databases.service.d.ts +15 -0
- package/dist/database/databases.service.js +183 -14
- package/dist/database/databases.service.js.map +1 -1
- package/dist/graph/graphs.service.d.ts +6 -0
- package/dist/graph/graphs.service.js +155 -35
- package/dist/graph/graphs.service.js.map +1 -1
- package/dist/index.d.ts +28 -10
- package/dist/index.js +88 -10
- package/dist/index.js.map +1 -1
- package/dist/processor/services/processor.service.d.ts +15 -2
- package/dist/processor/services/processor.service.js +246 -28
- package/dist/processor/services/processor.service.js.map +1 -1
- package/dist/products/services/products.service.js +23 -24
- package/dist/products/services/products.service.js.map +1 -1
- package/dist/types/appBuilder.types.d.ts +6 -0
- package/dist/vector/vector-database.service.d.ts +6 -0
- package/dist/vector/vector-database.service.js +138 -31
- package/dist/vector/vector-database.service.js.map +1 -1
- package/dist/workflows/workflow-executor.js +99 -44
- package/dist/workflows/workflow-executor.js.map +1 -1
- package/dist/workflows/workflows.service.js +63 -20
- package/dist/workflows/workflows.service.js.map +1 -1
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
-
(
|
|
681
|
-
|
|
682
|
-
|
|
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);
|