@mostajs/orm 1.0.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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +548 -0
  3. package/dist/core/base-repository.d.ts +26 -0
  4. package/dist/core/base-repository.js +82 -0
  5. package/dist/core/config.d.ts +62 -0
  6. package/dist/core/config.js +116 -0
  7. package/dist/core/errors.d.ts +30 -0
  8. package/dist/core/errors.js +49 -0
  9. package/dist/core/factory.d.ts +41 -0
  10. package/dist/core/factory.js +142 -0
  11. package/dist/core/normalizer.d.ts +9 -0
  12. package/dist/core/normalizer.js +19 -0
  13. package/dist/core/registry.d.ts +43 -0
  14. package/dist/core/registry.js +78 -0
  15. package/dist/core/types.d.ts +228 -0
  16. package/dist/core/types.js +5 -0
  17. package/dist/dialects/abstract-sql.dialect.d.ts +113 -0
  18. package/dist/dialects/abstract-sql.dialect.js +1071 -0
  19. package/dist/dialects/cockroachdb.dialect.d.ts +2 -0
  20. package/dist/dialects/cockroachdb.dialect.js +23 -0
  21. package/dist/dialects/db2.dialect.d.ts +2 -0
  22. package/dist/dialects/db2.dialect.js +190 -0
  23. package/dist/dialects/hana.dialect.d.ts +2 -0
  24. package/dist/dialects/hana.dialect.js +199 -0
  25. package/dist/dialects/hsqldb.dialect.d.ts +2 -0
  26. package/dist/dialects/hsqldb.dialect.js +114 -0
  27. package/dist/dialects/mariadb.dialect.d.ts +2 -0
  28. package/dist/dialects/mariadb.dialect.js +87 -0
  29. package/dist/dialects/mongo.dialect.d.ts +2 -0
  30. package/dist/dialects/mongo.dialect.js +480 -0
  31. package/dist/dialects/mssql.dialect.d.ts +27 -0
  32. package/dist/dialects/mssql.dialect.js +127 -0
  33. package/dist/dialects/mysql.dialect.d.ts +24 -0
  34. package/dist/dialects/mysql.dialect.js +101 -0
  35. package/dist/dialects/oracle.dialect.d.ts +2 -0
  36. package/dist/dialects/oracle.dialect.js +206 -0
  37. package/dist/dialects/postgres.dialect.d.ts +26 -0
  38. package/dist/dialects/postgres.dialect.js +105 -0
  39. package/dist/dialects/spanner.dialect.d.ts +2 -0
  40. package/dist/dialects/spanner.dialect.js +259 -0
  41. package/dist/dialects/sqlite.dialect.d.ts +2 -0
  42. package/dist/dialects/sqlite.dialect.js +1027 -0
  43. package/dist/dialects/sybase.dialect.d.ts +2 -0
  44. package/dist/dialects/sybase.dialect.js +119 -0
  45. package/dist/index.d.ts +8 -0
  46. package/dist/index.js +26 -0
  47. package/docs/api-reference.md +1009 -0
  48. package/docs/dialects.md +673 -0
  49. package/docs/tutorial.md +846 -0
  50. package/package.json +91 -0
