@promakeai/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.
- package/README.md +154 -0
- package/dist/ORM.d.ts +190 -0
- package/dist/adapters/IDataAdapter.d.ts +134 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +1054 -0
- package/dist/schema/defineSchema.d.ts +50 -0
- package/dist/schema/fieldBuilder.d.ts +152 -0
- package/dist/schema/helpers.d.ts +54 -0
- package/dist/schema/index.d.ts +9 -0
- package/dist/schema/schemaHelpers.d.ts +55 -0
- package/dist/schema/validator.d.ts +45 -0
- package/dist/types.d.ts +192 -0
- package/dist/utils/jsonConverter.d.ts +29 -0
- package/dist/utils/populateResolver.d.ts +57 -0
- package/dist/utils/translationQuery.d.ts +50 -0
- package/dist/utils/whereBuilder.d.ts +38 -0
- package/package.json +48 -0
- package/src/ORM.ts +398 -0
- package/src/adapters/IDataAdapter.ts +196 -0
- package/src/index.ts +148 -0
- package/src/schema/defineSchema.ts +164 -0
- package/src/schema/fieldBuilder.ts +244 -0
- package/src/schema/helpers.ts +171 -0
- package/src/schema/index.ts +47 -0
- package/src/schema/schemaHelpers.ts +123 -0
- package/src/schema/validator.ts +189 -0
- package/src/types.ts +243 -0
- package/src/utils/jsonConverter.ts +94 -0
- package/src/utils/populateResolver.ts +322 -0
- package/src/utils/translationQuery.ts +306 -0
- package/src/utils/whereBuilder.ts +154 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MongoDB-style Where Clause Builder
|
|
3
|
+
*
|
|
4
|
+
* Converts MongoDB/Mongoose-style query objects into SQL WHERE clauses
|
|
5
|
+
* with parameterized values.
|
|
6
|
+
*
|
|
7
|
+
* Supported operators:
|
|
8
|
+
* - Comparison: $eq, $ne, $gt, $gte, $lt, $lte
|
|
9
|
+
* - Array: $in, $nin
|
|
10
|
+
* - String: $like, $notLike
|
|
11
|
+
* - Range: $between
|
|
12
|
+
* - Null: $isNull
|
|
13
|
+
* - Negation: $not (field-level)
|
|
14
|
+
* - Logical: $and, $or, $nor
|
|
15
|
+
*/
|
|
16
|
+
export interface WhereResult {
|
|
17
|
+
sql: string;
|
|
18
|
+
params: unknown[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build a SQL WHERE clause from a MongoDB-style query object.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Simple equality
|
|
25
|
+
* buildWhereClause({ active: 1 })
|
|
26
|
+
* // => { sql: "active = ?", params: [1] }
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // Comparison operators
|
|
30
|
+
* buildWhereClause({ age: { $gte: 18, $lt: 65 } })
|
|
31
|
+
* // => { sql: "(age >= ? AND age < ?)", params: [18, 65] }
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // Logical operators
|
|
35
|
+
* buildWhereClause({ $or: [{ active: 1 }, { role: "admin" }] })
|
|
36
|
+
* // => { sql: "(active = ? OR role = ?)", params: [1, "admin"] }
|
|
37
|
+
*/
|
|
38
|
+
export declare function buildWhereClause(where?: Record<string, unknown>): WhereResult;
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@promakeai/orm",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Database-agnostic ORM core - works in browser and Node.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./schema": {
|
|
16
|
+
"types": "./dist/schema/index.d.ts",
|
|
17
|
+
"import": "./dist/schema/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "bun build src/index.ts --outdir dist --target browser --format esm && bun x tsc --emitDeclarationOnly",
|
|
22
|
+
"dev": "bun build src/index.ts --outdir dist --target browser --format esm --watch",
|
|
23
|
+
"test": "bun test",
|
|
24
|
+
"typecheck": "bun x tsc --noEmit"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"src"
|
|
29
|
+
],
|
|
30
|
+
"keywords": [
|
|
31
|
+
"orm",
|
|
32
|
+
"database",
|
|
33
|
+
"adapter",
|
|
34
|
+
"typescript",
|
|
35
|
+
"browser",
|
|
36
|
+
"nodejs",
|
|
37
|
+
"sql",
|
|
38
|
+
"nosql",
|
|
39
|
+
"rest"
|
|
40
|
+
],
|
|
41
|
+
"author": "Promake Inc.",
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"typescript": "^5.8.0"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/ORM.ts
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ORM Class - Main Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Database-agnostic ORM that works with any adapter implementing IDataAdapter.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { ORM } from '@promakeai/orm';
|
|
9
|
+
* import { SqliteAdapter } from '@promakeai/dbcli';
|
|
10
|
+
*
|
|
11
|
+
* const adapter = new SqliteAdapter('./database.db');
|
|
12
|
+
* const db = new ORM(adapter);
|
|
13
|
+
*
|
|
14
|
+
* // List with filters and populate
|
|
15
|
+
* const posts = await db.list('posts', {
|
|
16
|
+
* where: { published: true },
|
|
17
|
+
* populate: ['userId', 'categoryIds'],
|
|
18
|
+
* orderBy: [{ field: 'created_at', direction: 'DESC' }],
|
|
19
|
+
* limit: 10,
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* // Create
|
|
23
|
+
* const post = await db.create('posts', { title: 'Hello', published: true });
|
|
24
|
+
*
|
|
25
|
+
* // Transaction
|
|
26
|
+
* await db.transaction(async (tx) => {
|
|
27
|
+
* await tx.create('posts', { title: 'Post 1' });
|
|
28
|
+
* await tx.create('posts', { title: 'Post 2' });
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import type { IDataAdapter } from "./adapters/IDataAdapter";
|
|
34
|
+
import type {
|
|
35
|
+
QueryOptions,
|
|
36
|
+
PaginatedResult,
|
|
37
|
+
SchemaDefinition,
|
|
38
|
+
ORMConfig,
|
|
39
|
+
} from "./types";
|
|
40
|
+
import { resolvePopulate, validatePopulate } from "./utils/populateResolver";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Main ORM class - Completely adapter agnostic
|
|
44
|
+
*/
|
|
45
|
+
export class ORM {
|
|
46
|
+
private adapter: IDataAdapter;
|
|
47
|
+
private schema?: SchemaDefinition;
|
|
48
|
+
private defaultLang: string;
|
|
49
|
+
private fallbackLang?: string;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create ORM instance with any adapter
|
|
53
|
+
*
|
|
54
|
+
* @param adapter - Any object implementing IDataAdapter interface
|
|
55
|
+
* @param config - Optional configuration
|
|
56
|
+
*/
|
|
57
|
+
constructor(adapter: IDataAdapter, config?: ORMConfig) {
|
|
58
|
+
this.adapter = adapter;
|
|
59
|
+
this.schema = config?.schema;
|
|
60
|
+
this.defaultLang = config?.defaultLang || "en";
|
|
61
|
+
this.fallbackLang = config?.fallbackLang;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ==================== Query Methods ====================
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* List records from table
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* // Simple list
|
|
72
|
+
* const products = await db.list('products');
|
|
73
|
+
*
|
|
74
|
+
* // With populate
|
|
75
|
+
* const products = await db.list('products', {
|
|
76
|
+
* where: { active: true },
|
|
77
|
+
* populate: ['categoryId', 'brandId'],
|
|
78
|
+
* limit: 10,
|
|
79
|
+
* });
|
|
80
|
+
* // Each product has .category and .brand objects
|
|
81
|
+
*
|
|
82
|
+
* // Nested populate
|
|
83
|
+
* const orders = await db.list('orders', {
|
|
84
|
+
* populate: [
|
|
85
|
+
* 'userId',
|
|
86
|
+
* { path: 'items', populate: ['productId'] }
|
|
87
|
+
* ]
|
|
88
|
+
* });
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
async list<T = unknown>(
|
|
92
|
+
table: string,
|
|
93
|
+
options?: QueryOptions
|
|
94
|
+
): Promise<T[]> {
|
|
95
|
+
const opts = this.normalizeOptions(options);
|
|
96
|
+
const { populate, ...queryOpts } = opts;
|
|
97
|
+
|
|
98
|
+
// Fetch records
|
|
99
|
+
let records = await this.adapter.list<T>(table, queryOpts);
|
|
100
|
+
|
|
101
|
+
// Resolve populate if schema is available
|
|
102
|
+
if (populate && this.schema) {
|
|
103
|
+
records = await this.resolvePopulateForRecords(records, table, populate);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return records;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get single record by ID
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```typescript
|
|
114
|
+
* const product = await db.get('products', 1, {
|
|
115
|
+
* populate: ['categoryId']
|
|
116
|
+
* });
|
|
117
|
+
* console.log(product.category.name);
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
async get<T = unknown>(
|
|
121
|
+
table: string,
|
|
122
|
+
id: string | number,
|
|
123
|
+
options?: QueryOptions
|
|
124
|
+
): Promise<T | null> {
|
|
125
|
+
const opts = this.normalizeOptions(options);
|
|
126
|
+
const { populate, ...queryOpts } = opts;
|
|
127
|
+
|
|
128
|
+
const record = await this.adapter.get<T>(table, id, queryOpts);
|
|
129
|
+
|
|
130
|
+
// Resolve populate if record found and schema is available
|
|
131
|
+
if (record && populate && this.schema) {
|
|
132
|
+
const [populated] = await this.resolvePopulateForRecords(
|
|
133
|
+
[record],
|
|
134
|
+
table,
|
|
135
|
+
populate
|
|
136
|
+
);
|
|
137
|
+
return populated;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return record;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Count records
|
|
145
|
+
*/
|
|
146
|
+
async count(table: string, options?: QueryOptions): Promise<number> {
|
|
147
|
+
const opts = this.normalizeOptions(options);
|
|
148
|
+
return this.adapter.count(table, opts);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Paginated query
|
|
153
|
+
*/
|
|
154
|
+
async paginate<T = unknown>(
|
|
155
|
+
table: string,
|
|
156
|
+
page: number,
|
|
157
|
+
limit: number,
|
|
158
|
+
options?: QueryOptions
|
|
159
|
+
): Promise<PaginatedResult<T>> {
|
|
160
|
+
const opts = this.normalizeOptions(options);
|
|
161
|
+
return this.adapter.paginate<T>(table, page, limit, opts);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ==================== Write Methods ====================
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Create new record
|
|
168
|
+
*/
|
|
169
|
+
async create<T = unknown>(
|
|
170
|
+
table: string,
|
|
171
|
+
data: Record<string, unknown>
|
|
172
|
+
): Promise<T> {
|
|
173
|
+
// Validate if schema exists
|
|
174
|
+
if (this.schema) {
|
|
175
|
+
this.validateData(table, data);
|
|
176
|
+
}
|
|
177
|
+
return this.adapter.create<T>(table, data);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Update record by ID
|
|
182
|
+
*/
|
|
183
|
+
async update<T = unknown>(
|
|
184
|
+
table: string,
|
|
185
|
+
id: string | number,
|
|
186
|
+
data: Record<string, unknown>
|
|
187
|
+
): Promise<T> {
|
|
188
|
+
// Validate if schema exists
|
|
189
|
+
if (this.schema) {
|
|
190
|
+
this.validateData(table, data, true);
|
|
191
|
+
}
|
|
192
|
+
return this.adapter.update<T>(table, id, data);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Delete record by ID
|
|
197
|
+
*/
|
|
198
|
+
async delete(table: string, id: string | number): Promise<boolean> {
|
|
199
|
+
return this.adapter.delete(table, id);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ==================== Batch Methods ====================
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Create multiple records
|
|
206
|
+
*/
|
|
207
|
+
async createMany<T = unknown>(
|
|
208
|
+
table: string,
|
|
209
|
+
records: Record<string, unknown>[],
|
|
210
|
+
options?: { ignore?: boolean }
|
|
211
|
+
): Promise<{ created: number; ids: (number | bigint)[] }> {
|
|
212
|
+
return this.adapter.createMany<T>(table, records, options);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Update multiple records
|
|
217
|
+
*/
|
|
218
|
+
async updateMany(
|
|
219
|
+
table: string,
|
|
220
|
+
updates: { id: number | string; data: Record<string, unknown> }[]
|
|
221
|
+
): Promise<{ updated: number }> {
|
|
222
|
+
return this.adapter.updateMany(table, updates);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Delete multiple records
|
|
227
|
+
*/
|
|
228
|
+
async deleteMany(
|
|
229
|
+
table: string,
|
|
230
|
+
ids: (number | string)[]
|
|
231
|
+
): Promise<{ deleted: number }> {
|
|
232
|
+
return this.adapter.deleteMany(table, ids);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ==================== Raw Query Methods ====================
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Execute raw query
|
|
239
|
+
*/
|
|
240
|
+
async raw<T = unknown>(query: string, params?: unknown[]): Promise<T[]> {
|
|
241
|
+
return this.adapter.raw<T>(query, params);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Execute raw statement
|
|
246
|
+
*/
|
|
247
|
+
async execute(
|
|
248
|
+
query: string,
|
|
249
|
+
params?: unknown[]
|
|
250
|
+
): Promise<{ changes: number; lastInsertRowid: number | bigint }> {
|
|
251
|
+
return this.adapter.execute(query, params);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ==================== Transaction ====================
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Transaction wrapper
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```typescript
|
|
261
|
+
* await db.transaction(async (tx) => {
|
|
262
|
+
* const user = await tx.create('users', { name: 'John' });
|
|
263
|
+
* await tx.create('posts', { title: 'Hello', userId: user.id });
|
|
264
|
+
* });
|
|
265
|
+
* ```
|
|
266
|
+
*/
|
|
267
|
+
async transaction<T>(callback: (orm: ORM) => Promise<T>): Promise<T> {
|
|
268
|
+
await this.adapter.beginTransaction();
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
const result = await callback(this);
|
|
272
|
+
await this.adapter.commit();
|
|
273
|
+
return result;
|
|
274
|
+
} catch (error) {
|
|
275
|
+
await this.adapter.rollback();
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ==================== Schema Methods ====================
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get all table names
|
|
284
|
+
*/
|
|
285
|
+
async getTables(): Promise<string[]> {
|
|
286
|
+
if (this.adapter.getTables) {
|
|
287
|
+
return this.adapter.getTables();
|
|
288
|
+
}
|
|
289
|
+
throw new Error("Adapter does not support getTables()");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get table schema
|
|
294
|
+
*/
|
|
295
|
+
async getTableSchema(table: string): Promise<unknown[]> {
|
|
296
|
+
if (this.adapter.getTableSchema) {
|
|
297
|
+
return this.adapter.getTableSchema(table);
|
|
298
|
+
}
|
|
299
|
+
throw new Error("Adapter does not support getTableSchema()");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ==================== Lifecycle ====================
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Close connection
|
|
306
|
+
*/
|
|
307
|
+
async close(): Promise<void> {
|
|
308
|
+
return this.adapter.close();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ==================== Internal Methods ====================
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Normalize query options
|
|
315
|
+
*/
|
|
316
|
+
private normalizeOptions(options?: QueryOptions): QueryOptions {
|
|
317
|
+
return {
|
|
318
|
+
...options,
|
|
319
|
+
lang: options?.lang || this.defaultLang,
|
|
320
|
+
fallbackLang: options?.fallbackLang || this.fallbackLang,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Validate data against schema
|
|
326
|
+
*/
|
|
327
|
+
private validateData(
|
|
328
|
+
table: string,
|
|
329
|
+
data: Record<string, unknown>,
|
|
330
|
+
isUpdate = false
|
|
331
|
+
): void {
|
|
332
|
+
if (!this.schema) return;
|
|
333
|
+
|
|
334
|
+
const tableDef = this.schema.tables[table];
|
|
335
|
+
if (!tableDef) {
|
|
336
|
+
throw new Error(`Table "${table}" not found in schema`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Skip validation for now - can be implemented with more detailed checks
|
|
340
|
+
// This is a placeholder for future validation logic
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Resolve populate for records
|
|
345
|
+
*/
|
|
346
|
+
private async resolvePopulateForRecords<T>(
|
|
347
|
+
records: T[],
|
|
348
|
+
table: string,
|
|
349
|
+
populate: QueryOptions["populate"]
|
|
350
|
+
): Promise<T[]> {
|
|
351
|
+
if (!this.schema) return records;
|
|
352
|
+
|
|
353
|
+
// Validate populate options
|
|
354
|
+
const validation = validatePopulate(populate, table, this.schema);
|
|
355
|
+
if (!validation.valid) {
|
|
356
|
+
console.warn("Populate validation warnings:", validation.errors);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Create adapter wrapper for populate resolver
|
|
360
|
+
const adapterWrapper = {
|
|
361
|
+
findMany: <R = unknown>(
|
|
362
|
+
t: string,
|
|
363
|
+
opts?: { where?: Record<string, unknown> }
|
|
364
|
+
): Promise<R[]> => {
|
|
365
|
+
return this.adapter.list<R>(t, opts);
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
return resolvePopulate<T>(
|
|
370
|
+
records,
|
|
371
|
+
table,
|
|
372
|
+
populate,
|
|
373
|
+
this.schema,
|
|
374
|
+
adapterWrapper
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Get the underlying adapter (for advanced usage)
|
|
380
|
+
*/
|
|
381
|
+
getAdapter(): IDataAdapter {
|
|
382
|
+
return this.adapter;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Get current schema (if set)
|
|
387
|
+
*/
|
|
388
|
+
getSchema(): SchemaDefinition | undefined {
|
|
389
|
+
return this.schema;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Set schema
|
|
394
|
+
*/
|
|
395
|
+
setSchema(schema: SchemaDefinition): void {
|
|
396
|
+
this.schema = schema;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Adapter Interface
|
|
3
|
+
*
|
|
4
|
+
* All data sources must implement this interface.
|
|
5
|
+
* This is the contract between ORM and adapters.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Built-in multi-language support via lang option
|
|
9
|
+
* - Automatic translation handling for translatable fields
|
|
10
|
+
* - MongoDB-style populate for references
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { QueryOptions, PaginatedResult, SchemaDefinition } from "../types";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generic Data Adapter Interface
|
|
17
|
+
*/
|
|
18
|
+
export interface IDataAdapter {
|
|
19
|
+
/**
|
|
20
|
+
* Schema definition for translation support (optional)
|
|
21
|
+
*/
|
|
22
|
+
schema?: SchemaDefinition;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Default language for translations
|
|
26
|
+
*/
|
|
27
|
+
defaultLang?: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Set schema after construction
|
|
31
|
+
*/
|
|
32
|
+
setSchema?(schema: SchemaDefinition): void;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Initialize the adapter (optional)
|
|
36
|
+
*/
|
|
37
|
+
connect?(): void | Promise<void>;
|
|
38
|
+
|
|
39
|
+
// ==================== Query Methods ====================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* List records from table
|
|
43
|
+
*/
|
|
44
|
+
list<T = unknown>(table: string, options?: QueryOptions): Promise<T[]>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get single record by ID
|
|
48
|
+
*/
|
|
49
|
+
get<T = unknown>(
|
|
50
|
+
table: string,
|
|
51
|
+
id: string | number,
|
|
52
|
+
options?: QueryOptions
|
|
53
|
+
): Promise<T | null>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Count records matching condition
|
|
57
|
+
*/
|
|
58
|
+
count(table: string, options?: QueryOptions): Promise<number>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Query with pagination
|
|
62
|
+
*/
|
|
63
|
+
paginate<T = unknown>(
|
|
64
|
+
table: string,
|
|
65
|
+
page: number,
|
|
66
|
+
limit: number,
|
|
67
|
+
options?: QueryOptions
|
|
68
|
+
): Promise<PaginatedResult<T>>;
|
|
69
|
+
|
|
70
|
+
// ==================== Write Methods ====================
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Create new record
|
|
74
|
+
*/
|
|
75
|
+
create<T = unknown>(table: string, data: Record<string, unknown>): Promise<T>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Update record by ID
|
|
79
|
+
*/
|
|
80
|
+
update<T = unknown>(
|
|
81
|
+
table: string,
|
|
82
|
+
id: string | number,
|
|
83
|
+
data: Record<string, unknown>
|
|
84
|
+
): Promise<T>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Delete record by ID
|
|
88
|
+
*/
|
|
89
|
+
delete(table: string, id: string | number): Promise<boolean>;
|
|
90
|
+
|
|
91
|
+
// ==================== Batch Methods ====================
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Create multiple records
|
|
95
|
+
*/
|
|
96
|
+
createMany<T = unknown>(
|
|
97
|
+
table: string,
|
|
98
|
+
records: Record<string, unknown>[],
|
|
99
|
+
options?: { ignore?: boolean }
|
|
100
|
+
): Promise<{ created: number; ids: (number | bigint)[] }>;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Update multiple records
|
|
104
|
+
*/
|
|
105
|
+
updateMany(
|
|
106
|
+
table: string,
|
|
107
|
+
updates: { id: number | string; data: Record<string, unknown> }[]
|
|
108
|
+
): Promise<{ updated: number }>;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Delete multiple records
|
|
112
|
+
*/
|
|
113
|
+
deleteMany(
|
|
114
|
+
table: string,
|
|
115
|
+
ids: (number | string)[]
|
|
116
|
+
): Promise<{ deleted: number }>;
|
|
117
|
+
|
|
118
|
+
// ==================== Translation Methods ====================
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Create record with translations in a single transaction
|
|
122
|
+
* If translations not provided but data contains translatable fields,
|
|
123
|
+
* those fields are automatically added as defaultLang translation
|
|
124
|
+
*/
|
|
125
|
+
createWithTranslations<T = unknown>(
|
|
126
|
+
table: string,
|
|
127
|
+
data: Record<string, unknown>,
|
|
128
|
+
translations?: Record<string, Record<string, unknown>>
|
|
129
|
+
): Promise<T>;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Update or insert translation for specific language
|
|
133
|
+
*/
|
|
134
|
+
upsertTranslation(
|
|
135
|
+
table: string,
|
|
136
|
+
id: string | number,
|
|
137
|
+
lang: string,
|
|
138
|
+
data: Record<string, unknown>
|
|
139
|
+
): Promise<void>;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get all translations for a record
|
|
143
|
+
*/
|
|
144
|
+
getTranslations<T = unknown>(table: string, id: string | number): Promise<T[]>;
|
|
145
|
+
|
|
146
|
+
// ==================== Raw Query Methods ====================
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Execute raw query (adapter-specific)
|
|
150
|
+
*/
|
|
151
|
+
raw<T = unknown>(query: string, params?: unknown[]): Promise<T[]>;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Execute raw statement (for INSERT/UPDATE/DELETE)
|
|
155
|
+
*/
|
|
156
|
+
execute(
|
|
157
|
+
query: string,
|
|
158
|
+
params?: unknown[]
|
|
159
|
+
): Promise<{ changes: number; lastInsertRowid: number | bigint }>;
|
|
160
|
+
|
|
161
|
+
// ==================== Transaction Methods ====================
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Begin transaction
|
|
165
|
+
*/
|
|
166
|
+
beginTransaction(): Promise<void>;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Commit transaction
|
|
170
|
+
*/
|
|
171
|
+
commit(): Promise<void>;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Rollback transaction
|
|
175
|
+
*/
|
|
176
|
+
rollback(): Promise<void>;
|
|
177
|
+
|
|
178
|
+
// ==================== Schema Methods ====================
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get all table names
|
|
182
|
+
*/
|
|
183
|
+
getTables?(): Promise<string[]>;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get table schema/structure
|
|
187
|
+
*/
|
|
188
|
+
getTableSchema?(table: string): Promise<unknown[]>;
|
|
189
|
+
|
|
190
|
+
// ==================== Lifecycle ====================
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Close connection
|
|
194
|
+
*/
|
|
195
|
+
close(): void | Promise<void>;
|
|
196
|
+
}
|