@open-core/database 0.1.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/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2026 OpenCore Framework Team
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @open-core/database
2
+
3
+ Database adapter package for **OpenCore**. It provides `DatabaseContract` implementations and registration helpers so you can access databases through the framework’s standard API (`query`, `single`, `scalar`, `execute`, `insert`, `transaction`).
4
+
5
+ ## Purpose
6
+
7
+ This package aims to:
8
+
9
+ - Allow selecting the database backend **only via configuration** (convars), without changing data-access code.
10
+ - Keep compatibility with OpenCore’s database abstraction (`DatabaseService`).
11
+ - Provide production-ready adapters for:
12
+ - PostgreSQL (`postgres`)
13
+ - MySQL (`mysql2`)
14
+
15
+ ## How it works (high level)
16
+
17
+ OpenCore resolves the database adapter using `opencore_db_adapter` and a factory registered through `registerDatabaseAdapterFactory(name, factory)`.
18
+
19
+ This package exposes:
20
+
21
+ - `registerPostgresAdapter()` (registers `opencore_db_adapter=postgres`)
22
+ - `registerMysql2Adapter()` (registers `opencore_db_adapter=mysql2`)
23
+
24
+ Once the adapter is registered, you can initialize OpenCore’s database service and use the standard database functions.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pnpm add @open-core/database
30
+ ```
31
+
32
+ > This package includes driver dependencies (`pg`, `mysql2`).
33
+
34
+ ## Quickstart
35
+
36
+ 1. Register the adapters at the start of your server/resource.
37
+ 2. Configure `opencore_db_adapter` and the driver-specific convars.
38
+ 3. Initialize the framework database module.
39
+
40
+ Example:
41
+
42
+ ```ts
43
+ import { registerPostgresAdapter, registerMysql2Adapter } from '@open-core/database'
44
+ import { Server } from '@open-core/framework'
45
+
46
+ registerPostgresAdapter() // or registerMysql2Adapter()
47
+ Server.initDatabase()
48
+ // we highly recommend using the databaseService by injection
49
+ const rows = await Server.query('SELECT 1 as ok')
50
+ ```
51
+
52
+ ## Configuration
53
+
54
+ - `opencore_db_adapter`
55
+ - `postgres`
56
+ - `mysql2`
57
+
58
+ Guides:
59
+
60
+ - [PostgreSQL](./docs/postgresql.md)
61
+ - [MySQL](./docs/mysql.md)
62
+ - [ORM integration](./docs/orm-integration.md)
63
+
64
+ ## Compatibility notes
65
+
66
+ - Transactions using the oxmysql-style named shared parameters format `@name` (`TransactionSharedParams`) are not supported by these adapters. Use the **tuple** or **object** format with positional parameters.
67
+ - For PostgreSQL, to obtain `insertId` reliably, use `RETURNING id`.
@@ -0,0 +1,2 @@
1
+ export { Mysql2Adapter } from './mysql.adapter';
2
+ export { PostgresAdapter } from './postgresql.adapter';
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PostgresAdapter = exports.Mysql2Adapter = void 0;
4
+ var mysql_adapter_1 = require("./mysql.adapter");
5
+ Object.defineProperty(exports, "Mysql2Adapter", { enumerable: true, get: function () { return mysql_adapter_1.Mysql2Adapter; } });
6
+ var postgresql_adapter_1 = require("./postgresql.adapter");
7
+ Object.defineProperty(exports, "PostgresAdapter", { enumerable: true, get: function () { return postgresql_adapter_1.PostgresAdapter; } });
@@ -0,0 +1,12 @@
1
+ import { Server } from '@open-core/framework';
2
+ import { type Pool } from 'mysql2/promise';
3
+ export declare class Mysql2Adapter extends Server.DatabaseContract {
4
+ private pool;
5
+ constructor(pool?: Pool);
6
+ query<T = any>(sql: string, params?: any[]): Promise<T[]>;
7
+ single<T = any>(sql: string, params?: any[]): Promise<T | null>;
8
+ scalar<T = any>(sql: string, params?: any[]): Promise<T | null>;
9
+ execute(sql: string, params?: any[]): Promise<Server.ExecuteResult>;
10
+ insert(sql: string, params?: any[]): Promise<Server.InsertResult>;
11
+ transaction(queries: Server.TransactionInput, sharedParams?: Server.TransactionSharedParams): Promise<boolean>;
12
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Mysql2Adapter = void 0;
7
+ const framework_1 = require("@open-core/framework");
8
+ const promise_1 = __importDefault(require("mysql2/promise"));
9
+ function getRequiredConvar(name) {
10
+ const value = globalThis.GetConvar?.(name, '')?.trim() ?? '';
11
+ if (!value)
12
+ throw new Error(`[OpenCoreDB] Missing required convar '${name}'`);
13
+ return value;
14
+ }
15
+ function getOptionalConvar(name, fallback = '') {
16
+ return globalThis.GetConvar?.(name, fallback)?.trim() ?? fallback;
17
+ }
18
+ function getOptionalIntConvar(name, fallback) {
19
+ const raw = getOptionalConvar(name, String(fallback));
20
+ const n = Number(raw);
21
+ return Number.isFinite(n) ? n : fallback;
22
+ }
23
+ function buildPoolOptionsFromConvars() {
24
+ const url = getOptionalConvar('opencore_mysql_url', '');
25
+ const connectionLimit = getOptionalIntConvar('opencore_mysql_pool_max', 10);
26
+ if (url) {
27
+ return {
28
+ uri: url,
29
+ connectionLimit,
30
+ };
31
+ }
32
+ return {
33
+ host: getRequiredConvar('opencore_mysql_host'),
34
+ port: getOptionalIntConvar('opencore_mysql_port', 3306),
35
+ user: getRequiredConvar('opencore_mysql_user'),
36
+ password: getRequiredConvar('opencore_mysql_password'),
37
+ database: getRequiredConvar('opencore_mysql_database'),
38
+ connectionLimit,
39
+ };
40
+ }
41
+ function normalizeTransactionQueries(queries, sharedParams) {
42
+ if (queries.length === 0)
43
+ return [];
44
+ const first = queries[0];
45
+ if (typeof first === 'string') {
46
+ if (sharedParams && Object.keys(sharedParams).length > 0) {
47
+ throw new Error("[OpenCoreDB] MySQL adapter doesn't support named sharedParams transactions (the '@name' format). Use tuple/object format with positional params.");
48
+ }
49
+ return queries.map((sql) => ({ sql }));
50
+ }
51
+ if (Array.isArray(first)) {
52
+ return queries.map(([sql, params]) => ({ sql, params }));
53
+ }
54
+ return queries.map((q) => ({
55
+ sql: q.query,
56
+ params: q.values,
57
+ }));
58
+ }
59
+ class Mysql2Adapter extends framework_1.Server.DatabaseContract {
60
+ pool;
61
+ constructor(pool) {
62
+ super();
63
+ this.pool = pool ?? promise_1.default.createPool(buildPoolOptionsFromConvars());
64
+ }
65
+ async query(sql, params) {
66
+ const [rows] = await this.pool.query(sql, params);
67
+ return rows ?? [];
68
+ }
69
+ async single(sql, params) {
70
+ const rows = await this.query(sql, params);
71
+ return rows[0] ?? null;
72
+ }
73
+ async scalar(sql, params) {
74
+ const row = await this.single(sql, params);
75
+ if (!row)
76
+ return null;
77
+ const firstKey = Object.keys(row)[0];
78
+ return (firstKey ? row[firstKey] : null) ?? null;
79
+ }
80
+ async execute(sql, params) {
81
+ const [result] = await this.pool.execute(sql, params);
82
+ return { affectedRows: result.affectedRows ?? 0 };
83
+ }
84
+ async insert(sql, params) {
85
+ const [result] = await this.pool.execute(sql, params);
86
+ const insertId = typeof result.insertId === 'number' ? result.insertId : Number(result.insertId ?? 0);
87
+ return { insertId: Number.isFinite(insertId) ? insertId : 0 };
88
+ }
89
+ async transaction(queries, sharedParams) {
90
+ const normalized = normalizeTransactionQueries(queries, sharedParams);
91
+ if (normalized.length === 0)
92
+ return true;
93
+ const conn = await this.pool.getConnection();
94
+ try {
95
+ await conn.beginTransaction();
96
+ for (const q of normalized) {
97
+ await conn.query(q.sql, q.params);
98
+ }
99
+ await conn.commit();
100
+ return true;
101
+ }
102
+ catch (e) {
103
+ try {
104
+ await conn.rollback();
105
+ }
106
+ catch { }
107
+ return false;
108
+ }
109
+ finally {
110
+ conn.release();
111
+ }
112
+ }
113
+ }
114
+ exports.Mysql2Adapter = Mysql2Adapter;
@@ -0,0 +1,12 @@
1
+ import { Server } from '@open-core/framework';
2
+ import { Pool } from 'pg';
3
+ export declare class PostgresAdapter extends Server.DatabaseContract {
4
+ private pool;
5
+ constructor(pool?: Pool);
6
+ query<T = any>(sql: string, params?: any[]): Promise<T[]>;
7
+ single<T = any>(sql: string, params?: any[]): Promise<T | null>;
8
+ scalar<T = any>(sql: string, params?: any[]): Promise<T | null>;
9
+ execute(sql: string, params?: any[]): Promise<Server.ExecuteResult>;
10
+ insert(sql: string, params?: any[]): Promise<Server.InsertResult>;
11
+ transaction(queries: Server.TransactionInput, sharedParams?: Server.TransactionSharedParams): Promise<boolean>;
12
+ }
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PostgresAdapter = void 0;
4
+ const framework_1 = require("@open-core/framework");
5
+ const pg_1 = require("pg");
6
+ const pg_placeholders_1 = require("../sql/pg-placeholders");
7
+ function getRequiredConvar(name) {
8
+ const value = globalThis.GetConvar?.(name, '')?.trim() ?? '';
9
+ if (!value)
10
+ throw new Error(`[OpenCoreDB] Missing required convar '${name}'`);
11
+ return value;
12
+ }
13
+ function getOptionalConvar(name, fallback = '') {
14
+ return globalThis.GetConvar?.(name, fallback)?.trim() ?? fallback;
15
+ }
16
+ function getOptionalIntConvar(name, fallback) {
17
+ const raw = getOptionalConvar(name, String(fallback));
18
+ const n = Number(raw);
19
+ return Number.isFinite(n) ? n : fallback;
20
+ }
21
+ function buildPoolConfigFromConvars() {
22
+ const url = getOptionalConvar('opencore_pg_url', '');
23
+ if (url) {
24
+ return {
25
+ connectionString: url,
26
+ max: getOptionalIntConvar('opencore_pg_pool_max', 10),
27
+ };
28
+ }
29
+ return {
30
+ host: getRequiredConvar('opencore_pg_host'),
31
+ port: getOptionalIntConvar('opencore_pg_port', 5432),
32
+ user: getRequiredConvar('opencore_pg_user'),
33
+ password: getRequiredConvar('opencore_pg_password'),
34
+ database: getRequiredConvar('opencore_pg_database'),
35
+ max: getOptionalIntConvar('opencore_pg_pool_max', 10),
36
+ };
37
+ }
38
+ function normalizeTransactionQueries(queries, sharedParams) {
39
+ if (queries.length === 0)
40
+ return [];
41
+ const first = queries[0];
42
+ if (typeof first === 'string') {
43
+ if (sharedParams && Object.keys(sharedParams).length > 0) {
44
+ throw new Error("[OpenCoreDB] Postgres adapter doesn't support named sharedParams transactions (the '@name' format). Use tuple/object format with positional params.");
45
+ }
46
+ return queries.map((q) => ({ sql: q }));
47
+ }
48
+ if (Array.isArray(first)) {
49
+ return queries.map(([sql, params]) => ({ sql, params }));
50
+ }
51
+ return queries.map((q) => ({
52
+ sql: q.query,
53
+ params: q.values,
54
+ }));
55
+ }
56
+ class PostgresAdapter extends framework_1.Server.DatabaseContract {
57
+ pool;
58
+ constructor(pool) {
59
+ super();
60
+ this.pool = pool ?? new pg_1.Pool(buildPoolConfigFromConvars());
61
+ }
62
+ async query(sql, params) {
63
+ const pgSql = (0, pg_placeholders_1.toPgPlaceHolders)(sql);
64
+ const res = await this.pool.query(pgSql, params);
65
+ return (res.rows ?? []);
66
+ }
67
+ async single(sql, params) {
68
+ const rows = await this.query(sql, params);
69
+ return rows[0] ?? null;
70
+ }
71
+ async scalar(sql, params) {
72
+ const row = await this.single(sql, params);
73
+ if (!row)
74
+ return null;
75
+ const firstKey = Object.keys(row)[0];
76
+ return (firstKey ? row[firstKey] : null) ?? null;
77
+ }
78
+ async execute(sql, params) {
79
+ const pgSql = (0, pg_placeholders_1.toPgPlaceHolders)(sql);
80
+ const res = await this.pool.query(pgSql, params);
81
+ return { affectedRows: res.rowCount ?? 0 };
82
+ }
83
+ async insert(sql, params) {
84
+ const pgSql = (0, pg_placeholders_1.toPgPlaceHolders)(sql);
85
+ const res = await this.pool.query(pgSql, params);
86
+ const firstRow = res.rows?.[0];
87
+ const insertIdRaw = firstRow?.id ?? firstRow?.insertId ?? firstRow?.insert_id ?? firstRow?.ID ?? null;
88
+ const insertId = typeof insertIdRaw === 'number' ? insertIdRaw : Number(insertIdRaw ?? 0);
89
+ return { insertId: Number.isFinite(insertId) ? insertId : 0 };
90
+ }
91
+ async transaction(queries, sharedParams) {
92
+ const normalized = normalizeTransactionQueries(queries, sharedParams);
93
+ if (normalized.length === 0)
94
+ return true;
95
+ const client = await this.pool.connect();
96
+ try {
97
+ await client.query('BEGIN');
98
+ for (const q of normalized) {
99
+ const pgSql = (0, pg_placeholders_1.toPgPlaceHolders)(q.sql);
100
+ await client.query(pgSql, q.params);
101
+ }
102
+ await client.query('COMMIT');
103
+ return true;
104
+ }
105
+ catch (e) {
106
+ try {
107
+ await client.query('ROLLBACK');
108
+ }
109
+ catch { }
110
+ return false;
111
+ }
112
+ finally {
113
+ client.release();
114
+ }
115
+ }
116
+ }
117
+ exports.PostgresAdapter = PostgresAdapter;
@@ -0,0 +1,2 @@
1
+ export { registerPostgresAdapter, registerMysql2Adapter } from './register';
2
+ export * from './adapters/index';
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.registerMysql2Adapter = exports.registerPostgresAdapter = void 0;
18
+ var register_1 = require("./register");
19
+ Object.defineProperty(exports, "registerPostgresAdapter", { enumerable: true, get: function () { return register_1.registerPostgresAdapter; } });
20
+ Object.defineProperty(exports, "registerMysql2Adapter", { enumerable: true, get: function () { return register_1.registerMysql2Adapter; } });
21
+ __exportStar(require("./adapters/index"), exports);
@@ -0,0 +1,2 @@
1
+ export declare function registerPostgresAdapter(): void;
2
+ export declare function registerMysql2Adapter(): void;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerPostgresAdapter = registerPostgresAdapter;
4
+ exports.registerMysql2Adapter = registerMysql2Adapter;
5
+ const server_1 = require("@open-core/framework/dist/runtime/server");
6
+ const postgresql_adapter_1 = require("./adapters/postgresql.adapter");
7
+ const mysql_adapter_1 = require("./adapters/mysql.adapter");
8
+ let registered = false;
9
+ function registerPostgresAdapter() {
10
+ if (registered)
11
+ return;
12
+ (0, server_1.registerDatabaseAdapterFactory)('postgres', () => new postgresql_adapter_1.PostgresAdapter());
13
+ registered = true;
14
+ }
15
+ function registerMysql2Adapter() {
16
+ if (registered)
17
+ return;
18
+ (0, server_1.registerDatabaseAdapterFactory)('mysql2', () => new mysql_adapter_1.Mysql2Adapter());
19
+ registered = true;
20
+ }
@@ -0,0 +1 @@
1
+ export declare function toPgPlaceHolders(sql: string): string;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toPgPlaceHolders = toPgPlaceHolders;
4
+ function toPgPlaceHolders(sql) {
5
+ let idx = 0;
6
+ return sql.replace(/\?/g, () => {
7
+ idx += 1;
8
+ return `$${idx}`;
9
+ });
10
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@open-core/database",
3
+ "version": "0.1.0",
4
+ "type": "commonjs",
5
+ "description": "Database library for the Open Core framework with PostgreSQL and MySQL drivers.",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc -p tsconfig.build.json",
13
+ "prepack": "npm run build",
14
+ "typecheck": "tsc --noEmit"
15
+ },
16
+ "keywords": [
17
+ "open-core",
18
+ "database",
19
+ "sql",
20
+ "postgres",
21
+ "postgresql",
22
+ "mysql",
23
+ "node",
24
+ "typescript"
25
+ ],
26
+ "author": "OpenCore Team",
27
+ "license": "ISC",
28
+ "packageManager": "pnpm@10.27.0",
29
+ "repository": {
30
+ "url": "https://github.com/newcore-network/opencore-database"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "dependencies": {
36
+ "@open-core/framework": "^0.2.4",
37
+ "mysql2": "^3.16.0",
38
+ "pg": "^8.16.3"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^25.0.3",
42
+ "@types/pg": "^8.16.0",
43
+ "typescript": "^5.9.3"
44
+ }
45
+ }