@xemahq/biome-database-sdk 0.1.1
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/LICENSE +201 -0
- package/README.md +65 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/migration-discovery.d.ts +3 -0
- package/dist/lib/migration-discovery.d.ts.map +1 -0
- package/dist/lib/migration-discovery.js +93 -0
- package/dist/lib/migration-discovery.js.map +1 -0
- package/dist/lib/migration-runner.d.ts +3 -0
- package/dist/lib/migration-runner.d.ts.map +1 -0
- package/dist/lib/migration-runner.js +107 -0
- package/dist/lib/migration-runner.js.map +1 -0
- package/dist/lib/org-managed-db.module.d.ts +29 -0
- package/dist/lib/org-managed-db.module.d.ts.map +1 -0
- package/dist/lib/org-managed-db.module.js +161 -0
- package/dist/lib/org-managed-db.module.js.map +1 -0
- package/dist/lib/prisma-factory.d.ts +13 -0
- package/dist/lib/prisma-factory.d.ts.map +1 -0
- package/dist/lib/prisma-factory.js +57 -0
- package/dist/lib/prisma-factory.js.map +1 -0
- package/dist/lib/types.d.ts +69 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +3 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/url-builder.d.ts +3 -0
- package/dist/lib/url-builder.d.ts.map +1 -0
- package/dist/lib/url-builder.js +51 -0
- package/dist/lib/url-builder.js.map +1 -0
- package/package.json +55 -0
- package/src/index.ts +51 -0
- package/src/lib/migration-discovery.ts +134 -0
- package/src/lib/migration-runner.ts +177 -0
- package/src/lib/org-managed-db.module.ts +187 -0
- package/src/lib/prisma-factory.ts +101 -0
- package/src/lib/types.ts +113 -0
- package/src/lib/url-builder.ts +96 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var OrgManagedDatabaseModule_1;
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.OrgManagedDatabaseModule = exports.OrgManagedDatabaseService = exports.ORG_MANAGED_DATABASE_OPTIONS = void 0;
|
|
17
|
+
exports.hasOrgDatabaseConfig = hasOrgDatabaseConfig;
|
|
18
|
+
const common_1 = require("@nestjs/common");
|
|
19
|
+
exports.ORG_MANAGED_DATABASE_OPTIONS = 'ORG_MANAGED_DATABASE_OPTIONS';
|
|
20
|
+
const logger = new common_1.Logger('OrgManagedDatabaseModule');
|
|
21
|
+
let OrgManagedDatabaseService = class OrgManagedDatabaseService {
|
|
22
|
+
options;
|
|
23
|
+
refreshInterval;
|
|
24
|
+
currentGrant;
|
|
25
|
+
constructor(options) {
|
|
26
|
+
this.options = options;
|
|
27
|
+
}
|
|
28
|
+
async onModuleInit() {
|
|
29
|
+
if (!this.options.databaseId) {
|
|
30
|
+
logger.warn('databaseId not provided; org-managed database integration disabled');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
await this.refreshConnection();
|
|
34
|
+
const refreshInterval = this.options.refreshConnInterval || 30_000;
|
|
35
|
+
this.refreshInterval = setInterval(() => {
|
|
36
|
+
this.refreshConnection().catch((err) => {
|
|
37
|
+
logger.error('Failed to refresh org-managed database connection', err);
|
|
38
|
+
});
|
|
39
|
+
}, refreshInterval);
|
|
40
|
+
}
|
|
41
|
+
async onModuleDestroy() {
|
|
42
|
+
if (this.refreshInterval) {
|
|
43
|
+
clearInterval(this.refreshInterval);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async getConnectionGrant() {
|
|
47
|
+
if (!this.currentGrant) {
|
|
48
|
+
await this.refreshConnection();
|
|
49
|
+
}
|
|
50
|
+
const now = new Date();
|
|
51
|
+
const timeToExpiry = this.currentGrant.expiresAt.getTime() - now.getTime();
|
|
52
|
+
if (timeToExpiry < 5000) {
|
|
53
|
+
await this.refreshConnection();
|
|
54
|
+
}
|
|
55
|
+
return this.currentGrant;
|
|
56
|
+
}
|
|
57
|
+
async getDatabaseUrl() {
|
|
58
|
+
const grant = await this.getConnectionGrant();
|
|
59
|
+
return grant.databaseUrl;
|
|
60
|
+
}
|
|
61
|
+
async refreshConnection() {
|
|
62
|
+
if (!this.options.databaseId) {
|
|
63
|
+
throw new Error('databaseId is required for org-managed database');
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const database = await this.fetchJson(`${this.options.poolApiUrl}/databases/${this.options.databaseId}`);
|
|
67
|
+
const roleName = this.rolesForSchema(database.schemaName).rwRoleName;
|
|
68
|
+
const grant = await this.fetchJson(`${this.options.poolApiUrl}/connections/mint`, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: {
|
|
71
|
+
'Content-Type': 'application/json',
|
|
72
|
+
'X-Biome-Id': this.options.biomeId || 'unknown',
|
|
73
|
+
},
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
databaseId: this.options.databaseId,
|
|
76
|
+
schemaName: database.schemaName,
|
|
77
|
+
roleName,
|
|
78
|
+
ttlSeconds: 600,
|
|
79
|
+
}),
|
|
80
|
+
});
|
|
81
|
+
this.currentGrant = {
|
|
82
|
+
databaseUrl: this.buildPostgresUrl({
|
|
83
|
+
host: grant.host,
|
|
84
|
+
port: grant.port,
|
|
85
|
+
database: grant.database,
|
|
86
|
+
user: grant.user,
|
|
87
|
+
password: grant.password,
|
|
88
|
+
schema: grant.schema,
|
|
89
|
+
}),
|
|
90
|
+
host: grant.host,
|
|
91
|
+
port: grant.port,
|
|
92
|
+
database: grant.database,
|
|
93
|
+
schema: grant.schema,
|
|
94
|
+
user: grant.user,
|
|
95
|
+
password: grant.password,
|
|
96
|
+
expiresAt: new Date(grant.expiresAt),
|
|
97
|
+
databaseId: this.options.databaseId,
|
|
98
|
+
schemaName: database.schemaName,
|
|
99
|
+
roleName,
|
|
100
|
+
};
|
|
101
|
+
logger.log(`Connection grant refreshed for database ${this.options.databaseId}; expires at ${grant.expiresAt}`);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
logger.error(`Failed to refresh org-managed database connection: ${err instanceof Error ? err.message : String(err)}`);
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
rolesForSchema(schemaName) {
|
|
109
|
+
const base = schemaName.toLowerCase().replace(/[^a-z0-9_]/g, '_').slice(0, 54);
|
|
110
|
+
return {
|
|
111
|
+
rwRoleName: `${base}_rw`,
|
|
112
|
+
roRoleName: `${base}_ro`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
buildPostgresUrl(input) {
|
|
116
|
+
const user = encodeURIComponent(input.user);
|
|
117
|
+
const password = encodeURIComponent(input.password);
|
|
118
|
+
const schema = encodeURIComponent(input.schema);
|
|
119
|
+
return `postgresql://${user}:${password}@${input.host}:${input.port}/${input.database}?schema=${schema}`;
|
|
120
|
+
}
|
|
121
|
+
async fetchJson(url, init) {
|
|
122
|
+
const response = await fetch(url, init);
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
throw new Error(`Request failed: ${response.status} ${response.statusText} (${url})`);
|
|
125
|
+
}
|
|
126
|
+
return (await response.json());
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
exports.OrgManagedDatabaseService = OrgManagedDatabaseService;
|
|
130
|
+
exports.OrgManagedDatabaseService = OrgManagedDatabaseService = __decorate([
|
|
131
|
+
(0, common_1.Injectable)(),
|
|
132
|
+
__param(0, (0, common_1.Inject)(exports.ORG_MANAGED_DATABASE_OPTIONS)),
|
|
133
|
+
__metadata("design:paramtypes", [Object])
|
|
134
|
+
], OrgManagedDatabaseService);
|
|
135
|
+
let OrgManagedDatabaseModule = OrgManagedDatabaseModule_1 = class OrgManagedDatabaseModule {
|
|
136
|
+
static register(options) {
|
|
137
|
+
return {
|
|
138
|
+
module: OrgManagedDatabaseModule_1,
|
|
139
|
+
providers: [
|
|
140
|
+
{
|
|
141
|
+
provide: exports.ORG_MANAGED_DATABASE_OPTIONS,
|
|
142
|
+
useValue: options,
|
|
143
|
+
},
|
|
144
|
+
OrgManagedDatabaseService,
|
|
145
|
+
],
|
|
146
|
+
exports: [OrgManagedDatabaseService],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
exports.OrgManagedDatabaseModule = OrgManagedDatabaseModule;
|
|
151
|
+
exports.OrgManagedDatabaseModule = OrgManagedDatabaseModule = OrgManagedDatabaseModule_1 = __decorate([
|
|
152
|
+
(0, common_1.Global)(),
|
|
153
|
+
(0, common_1.Module)({
|
|
154
|
+
providers: [OrgManagedDatabaseService],
|
|
155
|
+
exports: [OrgManagedDatabaseService],
|
|
156
|
+
})
|
|
157
|
+
], OrgManagedDatabaseModule);
|
|
158
|
+
function hasOrgDatabaseConfig() {
|
|
159
|
+
return Boolean(process.env.ORG_DATABASE_POOL_API_URL && process.env.ORG_DATABASE_ID);
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=org-managed-db.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"org-managed-db.module.js","sourceRoot":"","sources":["../../src/lib/org-managed-db.module.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CreateBiomePrismaClientOptions } from './types';
|
|
2
|
+
interface PrismaClientLike {
|
|
3
|
+
$on(event: 'error' | 'warn', handler: (event: {
|
|
4
|
+
message: string;
|
|
5
|
+
stack?: string;
|
|
6
|
+
}) => void): void;
|
|
7
|
+
$connect(): Promise<void>;
|
|
8
|
+
$disconnect(): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export declare function createBiomePrismaClient(options: CreateBiomePrismaClientOptions): PrismaClientLike;
|
|
11
|
+
export declare function disconnectBiomePrismaClient(client: PrismaClientLike, biomeId?: string): Promise<void>;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=prisma-factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prisma-factory.d.ts","sourceRoot":"","sources":["../../src/lib/prisma-factory.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,SAAS,CAAC;AAI9D,UAAU,gBAAgB;IACxB,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAClG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAmBD,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,8BAA8B,GACtC,gBAAgB,CAsDlB;AAKD,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE,MAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAQf"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createBiomePrismaClient = createBiomePrismaClient;
|
|
4
|
+
exports.disconnectBiomePrismaClient = disconnectBiomePrismaClient;
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const logger = new common_1.Logger('BiomePrismaFactory');
|
|
7
|
+
const { PrismaClient } = require('@prisma/client');
|
|
8
|
+
function createBiomePrismaClient(options) {
|
|
9
|
+
const { databaseUrl, biomeId = 'unknown', schema, logLevel = 'warn' } = options;
|
|
10
|
+
if (!databaseUrl) {
|
|
11
|
+
throw new Error('[BiomePrismaFactory] databaseUrl is required');
|
|
12
|
+
}
|
|
13
|
+
let finalUrl = databaseUrl;
|
|
14
|
+
if (schema && !databaseUrl.includes(`schema=${schema}`)) {
|
|
15
|
+
const separator = databaseUrl.includes('?') ? '&' : '?';
|
|
16
|
+
finalUrl = `${databaseUrl}${separator}schema=${schema}`;
|
|
17
|
+
}
|
|
18
|
+
const client = new PrismaClient({
|
|
19
|
+
datasources: {
|
|
20
|
+
db: {
|
|
21
|
+
url: finalUrl,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
log: [
|
|
25
|
+
{
|
|
26
|
+
emit: 'event',
|
|
27
|
+
level: logLevel,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
client.$on('error', (event) => {
|
|
32
|
+
logger.error(`[${biomeId}] Prisma error: ${event.message}`, event.stack);
|
|
33
|
+
});
|
|
34
|
+
client.$on('warn', (event) => {
|
|
35
|
+
logger.warn(`[${biomeId}] Prisma warn: ${event.message}`);
|
|
36
|
+
});
|
|
37
|
+
client.$connect()
|
|
38
|
+
.then(() => {
|
|
39
|
+
const schemaLabel = schema ? `${schema} schema` : 'default schema';
|
|
40
|
+
logger.log(`[${biomeId}] Prisma client connected to ${schemaLabel}`);
|
|
41
|
+
})
|
|
42
|
+
.catch((err) => {
|
|
43
|
+
logger.error(`[${biomeId}] Failed to connect Prisma client: ${err.message}`, err.stack);
|
|
44
|
+
});
|
|
45
|
+
return client;
|
|
46
|
+
}
|
|
47
|
+
async function disconnectBiomePrismaClient(client, biomeId = 'unknown') {
|
|
48
|
+
try {
|
|
49
|
+
await client.$disconnect();
|
|
50
|
+
logger.log(`[${biomeId}] Prisma client disconnected`);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
54
|
+
logger.error(`[${biomeId}] Failed to disconnect Prisma client: ${message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=prisma-factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prisma-factory.js","sourceRoot":"","sources":["../../src/lib/prisma-factory.ts"],"names":[],"mappings":";;AA4BA,0DAwDC;AAKD,kEAWC;AApGD,2CAAwC;AAGxC,MAAM,MAAM,GAAG,IAAI,eAAM,CAAC,oBAAoB,CAAC,CAAC;AAQhD,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAYhD,CAAC;AAKF,SAAgB,uBAAuB,CACrC,OAAuC;IAEvC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAG,SAAS,EAAE,MAAM,EAAE,QAAQ,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC;IAEhF,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IAGD,IAAI,QAAQ,GAAG,WAAW,CAAC;IAC3B,IAAI,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,MAAM,EAAE,CAAC,EAAE,CAAC;QACxD,MAAM,SAAS,GAAG,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QACxD,QAAQ,GAAG,GAAG,WAAW,GAAG,SAAS,UAAU,MAAM,EAAE,CAAC;IAC1D,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC;QAC9B,WAAW,EAAE;YACX,EAAE,EAAE;gBACF,GAAG,EAAE,QAAQ;aACd;SACF;QACD,GAAG,EAAE;YACH;gBACE,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,QAAe;aACvB;SACF;KACF,CAAC,CAAC;IAGH,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;QAC5B,MAAM,CAAC,KAAK,CACV,IAAI,OAAO,mBAAmB,KAAK,CAAC,OAAO,EAAE,EAC7C,KAAK,CAAC,KAAK,CACZ,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;QAC3B,MAAM,CAAC,IAAI,CAAC,IAAI,OAAO,kBAAkB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAGH,MAAM,CAAC,QAAQ,EAAE;SACd,IAAI,CAAC,GAAG,EAAE;QACT,MAAM,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC;QACnE,MAAM,CAAC,GAAG,CAAC,IAAI,OAAO,gCAAgC,WAAW,EAAE,CAAC,CAAC;IACvE,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACb,MAAM,CAAC,KAAK,CACV,IAAI,OAAO,sCAAsC,GAAG,CAAC,OAAO,EAAE,EAC9D,GAAG,CAAC,KAAK,CACV,CAAC;IACJ,CAAC,CAAC,CAAC;IAEL,OAAO,MAAM,CAAC;AAChB,CAAC;AAKM,KAAK,UAAU,2BAA2B,CAC/C,MAAwB,EACxB,UAAkB,SAAS;IAE3B,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;QAC3B,MAAM,CAAC,GAAG,CAAC,IAAI,OAAO,8BAA8B,CAAC,CAAC;IACxD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,CAAC,KAAK,CAAC,IAAI,OAAO,yCAAyC,OAAO,EAAE,CAAC,CAAC;IAC9E,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export type MigrationType = 'prisma' | 'flyway' | 'drizzle' | 'custom';
|
|
2
|
+
export interface DatabaseConfig {
|
|
3
|
+
name: string;
|
|
4
|
+
schema?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface EnsureDatabaseUrlsOptions {
|
|
7
|
+
singleDatabase?: boolean;
|
|
8
|
+
dbName?: string;
|
|
9
|
+
dbSchema?: string;
|
|
10
|
+
databases?: Record<string, DatabaseConfig>;
|
|
11
|
+
}
|
|
12
|
+
export interface CreateBiomePrismaClientOptions {
|
|
13
|
+
databaseUrl: string;
|
|
14
|
+
biomeId?: string;
|
|
15
|
+
schema?: string;
|
|
16
|
+
logLevel?: 'error' | 'warn' | 'info' | 'debug';
|
|
17
|
+
}
|
|
18
|
+
export interface DiscoverMigrationsOptions {
|
|
19
|
+
searchPaths: string[];
|
|
20
|
+
migrationType: MigrationType;
|
|
21
|
+
}
|
|
22
|
+
export interface DiscoveredMigrations {
|
|
23
|
+
applied: MigrationRecord[];
|
|
24
|
+
pending: MigrationRecord[];
|
|
25
|
+
errors?: string[];
|
|
26
|
+
}
|
|
27
|
+
export interface MigrationRecord {
|
|
28
|
+
name: string;
|
|
29
|
+
checksum?: string;
|
|
30
|
+
appliedAt?: Date;
|
|
31
|
+
path: string;
|
|
32
|
+
}
|
|
33
|
+
export interface BiomeMigrationOptions {
|
|
34
|
+
databaseUrl: string;
|
|
35
|
+
migrationsPath: string;
|
|
36
|
+
migrationType: MigrationType;
|
|
37
|
+
dryRun?: boolean;
|
|
38
|
+
backupBeforeRun?: boolean;
|
|
39
|
+
biomeId?: string;
|
|
40
|
+
orgDatabasePoolApiUrl?: string;
|
|
41
|
+
databaseId?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface BiomeMigrationResult {
|
|
44
|
+
success: boolean;
|
|
45
|
+
migrationsRun: string[];
|
|
46
|
+
errors?: string[];
|
|
47
|
+
backupId?: string;
|
|
48
|
+
duration: number;
|
|
49
|
+
}
|
|
50
|
+
export interface OrgManagedDatabaseOptions {
|
|
51
|
+
poolApiUrl: string;
|
|
52
|
+
refreshConnInterval?: number;
|
|
53
|
+
databaseId?: string;
|
|
54
|
+
biomeId?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface ConnectionGrant {
|
|
57
|
+
databaseUrl: string;
|
|
58
|
+
host: string;
|
|
59
|
+
port: number;
|
|
60
|
+
database: string;
|
|
61
|
+
schema: string;
|
|
62
|
+
user: string;
|
|
63
|
+
password: string;
|
|
64
|
+
expiresAt: Date;
|
|
65
|
+
databaseId: string;
|
|
66
|
+
schemaName: string;
|
|
67
|
+
roleName: string;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAKvE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAKD,MAAM,WAAW,yBAAyB;IACxC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CAC5C;AAKD,MAAM,WAAW,8BAA8B;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CAChD;AAKD,MAAM,WAAW,yBAAyB;IACxC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,EAAE,aAAa,CAAC;CAC9B;AAKD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAKD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAKD,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,aAAa,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAKD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAKD,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAKD,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-builder.d.ts","sourceRoot":"","sources":["../../src/lib/url-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,yBAAyB,EAAE,MAAM,SAAS,CAAC;AAUzE,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,yBAA8B,GAAG,IAAI,CAahF"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureDatabaseUrls = ensureDatabaseUrls;
|
|
4
|
+
function ensureDatabaseUrls(options = {}) {
|
|
5
|
+
const { singleDatabase = true, dbName, dbSchema, databases, } = options;
|
|
6
|
+
if (singleDatabase) {
|
|
7
|
+
ensureSingleDatabase(dbName, dbSchema);
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
ensureMultiDatabase(databases || {});
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function ensureSingleDatabase(dbName, dbSchema) {
|
|
14
|
+
if (process.env.DATABASE_URL) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const { DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME, DB_SCHEMA, DB_SSL_PARAMS, } = process.env;
|
|
18
|
+
const user = DB_USER;
|
|
19
|
+
const password = DB_PASSWORD;
|
|
20
|
+
const host = DB_HOST;
|
|
21
|
+
const port = DB_PORT;
|
|
22
|
+
const name = DB_NAME || dbName || 'xema';
|
|
23
|
+
const schema = DB_SCHEMA || dbSchema;
|
|
24
|
+
const ssl = DB_SSL_PARAMS;
|
|
25
|
+
if (!user || !password || !host || !port) {
|
|
26
|
+
throw new Error('[BiomeDatabase] DATABASE_URL not set and incomplete DB_* env vars. ' +
|
|
27
|
+
'Set DATABASE_URL or provide DB_USER, DB_PASSWORD, DB_HOST, DB_PORT');
|
|
28
|
+
}
|
|
29
|
+
const base = `postgresql://${user}:${encodeURIComponent(password)}@${host}:${port}/${name}`;
|
|
30
|
+
const schemaPart = schema ? `schema=${schema}` : null;
|
|
31
|
+
const params = [schemaPart, ssl].filter(Boolean).join('&');
|
|
32
|
+
process.env.DATABASE_URL = params ? `${base}?${params}` : base;
|
|
33
|
+
}
|
|
34
|
+
function ensureMultiDatabase(databases) {
|
|
35
|
+
for (const [key, config] of Object.entries(databases)) {
|
|
36
|
+
const envKey = key === 'main' ? 'DATABASE_URL' : `DATABASE_URL_${key.toUpperCase()}`;
|
|
37
|
+
if (process.env[envKey]) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const { DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_SSL_PARAMS, } = process.env;
|
|
41
|
+
if (!DB_USER || !DB_PASSWORD || !DB_HOST || !DB_PORT) {
|
|
42
|
+
throw new Error(`[BiomeDatabase] ${envKey} not set and incomplete DB_* env vars. ` +
|
|
43
|
+
'Set DATABASE_URL or provide DB_USER, DB_PASSWORD, DB_HOST, DB_PORT');
|
|
44
|
+
}
|
|
45
|
+
const base = `postgresql://${DB_USER}:${encodeURIComponent(DB_PASSWORD)}@${DB_HOST}:${DB_PORT}/${config.name}`;
|
|
46
|
+
const schemaPart = config.schema ? `schema=${config.schema}` : null;
|
|
47
|
+
const params = [schemaPart, DB_SSL_PARAMS].filter(Boolean).join('&');
|
|
48
|
+
process.env[envKey] = params ? `${base}?${params}` : base;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=url-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-builder.js","sourceRoot":"","sources":["../../src/lib/url-builder.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xemahq/biome-database-sdk",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Shared utilities for biome API services: database URL construction, migration discovery (Prisma/Flyway/Drizzle), org-managed database attachment, Prisma client factory, and migration runner. Eliminates code duplication across biomes.",
|
|
5
|
+
"license": "Proprietary",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"xema-source": "./src/index.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./dist/lib/*": {
|
|
13
|
+
"types": "./dist/lib/*.d.ts",
|
|
14
|
+
"default": "./dist/lib/*.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"src"
|
|
22
|
+
],
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@xemahq/platform-common": "^0.1.3"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@nestjs/common": ">=10",
|
|
28
|
+
"@nestjs/core": ">=10",
|
|
29
|
+
"@prisma/client": ">=5"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^20.0.0",
|
|
33
|
+
"typescript": "^5.0.0",
|
|
34
|
+
"@nestjs/common": "11.1.13",
|
|
35
|
+
"@nestjs/core": "11.1.13",
|
|
36
|
+
"@nestjs/testing": "11.1.13",
|
|
37
|
+
"@prisma/client": "^5.0.0",
|
|
38
|
+
"jest": "^29.0.0",
|
|
39
|
+
"ts-jest": "^29.0.0"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"xema",
|
|
43
|
+
"biome",
|
|
44
|
+
"database",
|
|
45
|
+
"prisma",
|
|
46
|
+
"migrations",
|
|
47
|
+
"org-managed"
|
|
48
|
+
],
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsc",
|
|
51
|
+
"type-check": "tsc --noEmit",
|
|
52
|
+
"lint": "tsc --noEmit",
|
|
53
|
+
"test": "jest --passWithNoTests"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @xemahq/biome-database-sdk
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for biome API services to initialize, discover, and manage databases.
|
|
5
|
+
* Eliminates code duplication across biomes while supporting single/multi-database setups,
|
|
6
|
+
* org-managed database attachment, and multiple migration systems.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // In a biome's main.ts
|
|
10
|
+
* import { ensureDatabaseUrls } from '@xemahq/biome-database-sdk';
|
|
11
|
+
*
|
|
12
|
+
* ensureDatabaseUrls({ singleDatabase: true });
|
|
13
|
+
* await NestFactory.create(AppModule);
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // In a biome's app.module.ts
|
|
17
|
+
* import { OrgManagedDatabaseModule } from '@xemahq/biome-database-sdk';
|
|
18
|
+
*
|
|
19
|
+
* @Module({
|
|
20
|
+
* imports: [
|
|
21
|
+
* OrgManagedDatabaseModule.register({
|
|
22
|
+
* poolApiUrl: process.env.ORG_DATABASE_POOL_API_URL,
|
|
23
|
+
* }),
|
|
24
|
+
* ],
|
|
25
|
+
* })
|
|
26
|
+
* export class AppModule {}
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
export { ensureDatabaseUrls } from './lib/url-builder';
|
|
30
|
+
export { discoverMigrations } from './lib/migration-discovery';
|
|
31
|
+
export { createBiomePrismaClient, disconnectBiomePrismaClient } from './lib/prisma-factory';
|
|
32
|
+
export { runBiomeMigration } from './lib/migration-runner';
|
|
33
|
+
export {
|
|
34
|
+
OrgManagedDatabaseModule,
|
|
35
|
+
OrgManagedDatabaseService,
|
|
36
|
+
hasOrgDatabaseConfig,
|
|
37
|
+
} from './lib/org-managed-db.module';
|
|
38
|
+
|
|
39
|
+
export type {
|
|
40
|
+
MigrationType,
|
|
41
|
+
DatabaseConfig,
|
|
42
|
+
EnsureDatabaseUrlsOptions,
|
|
43
|
+
CreateBiomePrismaClientOptions,
|
|
44
|
+
DiscoverMigrationsOptions,
|
|
45
|
+
DiscoveredMigrations,
|
|
46
|
+
MigrationRecord,
|
|
47
|
+
BiomeMigrationOptions,
|
|
48
|
+
BiomeMigrationResult,
|
|
49
|
+
OrgManagedDatabaseOptions,
|
|
50
|
+
ConnectionGrant,
|
|
51
|
+
} from './lib/types';
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import type { DiscoveredMigrations, DiscoverMigrationsOptions, MigrationRecord, MigrationType } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Discovers migrations in the given search paths.
|
|
7
|
+
* Supports Prisma, Flyway, Drizzle, and custom migration structures.
|
|
8
|
+
*
|
|
9
|
+
* Returns applied and pending migrations based on the migration system.
|
|
10
|
+
*/
|
|
11
|
+
export async function discoverMigrations(
|
|
12
|
+
options: DiscoverMigrationsOptions,
|
|
13
|
+
): Promise<DiscoveredMigrations> {
|
|
14
|
+
const { searchPaths, migrationType } = options;
|
|
15
|
+
const errors: string[] = [];
|
|
16
|
+
const allMigrations: MigrationRecord[] = [];
|
|
17
|
+
|
|
18
|
+
for (const searchPath of searchPaths) {
|
|
19
|
+
try {
|
|
20
|
+
const absolutePath = resolve(searchPath);
|
|
21
|
+
const migrations = await discoverMigrationsInPath(absolutePath, migrationType);
|
|
22
|
+
allMigrations.push(...migrations);
|
|
23
|
+
} catch (err) {
|
|
24
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25
|
+
errors.push(`Failed to discover migrations in ${searchPath}: ${message}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// For now, we return all discovered as "pending" since we can't determine
|
|
30
|
+
// applied status without a database connection. The calling code can refine
|
|
31
|
+
// this by comparing against `_prisma_migrations` or equivalent.
|
|
32
|
+
return {
|
|
33
|
+
applied: [],
|
|
34
|
+
pending: allMigrations,
|
|
35
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Discover migrations in a single directory.
|
|
41
|
+
*/
|
|
42
|
+
async function discoverMigrationsInPath(
|
|
43
|
+
searchPath: string,
|
|
44
|
+
migrationType: MigrationType,
|
|
45
|
+
): Promise<MigrationRecord[]> {
|
|
46
|
+
try {
|
|
47
|
+
await fs.access(searchPath);
|
|
48
|
+
} catch {
|
|
49
|
+
return []; // path doesn't exist
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
switch (migrationType) {
|
|
53
|
+
case 'prisma':
|
|
54
|
+
return discoverPrismaMigrations(searchPath);
|
|
55
|
+
case 'flyway':
|
|
56
|
+
return discoverFlywayMigrations(searchPath);
|
|
57
|
+
case 'drizzle':
|
|
58
|
+
return discoverDrizzleMigrations(searchPath);
|
|
59
|
+
default:
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Discover Prisma migrations (numeric directory naming convention).
|
|
66
|
+
* Example: 20240101120000_init, 20240102150000_add_users_table
|
|
67
|
+
*/
|
|
68
|
+
async function discoverPrismaMigrations(searchPath: string): Promise<MigrationRecord[]> {
|
|
69
|
+
const entries = await fs.readdir(searchPath, { withFileTypes: true });
|
|
70
|
+
const migrations: MigrationRecord[] = [];
|
|
71
|
+
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
if (!entry.isDirectory()) continue;
|
|
74
|
+
|
|
75
|
+
const name = entry.name;
|
|
76
|
+
// Prisma format: numeric timestamp + underscore + description
|
|
77
|
+
if (/^\d{14}_/.test(name)) {
|
|
78
|
+
migrations.push({
|
|
79
|
+
name,
|
|
80
|
+
path: join(searchPath, name),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return migrations.sort((a, b) => a.name.localeCompare(b.name));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Discover Flyway migrations (V/U/R + numeric version + optional description).
|
|
90
|
+
* Example: V1__init.sql, V2__add_users.sql
|
|
91
|
+
*/
|
|
92
|
+
async function discoverFlywayMigrations(searchPath: string): Promise<MigrationRecord[]> {
|
|
93
|
+
const entries = await fs.readdir(searchPath, { withFileTypes: true });
|
|
94
|
+
const migrations: MigrationRecord[] = [];
|
|
95
|
+
|
|
96
|
+
for (const entry of entries) {
|
|
97
|
+
if (entry.isDirectory()) continue;
|
|
98
|
+
|
|
99
|
+
const name = entry.name;
|
|
100
|
+
// Flyway format: V<version>__<description>.sql or similar
|
|
101
|
+
if (/^[VUR]\d+__.+\.sql$/i.test(name)) {
|
|
102
|
+
migrations.push({
|
|
103
|
+
name,
|
|
104
|
+
path: join(searchPath, name),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return migrations.sort((a, b) => a.name.localeCompare(b.name));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Discover Drizzle migrations (epoch + underscore + description).
|
|
114
|
+
* Example: 0001_init.sql, 0002_add_users.sql
|
|
115
|
+
*/
|
|
116
|
+
async function discoverDrizzleMigrations(searchPath: string): Promise<MigrationRecord[]> {
|
|
117
|
+
const entries = await fs.readdir(searchPath, { withFileTypes: true });
|
|
118
|
+
const migrations: MigrationRecord[] = [];
|
|
119
|
+
|
|
120
|
+
for (const entry of entries) {
|
|
121
|
+
if (entry.isDirectory()) continue;
|
|
122
|
+
|
|
123
|
+
const name = entry.name;
|
|
124
|
+
// Drizzle format: <epoch>_<description>.sql
|
|
125
|
+
if (/^\d+_.+\.sql$/i.test(name)) {
|
|
126
|
+
migrations.push({
|
|
127
|
+
name,
|
|
128
|
+
path: join(searchPath, name),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return migrations.sort((a, b) => a.name.localeCompare(b.name));
|
|
134
|
+
}
|