@heyhru/business-dms-datasource 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # @heyhru/business-dms-datasource
2
+
3
+ DMS data source domain logic: service, model, sql, and connection pool management.
4
+
5
+ ## Exports
6
+
7
+ ### Route handlers
8
+
9
+ ```ts
10
+ import {
11
+ datasourceList,
12
+ datasourceGet,
13
+ datasourceCreate,
14
+ datasourceDelete,
15
+ } from "@heyhru/business-dms-datasource";
16
+
17
+ // Direct handlers (no encryptionKey needed)
18
+ app.post("/datasources/list", datasourceList);
19
+ app.post("/datasources/get", datasourceGet);
20
+ app.post("/datasources/delete", datasourceDelete);
21
+
22
+ // Curried handlers (encryptionKey required)
23
+ app.post("/datasources/create", datasourceCreate(config.encryptionKey));
24
+ app.post("/datasources/update", datasourceUpdate(config.encryptionKey));
25
+ ```
26
+
27
+ ### Cross-module functions
28
+
29
+ ```ts
30
+ import {
31
+ getDataSourceWithPassword,
32
+ getPool,
33
+ getPoolForDatabase,
34
+ } from "@heyhru/business-dms-datasource";
35
+
36
+ // Decrypt password and return full data source object
37
+ const ds = await getDataSourceWithPassword(dataSourceId, config.encryptionKey);
38
+
39
+ // Get or create a connection pool
40
+ const pool = getPool(ds);
41
+
42
+ // Get pool for a specific database within an instance
43
+ const pool = await getPoolForDatabase(dataSourceId, database, config.encryptionKey);
44
+ ```
45
+
46
+ ## Types
47
+
48
+ ```ts
49
+ import type { DbPool, DataSourceWithPassword } from "@heyhru/business-dms-datasource";
50
+ ```
@@ -0,0 +1,19 @@
1
+ export declare function listDataSources(): Promise<Record<string, unknown>[]>;
2
+ export declare function getDataSourceById(id: string): Promise<Record<string, unknown> | undefined>;
3
+ export declare function getDataSourceRow(id: string): Promise<Record<string, unknown> | undefined>;
4
+ export declare function insertDataSource(data: {
5
+ name: string;
6
+ type: string;
7
+ host: string;
8
+ port: number;
9
+ database: string;
10
+ username: string;
11
+ ssl: boolean;
12
+ pool_min: number;
13
+ pool_max: number;
14
+ }, encryptedPassword: string, createdBy: string): Promise<Record<string, unknown> | undefined>;
15
+ export declare function updateDataSource(id: string, data: Record<string, unknown>, encryptedPassword?: string): Promise<Record<string, unknown> | undefined>;
16
+ export declare function removeDataSource(id: string): Promise<{
17
+ changes: number;
18
+ }>;
19
+ //# sourceMappingURL=datasources.model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datasources.model.d.ts","sourceRoot":"","sources":["../src/datasources.model.ts"],"names":[],"mappings":"AAGA,wBAAgB,eAAe,uCAE9B;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,gDAE3C;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,gDAE1C;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE;IACJ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,OAAO,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,EACD,iBAAiB,EAAE,MAAM,EACzB,SAAS,EAAE,MAAM,gDAelB;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,iBAAiB,CAAC,EAAE,MAAM,gDAoB3B;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM;;GAE1C"}
@@ -0,0 +1,40 @@
1
+ import { type FastifyRequest, type FastifyReply } from "fastify";
2
+ interface AuthenticatedRequest extends FastifyRequest {
3
+ user: {
4
+ id: string;
5
+ };
6
+ }
7
+ import type { DbPool } from "@heyhru/server-plugin-mysql";
8
+ export type { DbPool };
9
+ export declare function getPool(ds: {
10
+ id: string;
11
+ type: "mysql" | "postgres";
12
+ host: string;
13
+ port: number;
14
+ database: string;
15
+ username: string;
16
+ password: string;
17
+ ssl: boolean;
18
+ pool_min: number;
19
+ pool_max: number;
20
+ }): DbPool;
21
+ export declare function getDataSourceWithPassword(id: string, encryptionKey: string): Promise<DataSourceWithPassword | null>;
22
+ export declare function getPoolForDatabase(dataSourceId: string, database: string, encryptionKey: string): Promise<DbPool | null>;
23
+ export interface DataSourceWithPassword {
24
+ id: string;
25
+ type: "mysql" | "postgres";
26
+ host: string;
27
+ port: number;
28
+ database: string | null;
29
+ username: string;
30
+ password: string;
31
+ ssl: boolean;
32
+ pool_min: number;
33
+ pool_max: number;
34
+ }
35
+ export declare function datasourceList(_req: FastifyRequest, reply: FastifyReply): Promise<never>;
36
+ export declare function datasourceGet(req: FastifyRequest, reply: FastifyReply): Promise<never>;
37
+ export declare function datasourceCreate(encryptionKey: string): (req: AuthenticatedRequest, reply: FastifyReply) => Promise<never>;
38
+ export declare function datasourceUpdate(encryptionKey: string): (req: FastifyRequest, reply: FastifyReply) => Promise<never>;
39
+ export declare function datasourceDelete(req: FastifyRequest, reply: FastifyReply): Promise<never>;
40
+ //# sourceMappingURL=datasources.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datasources.service.d.ts","sourceRoot":"","sources":["../src/datasources.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAEjE,UAAU,oBAAqB,SAAQ,cAAc;IACnD,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;CACtB;AAID,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AAW1D,YAAY,EAAE,MAAM,EAAE,CAAC;AAUvB,wBAAgB,OAAO,CAAC,EAAE,EAAE;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,GAAG,UAAU,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,OAAO,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,CA4BT;AAED,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,MAAM,EACV,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAexC;AAED,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAIxB;AAaD,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,GAAG,UAAU,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,OAAO,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,kBAE7E;AAED,wBAAsB,aAAa,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,kBAK3E;AAED,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,IACtC,KAAK,oBAAoB,EAAE,OAAO,YAAY,oBAoB7D;AAED,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,IACtC,KAAK,cAAc,EAAE,OAAO,YAAY,oBAavD;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,kBAK9E"}
@@ -0,0 +1,7 @@
1
+ export declare const LIST = "\nSELECT id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at\nFROM data_sources\nORDER BY created_at DESC";
2
+ export declare const FIND_BY_ID = "\nSELECT id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at\nFROM data_sources\nWHERE id = ?";
3
+ export declare const FIND_WITH_PASSWORD = "\nSELECT *\nFROM data_sources\nWHERE id = ?";
4
+ export declare const CREATE = "\nINSERT INTO data_sources (name, type, host, port, database, username, password_encrypted, ssl, pool_min, pool_max, created_by)\nVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\nRETURNING id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at";
5
+ export declare const UPDATE_FIELDS: string[];
6
+ export declare const DELETE = "\nDELETE FROM data_sources\nWHERE id = ?";
7
+ //# sourceMappingURL=datasources.sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datasources.sql.d.ts","sourceRoot":"","sources":["../src/datasources.sql.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,0JAGQ,CAAC;AAE1B,eAAO,MAAM,UAAU,8IAGV,CAAC;AAEd,eAAO,MAAM,kBAAkB,gDAGlB,CAAC;AAEd,eAAO,MAAM,MAAM,0RAGuF,CAAC;AAE3G,eAAO,MAAM,aAAa,UAA0F,CAAC;AAErH,eAAO,MAAM,MAAM,6CAEN,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { getPool, getDataSourceWithPassword, getPoolForDatabase, datasourceList, datasourceGet, datasourceCreate, datasourceUpdate, datasourceDelete, } from "./datasources.service.js";
2
+ export type { DbPool, DataSourceWithPassword } from "./datasources.service.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,yBAAyB,EACzB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,0BAA0B,CAAC;AAElC,YAAY,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ datasourceCreate: () => datasourceCreate,
24
+ datasourceDelete: () => datasourceDelete,
25
+ datasourceGet: () => datasourceGet,
26
+ datasourceList: () => datasourceList,
27
+ datasourceUpdate: () => datasourceUpdate,
28
+ getDataSourceWithPassword: () => getDataSourceWithPassword,
29
+ getPool: () => getPool,
30
+ getPoolForDatabase: () => getPoolForDatabase
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+
34
+ // src/datasources.service.ts
35
+ var import_server_util_crypto = require("@heyhru/server-util-crypto");
36
+ var import_server_plugin_mysql = require("@heyhru/server-plugin-mysql");
37
+ var import_server_plugin_pg2 = require("@heyhru/server-plugin-pg");
38
+ var import_common_util_logger = require("@heyhru/common-util-logger");
39
+
40
+ // src/datasources.model.ts
41
+ var import_server_plugin_pg = require("@heyhru/server-plugin-pg");
42
+
43
+ // src/datasources.sql.ts
44
+ var LIST = `
45
+ SELECT id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at
46
+ FROM data_sources
47
+ ORDER BY created_at DESC`;
48
+ var FIND_BY_ID = `
49
+ SELECT id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at
50
+ FROM data_sources
51
+ WHERE id = ?`;
52
+ var FIND_WITH_PASSWORD = `
53
+ SELECT *
54
+ FROM data_sources
55
+ WHERE id = ?`;
56
+ var CREATE = `
57
+ INSERT INTO data_sources (name, type, host, port, database, username, password_encrypted, ssl, pool_min, pool_max, created_by)
58
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
59
+ RETURNING id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at`;
60
+ var UPDATE_FIELDS = ["name", "type", "host", "port", "database", "username", "ssl", "pool_min", "pool_max"];
61
+ var DELETE = `
62
+ DELETE FROM data_sources
63
+ WHERE id = ?`;
64
+
65
+ // src/datasources.model.ts
66
+ function listDataSources() {
67
+ return (0, import_server_plugin_pg.getPgDb)().query(LIST);
68
+ }
69
+ function getDataSourceById(id) {
70
+ return (0, import_server_plugin_pg.getPgDb)().queryOne(FIND_BY_ID, [id]);
71
+ }
72
+ function getDataSourceRow(id) {
73
+ return (0, import_server_plugin_pg.getPgDb)().queryOne(FIND_WITH_PASSWORD, [id]);
74
+ }
75
+ function insertDataSource(data, encryptedPassword, createdBy) {
76
+ return (0, import_server_plugin_pg.getPgDb)().queryOne(CREATE, [
77
+ data.name,
78
+ data.type,
79
+ data.host,
80
+ data.port,
81
+ data.database,
82
+ data.username,
83
+ encryptedPassword,
84
+ data.ssl,
85
+ data.pool_min,
86
+ data.pool_max,
87
+ createdBy
88
+ ]);
89
+ }
90
+ async function updateDataSource(id, data, encryptedPassword) {
91
+ const fields = [];
92
+ const values = [];
93
+ for (const key of UPDATE_FIELDS) {
94
+ if (data[key] !== void 0) {
95
+ fields.push(`${key} = ?`);
96
+ values.push(data[key]);
97
+ }
98
+ }
99
+ if (encryptedPassword) {
100
+ fields.push("password_encrypted = ?");
101
+ values.push(encryptedPassword);
102
+ }
103
+ if (!fields.length) return getDataSourceById(id);
104
+ values.push(id);
105
+ return (0, import_server_plugin_pg.getPgDb)().queryOne(
106
+ `UPDATE data_sources SET ${fields.join(", ")} WHERE id = ? RETURNING id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at`,
107
+ values
108
+ );
109
+ }
110
+ function removeDataSource(id) {
111
+ return (0, import_server_plugin_pg.getPgDb)().run(DELETE, [id]);
112
+ }
113
+
114
+ // src/datasources.service.ts
115
+ var logger = (0, import_common_util_logger.createLogger)({ name: "@heyhru/business-dms-datasource" });
116
+ var pools = /* @__PURE__ */ new Map();
117
+ function poolKey(id, database) {
118
+ return database ? `${id}:${database}` : id;
119
+ }
120
+ function getPool(ds) {
121
+ const key = poolKey(ds.id, ds.database);
122
+ if (pools.has(key)) return pools.get(key);
123
+ const pool = ds.type === "mysql" ? (0, import_server_plugin_mysql.createPool)({
124
+ host: ds.host,
125
+ port: ds.port,
126
+ database: ds.database,
127
+ username: ds.username,
128
+ password: ds.password,
129
+ poolMax: ds.pool_max
130
+ }) : (0, import_server_plugin_pg2.createPool)({
131
+ host: ds.host,
132
+ port: ds.port,
133
+ database: ds.database,
134
+ username: ds.username,
135
+ password: ds.password,
136
+ poolMin: ds.pool_min,
137
+ poolMax: ds.pool_max,
138
+ ssl: ds.ssl
139
+ });
140
+ pools.set(key, pool);
141
+ logger.info("Connection pool created (ds=%s, db=%s, type=%s)", ds.id, ds.database, ds.type);
142
+ return pool;
143
+ }
144
+ async function getDataSourceWithPassword(id, encryptionKey) {
145
+ const row = await getDataSourceRow(id);
146
+ if (!row) return null;
147
+ return {
148
+ id: row.id,
149
+ type: row.type,
150
+ host: row.host,
151
+ port: row.port,
152
+ database: row.database ?? null,
153
+ username: row.username,
154
+ password: (0, import_server_util_crypto.decrypt)(row.password_encrypted, encryptionKey),
155
+ ssl: row.ssl,
156
+ pool_min: row.pool_min,
157
+ pool_max: row.pool_max
158
+ };
159
+ }
160
+ async function getPoolForDatabase(dataSourceId, database, encryptionKey) {
161
+ const ds = await getDataSourceWithPassword(dataSourceId, encryptionKey);
162
+ if (!ds) return null;
163
+ return getPool({ ...ds, database });
164
+ }
165
+ async function destroyPool(id) {
166
+ const prefix = `${id}:`;
167
+ for (const [key, pool] of pools) {
168
+ if (key === id || key.startsWith(prefix)) {
169
+ await pool.end();
170
+ pools.delete(key);
171
+ logger.info("Connection pool destroyed (key=%s)", key);
172
+ }
173
+ }
174
+ }
175
+ async function datasourceList(_req, reply) {
176
+ return reply.send(await listDataSources());
177
+ }
178
+ async function datasourceGet(req, reply) {
179
+ const { id } = req.body ?? {};
180
+ const ds = await getDataSourceById(id);
181
+ if (!ds) return reply.code(404).send({ error: "Not found" });
182
+ return reply.send(ds);
183
+ }
184
+ function datasourceCreate(encryptionKey) {
185
+ return async (req, reply) => {
186
+ const body = req.body ?? {};
187
+ const ds = await insertDataSource(
188
+ { ...body, ssl: body.ssl ?? false, pool_min: body.pool_min ?? 1, pool_max: body.pool_max ?? 10 },
189
+ (0, import_server_util_crypto.encrypt)(body.password, encryptionKey),
190
+ req.user.id
191
+ );
192
+ return reply.code(201).send(ds);
193
+ };
194
+ }
195
+ function datasourceUpdate(encryptionKey) {
196
+ return async (req, reply) => {
197
+ const { id, password, ...rest } = req.body ?? {};
198
+ const existing = await getDataSourceById(id);
199
+ if (!existing) return reply.code(404).send({ error: "Not found" });
200
+ const encryptedPassword = password ? (0, import_server_util_crypto.encrypt)(password, encryptionKey) : void 0;
201
+ await destroyPool(id);
202
+ const ds = await updateDataSource(id, rest, encryptedPassword);
203
+ return reply.send(ds);
204
+ };
205
+ }
206
+ async function datasourceDelete(req, reply) {
207
+ const { id } = req.body ?? {};
208
+ await destroyPool(id);
209
+ await removeDataSource(id);
210
+ return reply.code(204).send();
211
+ }
212
+ // Annotate the CommonJS export names for ESM import in node:
213
+ 0 && (module.exports = {
214
+ datasourceCreate,
215
+ datasourceDelete,
216
+ datasourceGet,
217
+ datasourceList,
218
+ datasourceUpdate,
219
+ getDataSourceWithPassword,
220
+ getPool,
221
+ getPoolForDatabase
222
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,188 @@
1
+ // src/datasources.service.ts
2
+ import { encrypt, decrypt } from "@heyhru/server-util-crypto";
3
+ import { createPool as createMysqlPool } from "@heyhru/server-plugin-mysql";
4
+ import { createPool as createPgPool } from "@heyhru/server-plugin-pg";
5
+ import { createLogger } from "@heyhru/common-util-logger";
6
+
7
+ // src/datasources.model.ts
8
+ import { getPgDb } from "@heyhru/server-plugin-pg";
9
+
10
+ // src/datasources.sql.ts
11
+ var LIST = `
12
+ SELECT id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at
13
+ FROM data_sources
14
+ ORDER BY created_at DESC`;
15
+ var FIND_BY_ID = `
16
+ SELECT id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at
17
+ FROM data_sources
18
+ WHERE id = ?`;
19
+ var FIND_WITH_PASSWORD = `
20
+ SELECT *
21
+ FROM data_sources
22
+ WHERE id = ?`;
23
+ var CREATE = `
24
+ INSERT INTO data_sources (name, type, host, port, database, username, password_encrypted, ssl, pool_min, pool_max, created_by)
25
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
26
+ RETURNING id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at`;
27
+ var UPDATE_FIELDS = ["name", "type", "host", "port", "database", "username", "ssl", "pool_min", "pool_max"];
28
+ var DELETE = `
29
+ DELETE FROM data_sources
30
+ WHERE id = ?`;
31
+
32
+ // src/datasources.model.ts
33
+ function listDataSources() {
34
+ return getPgDb().query(LIST);
35
+ }
36
+ function getDataSourceById(id) {
37
+ return getPgDb().queryOne(FIND_BY_ID, [id]);
38
+ }
39
+ function getDataSourceRow(id) {
40
+ return getPgDb().queryOne(FIND_WITH_PASSWORD, [id]);
41
+ }
42
+ function insertDataSource(data, encryptedPassword, createdBy) {
43
+ return getPgDb().queryOne(CREATE, [
44
+ data.name,
45
+ data.type,
46
+ data.host,
47
+ data.port,
48
+ data.database,
49
+ data.username,
50
+ encryptedPassword,
51
+ data.ssl,
52
+ data.pool_min,
53
+ data.pool_max,
54
+ createdBy
55
+ ]);
56
+ }
57
+ async function updateDataSource(id, data, encryptedPassword) {
58
+ const fields = [];
59
+ const values = [];
60
+ for (const key of UPDATE_FIELDS) {
61
+ if (data[key] !== void 0) {
62
+ fields.push(`${key} = ?`);
63
+ values.push(data[key]);
64
+ }
65
+ }
66
+ if (encryptedPassword) {
67
+ fields.push("password_encrypted = ?");
68
+ values.push(encryptedPassword);
69
+ }
70
+ if (!fields.length) return getDataSourceById(id);
71
+ values.push(id);
72
+ return getPgDb().queryOne(
73
+ `UPDATE data_sources SET ${fields.join(", ")} WHERE id = ? RETURNING id, name, type, host, port, database, username, ssl, pool_min, pool_max, created_by, created_at`,
74
+ values
75
+ );
76
+ }
77
+ function removeDataSource(id) {
78
+ return getPgDb().run(DELETE, [id]);
79
+ }
80
+
81
+ // src/datasources.service.ts
82
+ var logger = createLogger({ name: "@heyhru/business-dms-datasource" });
83
+ var pools = /* @__PURE__ */ new Map();
84
+ function poolKey(id, database) {
85
+ return database ? `${id}:${database}` : id;
86
+ }
87
+ function getPool(ds) {
88
+ const key = poolKey(ds.id, ds.database);
89
+ if (pools.has(key)) return pools.get(key);
90
+ const pool = ds.type === "mysql" ? createMysqlPool({
91
+ host: ds.host,
92
+ port: ds.port,
93
+ database: ds.database,
94
+ username: ds.username,
95
+ password: ds.password,
96
+ poolMax: ds.pool_max
97
+ }) : createPgPool({
98
+ host: ds.host,
99
+ port: ds.port,
100
+ database: ds.database,
101
+ username: ds.username,
102
+ password: ds.password,
103
+ poolMin: ds.pool_min,
104
+ poolMax: ds.pool_max,
105
+ ssl: ds.ssl
106
+ });
107
+ pools.set(key, pool);
108
+ logger.info("Connection pool created (ds=%s, db=%s, type=%s)", ds.id, ds.database, ds.type);
109
+ return pool;
110
+ }
111
+ async function getDataSourceWithPassword(id, encryptionKey) {
112
+ const row = await getDataSourceRow(id);
113
+ if (!row) return null;
114
+ return {
115
+ id: row.id,
116
+ type: row.type,
117
+ host: row.host,
118
+ port: row.port,
119
+ database: row.database ?? null,
120
+ username: row.username,
121
+ password: decrypt(row.password_encrypted, encryptionKey),
122
+ ssl: row.ssl,
123
+ pool_min: row.pool_min,
124
+ pool_max: row.pool_max
125
+ };
126
+ }
127
+ async function getPoolForDatabase(dataSourceId, database, encryptionKey) {
128
+ const ds = await getDataSourceWithPassword(dataSourceId, encryptionKey);
129
+ if (!ds) return null;
130
+ return getPool({ ...ds, database });
131
+ }
132
+ async function destroyPool(id) {
133
+ const prefix = `${id}:`;
134
+ for (const [key, pool] of pools) {
135
+ if (key === id || key.startsWith(prefix)) {
136
+ await pool.end();
137
+ pools.delete(key);
138
+ logger.info("Connection pool destroyed (key=%s)", key);
139
+ }
140
+ }
141
+ }
142
+ async function datasourceList(_req, reply) {
143
+ return reply.send(await listDataSources());
144
+ }
145
+ async function datasourceGet(req, reply) {
146
+ const { id } = req.body ?? {};
147
+ const ds = await getDataSourceById(id);
148
+ if (!ds) return reply.code(404).send({ error: "Not found" });
149
+ return reply.send(ds);
150
+ }
151
+ function datasourceCreate(encryptionKey) {
152
+ return async (req, reply) => {
153
+ const body = req.body ?? {};
154
+ const ds = await insertDataSource(
155
+ { ...body, ssl: body.ssl ?? false, pool_min: body.pool_min ?? 1, pool_max: body.pool_max ?? 10 },
156
+ encrypt(body.password, encryptionKey),
157
+ req.user.id
158
+ );
159
+ return reply.code(201).send(ds);
160
+ };
161
+ }
162
+ function datasourceUpdate(encryptionKey) {
163
+ return async (req, reply) => {
164
+ const { id, password, ...rest } = req.body ?? {};
165
+ const existing = await getDataSourceById(id);
166
+ if (!existing) return reply.code(404).send({ error: "Not found" });
167
+ const encryptedPassword = password ? encrypt(password, encryptionKey) : void 0;
168
+ await destroyPool(id);
169
+ const ds = await updateDataSource(id, rest, encryptedPassword);
170
+ return reply.send(ds);
171
+ };
172
+ }
173
+ async function datasourceDelete(req, reply) {
174
+ const { id } = req.body ?? {};
175
+ await destroyPool(id);
176
+ await removeDataSource(id);
177
+ return reply.code(204).send();
178
+ }
179
+ export {
180
+ datasourceCreate,
181
+ datasourceDelete,
182
+ datasourceGet,
183
+ datasourceList,
184
+ datasourceUpdate,
185
+ getDataSourceWithPassword,
186
+ getPool,
187
+ getPoolForDatabase
188
+ };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@heyhru/business-dms-datasource",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.4.0",
7
+ "description": "DMS data source domain logic: service, model, sql, pool management",
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.mjs",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup && tsc --emitDeclarationOnly",
23
+ "dev": "tsup --watch",
24
+ "lint": "eslint src",
25
+ "test": "vitest run",
26
+ "clean": "rm -rf dist"
27
+ },
28
+ "dependencies": {
29
+ "@heyhru/common-util-logger": "0.4.0",
30
+ "@heyhru/server-plugin-mysql": "0.4.0",
31
+ "@heyhru/server-plugin-pg": "0.4.0",
32
+ "@heyhru/server-util-crypto": "0.4.0",
33
+ "fastify": "^5.8.4"
34
+ },
35
+ "devDependencies": {
36
+ "tsup": "^8.5.1",
37
+ "typescript": "^6.0.2",
38
+ "vitest": "^4.1.2"
39
+ },
40
+ "gitHead": "48f00e1f0b1ca5d0789c0df9384a12f2c8e847c4"
41
+ }