@@ -0,0 +1,101 @@
1
+ // MySQL Dialect — extends AbstractSqlDialect
2
+ // Equivalent to org.hibernate.dialect.MySQLDialect (Hibernate ORM 6.4)
3
+ // Driver: npm install mysql2
4
+ // Author: Dr Hamid MADANI drmdh@msn.com
5
+ import { AbstractSqlDialect } from './abstract-sql.dialect.js';
6
+ // ============================================================
7
+ // Type Mapping — DAL FieldType → MySQL column type
8
+ // ============================================================
9
+ const MYSQL_TYPE_MAP = {
10
+ string: 'TEXT',
11
+ number: 'DOUBLE',
12
+ boolean: 'TINYINT(1)',
13
+ date: 'DATETIME',
14
+ json: 'JSON',
15
+ array: 'JSON',
16
+ };
17
+ // ============================================================
18
+ // MySQLDialect
19
+ // ============================================================
20
+ export class MySQLDialect extends AbstractSqlDialect {
21
+ dialectType = 'mysql';
22
+ pool = null;
23
+ // --- Abstract implementations ---
24
+ quoteIdentifier(name) {
25
+ return `\`${name}\``;
26
+ }
27
+ getPlaceholder(_index) {
28
+ return '?';
29
+ }
30
+ fieldToSqlType(field) {
31
+ return MYSQL_TYPE_MAP[field.type] || 'TEXT';
32
+ }
33
+ getIdColumnType() {
34
+ return 'VARCHAR(36)';
35
+ }
36
+ getTableListQuery() {
37
+ return "SELECT table_name as name FROM information_schema.tables WHERE table_schema = DATABASE()";
38
+ }
39
+ // --- Hooks ---
40
+ supportsIfNotExists() { return true; }
41
+ supportsReturning() { return false; }
42
+ // MySQL uses TINYINT(1) for boolean: 1 = true, 0 = false
43
+ serializeBoolean(v) { return v ? 1 : 0; }
44
+ deserializeBoolean(v) {
45
+ return v === 1 || v === true || v === '1';
46
+ }
47
+ // --- Connection ---
48
+ async doConnect(config) {
49
+ try {
50
+ const mysql2 = await import(/* webpackIgnore: true */ 'mysql2/promise');
51
+ const createPool = mysql2.default?.createPool || mysql2.createPool;
52
+ this.pool = createPool({
53
+ uri: config.uri,
54
+ connectionLimit: config.poolSize ?? 10,
55
+ waitForConnections: true,
56
+ });
57
+ }
58
+ catch (e) {
59
+ throw new Error(`MySQL driver not found. Install it: npm install mysql2\n` +
60
+ `Original error: ${e instanceof Error ? e.message : String(e)}`);
61
+ }
62
+ }
63
+ async doDisconnect() {
64
+ if (this.pool) {
65
+ await this.pool.end();
66
+ this.pool = null;
67
+ }
68
+ }
69
+ async doTestConnection() {
70
+ if (!this.pool)
71
+ return false;
72
+ const conn = await this.pool.getConnection();
73
+ try {
74
+ await conn.query('SELECT 1');
75
+ return true;
76
+ }
77
+ finally {
78
+ conn.release();
79
+ }
80
+ }
81
+ // --- Query execution ---
82
+ async executeQuery(sql, params) {
83
+ if (!this.pool)
84
+ throw new Error('MySQL not connected. Call connect() first.');
85
+ const [rows] = await this.pool.execute(sql, params);
86
+ return rows;
87
+ }
88
+ async executeRun(sql, params) {
89
+ if (!this.pool)
90
+ throw new Error('MySQL not connected. Call connect() first.');
91
+ const [result] = await this.pool.execute(sql, params);
92
+ return { changes: result.affectedRows ?? 0 };
93
+ }
94
+ getDialectLabel() { return 'MySQL'; }
95
+ }
96
+ // ============================================================
97
+ // Factory export
98
+ // ============================================================
99
+ export function createDialect() {
100
+ return new MySQLDialect();
101
+ }
@@ -0,0 +1,2 @@
1
+ import type { IDialect } from '../core/types.js';
2
+ export declare function createDialect(): IDialect;
@@ -0,0 +1,206 @@
1
+ // Oracle Database Dialect — extends AbstractSqlDialect
2
+ // Equivalent to org.hibernate.dialect.OracleDialect (Hibernate ORM 6.4)
3
+ // Driver: npm install oracledb
4
+ // Author: Dr Hamid MADANI drmdh@msn.com
5
+ import { AbstractSqlDialect } from './abstract-sql.dialect.js';
6
+ // ============================================================
7
+ // Type Mapping — DAL FieldType → Oracle column type
8
+ // ============================================================
9
+ const ORACLE_TYPE_MAP = {
10
+ string: 'VARCHAR2(4000)',
11
+ number: 'NUMBER',
12
+ boolean: 'NUMBER(1)',
13
+ date: 'TIMESTAMP',
14
+ json: 'CLOB',
15
+ array: 'CLOB',
16
+ };
17
+ // ============================================================
18
+ // OracleDialect
19
+ // ============================================================
20
+ class OracleDialect extends AbstractSqlDialect {
21
+ dialectType = 'oracle';
22
+ pool = null;
23
+ oracledb = null;
24
+ // --- Abstract implementations ---
25
+ quoteIdentifier(name) {
26
+ return `"${name}"`;
27
+ }
28
+ // Oracle uses :1, :2, ... bind variables
29
+ getPlaceholder(index) {
30
+ return `:${index}`;
31
+ }
32
+ fieldToSqlType(field) {
33
+ return ORACLE_TYPE_MAP[field.type] || 'VARCHAR2(4000)';
34
+ }
35
+ getIdColumnType() {
36
+ return 'VARCHAR2(36)';
37
+ }
38
+ getTableListQuery() {
39
+ return "SELECT table_name as name FROM user_tables";
40
+ }
41
+ // --- Hooks ---
42
+ // Oracle prior to 23c doesn't support IF NOT EXISTS
43
+ supportsIfNotExists() { return false; }
44
+ supportsReturning() { return false; }
45
+ // Oracle NUMBER(1): 1 = true, 0 = false
46
+ serializeBoolean(v) { return v ? 1 : 0; }
47
+ deserializeBoolean(v) {
48
+ return v === 1 || v === true || v === '1';
49
+ }
50
+ /** Oracle LIKE is case-sensitive — use UPPER() for case-insensitive search */
51
+ buildRegexCondition(col, flags) {
52
+ if (flags?.includes('i')) {
53
+ return `UPPER(${col}) LIKE UPPER(${this.nextPlaceholder()})`;
54
+ }
55
+ return `${col} LIKE ${this.nextPlaceholder()}`;
56
+ }
57
+ // Oracle uses OFFSET n ROWS FETCH FIRST m ROWS ONLY (12c+)
58
+ buildLimitOffset(options) {
59
+ if (!options?.limit && !options?.skip)
60
+ return '';
61
+ const offset = options.skip ?? 0;
62
+ const limit = options.limit;
63
+ let sql = ` OFFSET ${offset} ROWS`;
64
+ if (limit)
65
+ sql += ` FETCH FIRST ${limit} ROWS ONLY`;
66
+ return sql;
67
+ }
68
+ // Oracle: use PL/SQL block to check existence before CREATE TABLE
69
+ getCreateTablePrefix(tableName) {
70
+ const q = this.quoteIdentifier(tableName);
71
+ return `CREATE TABLE ${q}`;
72
+ }
73
+ getCreateIndexPrefix(indexName, unique) {
74
+ const u = unique ? 'UNIQUE ' : '';
75
+ return `CREATE ${u}INDEX ${this.quoteIdentifier(indexName)}`;
76
+ }
77
+ // --- Connection ---
78
+ async doConnect(config) {
79
+ try {
80
+ const oracledb = await import(/* webpackIgnore: true */ 'oracledb');
81
+ this.oracledb = oracledb.default || oracledb;
82
+ this.oracledb.outFormat = this.oracledb.OUT_FORMAT_OBJECT;
83
+ this.oracledb.autoCommit = true;
84
+ this.pool = await this.oracledb.createPool({
85
+ connectString: config.uri,
86
+ poolMax: config.poolSize ?? 10,
87
+ poolMin: 2,
88
+ });
89
+ }
90
+ catch (e) {
91
+ throw new Error(`Oracle driver not found. Install it: npm install oracledb\n` +
92
+ `Original error: ${e instanceof Error ? e.message : String(e)}`);
93
+ }
94
+ }
95
+ async doDisconnect() {
96
+ if (this.pool) {
97
+ await this.pool.close(0);
98
+ this.pool = null;
99
+ }
100
+ }
101
+ async doTestConnection() {
102
+ if (!this.pool)
103
+ return false;
104
+ const conn = await this.pool.getConnection();
105
+ try {
106
+ await conn.execute('SELECT 1 FROM DUAL');
107
+ return true;
108
+ }
109
+ finally {
110
+ await conn.close();
111
+ }
112
+ }
113
+ // --- Query execution ---
114
+ async executeQuery(sql, params) {
115
+ if (!this.pool)
116
+ throw new Error('Oracle not connected. Call connect() first.');
117
+ const conn = await this.pool.getConnection();
118
+ try {
119
+ const result = await conn.execute(sql, params);
120
+ return result.rows ?? [];
121
+ }
122
+ finally {
123
+ await conn.close();
124
+ }
125
+ }
126
+ async executeRun(sql, params) {
127
+ if (!this.pool)
128
+ throw new Error('Oracle not connected. Call connect() first.');
129
+ const conn = await this.pool.getConnection();
130
+ try {
131
+ const result = await conn.execute(sql, params);
132
+ return { changes: result.rowsAffected ?? 0 };
133
+ }
134
+ finally {
135
+ await conn.close();
136
+ }
137
+ }
138
+ // Override initSchema to handle Oracle's lack of IF NOT EXISTS
139
+ async initSchema(schemas) {
140
+ this.schemas = schemas;
141
+ const strategy = this.config?.schemaStrategy ?? 'none';
142
+ this.log('INIT_SCHEMA', `strategy=${strategy}`, { entities: schemas.map(s => s.name) });
143
+ if (strategy === 'none')
144
+ return;
145
+ if (strategy === 'validate') {
146
+ for (const schema of schemas) {
147
+ const exists = await this.tableExists(schema.collection);
148
+ if (!exists) {
149
+ throw new Error(`Schema validation failed: table "${schema.collection}" does not exist ` +
150
+ `(entity: ${schema.name}). Set schemaStrategy to "update" or "create".`);
151
+ }
152
+ }
153
+ return;
154
+ }
155
+ // For 'update' strategy: create tables only if they don't exist
156
+ for (const schema of schemas) {
157
+ const exists = await this.tableExists(schema.collection);
158
+ if (!exists) {
159
+ const createSql = this.generateCreateTable(schema);
160
+ this.log('DDL', schema.collection, createSql);
161
+ await this.executeRun(createSql, []);
162
+ }
163
+ // Indexes: check existence before creating
164
+ const indexStatements = this.generateIndexes(schema);
165
+ for (const stmt of indexStatements) {
166
+ try {
167
+ await this.executeRun(stmt, []);
168
+ }
169
+ catch {
170
+ // Index may already exist — ignore
171
+ }
172
+ }
173
+ }
174
+ // Junction tables
175
+ for (const schema of schemas) {
176
+ for (const [, rel] of Object.entries(schema.relations)) {
177
+ if (rel.type === 'many-to-many' && rel.through) {
178
+ const exists = await this.tableExists(rel.through);
179
+ if (exists)
180
+ continue;
181
+ const targetSchema = schemas.find(s => s.name === rel.target);
182
+ if (!targetSchema)
183
+ continue;
184
+ const sourceKey = `${schema.name.toLowerCase()}Id`;
185
+ const targetKey = `${rel.target.toLowerCase()}Id`;
186
+ const q = (n) => this.quoteIdentifier(n);
187
+ const idType = this.getIdColumnType();
188
+ const ddl = `CREATE TABLE ${q(rel.through)} (
189
+ ${q(sourceKey)} ${idType} NOT NULL,
190
+ ${q(targetKey)} ${idType} NOT NULL,
191
+ PRIMARY KEY (${q(sourceKey)}, ${q(targetKey)})
192
+ )`;
193
+ this.log('DDL_JUNCTION', rel.through, ddl);
194
+ await this.executeRun(ddl, []);
195
+ }
196
+ }
197
+ }
198
+ }
199
+ getDialectLabel() { return 'Oracle'; }
200
+ }
201
+ // ============================================================
202
+ // Factory export
203
+ // ============================================================
204
+ export function createDialect() {
205
+ return new OracleDialect();
206
+ }
@@ -0,0 +1,26 @@
1
+ import type { IDialect, DialectType, ConnectionConfig, FieldDef } from '../core/types.js';
2
+ import { AbstractSqlDialect } from './abstract-sql.dialect.js';
3
+ export declare class PostgresDialect extends AbstractSqlDialect {
4
+ readonly dialectType: DialectType;
5
+ protected pool: unknown;
6
+ quoteIdentifier(name: string): string;
7
+ getPlaceholder(index: number): string;
8
+ fieldToSqlType(field: FieldDef): string;
9
+ getIdColumnType(): string;
10
+ getTableListQuery(): string;
11
+ protected supportsIfNotExists(): boolean;
12
+ protected supportsReturning(): boolean;
13
+ protected serializeBoolean(v: boolean): unknown;
14
+ protected deserializeBoolean(v: unknown): boolean;
15
+ /** PostgreSQL LIKE is case-sensitive — use ILIKE when flags contain 'i' */
16
+ protected buildRegexCondition(col: string, flags?: string): string;
17
+ doConnect(config: ConnectionConfig): Promise<void>;
18
+ doDisconnect(): Promise<void>;
19
+ doTestConnection(): Promise<boolean>;
20
+ executeQuery<T>(sql: string, params: unknown[]): Promise<T[]>;
21
+ executeRun(sql: string, params: unknown[]): Promise<{
22
+ changes: number;
23
+ }>;
24
+ protected getDialectLabel(): string;
25
+ }
26
+ export declare function createDialect(): IDialect;
@@ -0,0 +1,105 @@
1
+ // PostgreSQL Dialect — extends AbstractSqlDialect
2
+ // Equivalent to org.hibernate.dialect.PostgreSQLDialect (Hibernate ORM 6.4)
3
+ // Driver: npm install pg
4
+ // Author: Dr Hamid MADANI drmdh@msn.com
5
+ import { AbstractSqlDialect } from './abstract-sql.dialect.js';
6
+ // ============================================================
7
+ // Type Mapping — DAL FieldType → PostgreSQL column type
8
+ // ============================================================
9
+ const PG_TYPE_MAP = {
10
+ string: 'TEXT',
11
+ number: 'DOUBLE PRECISION',
12
+ boolean: 'BOOLEAN',
13
+ date: 'TIMESTAMPTZ',
14
+ json: 'JSONB',
15
+ array: 'JSONB',
16
+ };
17
+ // ============================================================
18
+ // PostgresDialect
19
+ // ============================================================
20
+ export class PostgresDialect extends AbstractSqlDialect {
21
+ dialectType = 'postgres';
22
+ pool = null;
23
+ // --- Abstract implementations ---
24
+ quoteIdentifier(name) {
25
+ return `"${name}"`;
26
+ }
27
+ getPlaceholder(index) {
28
+ return `$${index}`;
29
+ }
30
+ fieldToSqlType(field) {
31
+ return PG_TYPE_MAP[field.type] || 'TEXT';
32
+ }
33
+ getIdColumnType() {
34
+ return 'TEXT';
35
+ }
36
+ getTableListQuery() {
37
+ return "SELECT tablename as name FROM pg_tables WHERE schemaname = 'public'";
38
+ }
39
+ // --- Hooks ---
40
+ supportsIfNotExists() { return true; }
41
+ supportsReturning() { return true; }
42
+ serializeBoolean(v) { return v; }
43
+ deserializeBoolean(v) { return v === true || v === 't' || v === 'true'; }
44
+ /** PostgreSQL LIKE is case-sensitive — use ILIKE when flags contain 'i' */
45
+ buildRegexCondition(col, flags) {
46
+ if (flags?.includes('i')) {
47
+ return `${col} ILIKE ${this.nextPlaceholder()}`;
48
+ }
49
+ return `${col} LIKE ${this.nextPlaceholder()}`;
50
+ }
51
+ // --- Connection ---
52
+ async doConnect(config) {
53
+ try {
54
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
55
+ const pg = await import(/* webpackIgnore: true */ 'pg');
56
+ const Pool = pg.default?.Pool || pg.Pool;
57
+ this.pool = new Pool({
58
+ connectionString: config.uri,
59
+ max: config.poolSize ?? 10,
60
+ });
61
+ }
62
+ catch (e) {
63
+ throw new Error(`PostgreSQL driver not found. Install it: npm install pg\n` +
64
+ `Original error: ${e instanceof Error ? e.message : String(e)}`);
65
+ }
66
+ }
67
+ async doDisconnect() {
68
+ if (this.pool) {
69
+ await this.pool.end();
70
+ this.pool = null;
71
+ }
72
+ }
73
+ async doTestConnection() {
74
+ if (!this.pool)
75
+ return false;
76
+ const client = await this.pool.connect();
77
+ try {
78
+ await client.query('SELECT 1');
79
+ return true;
80
+ }
81
+ finally {
82
+ client.release();
83
+ }
84
+ }
85
+ // --- Query execution ---
86
+ async executeQuery(sql, params) {
87
+ if (!this.pool)
88
+ throw new Error('PostgreSQL not connected. Call connect() first.');
89
+ const result = await this.pool.query(sql, params);
90
+ return result.rows;
91
+ }
92
+ async executeRun(sql, params) {
93
+ if (!this.pool)
94
+ throw new Error('PostgreSQL not connected. Call connect() first.');
95
+ const result = await this.pool.query(sql, params);
96
+ return { changes: result.rowCount ?? 0 };
97
+ }
98
+ getDialectLabel() { return 'PostgreSQL'; }
99
+ }
100
+ // ============================================================
101
+ // Factory export
102
+ // ============================================================
103
+ export function createDialect() {
104
+ return new PostgresDialect();
105
+ }
@@ -0,0 +1,2 @@
1
+ import type { IDialect } from '../core/types.js';
2
+ export declare function createDialect(): IDialect;