@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 +50 -0
- package/dist/datasources.model.d.ts +19 -0
- package/dist/datasources.model.d.ts.map +1 -0
- package/dist/datasources.service.d.ts +40 -0
- package/dist/datasources.service.d.ts.map +1 -0
- package/dist/datasources.sql.d.ts +7 -0
- package/dist/datasources.sql.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +222 -0
- package/dist/index.mjs +188 -0
- package/package.json +41 -0
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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|