@zhin.js/database 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/LICENSE +21 -0
- package/README.md +93 -0
- package/lib/base/database.d.ts +71 -0
- package/lib/base/database.d.ts.map +1 -0
- package/lib/base/database.js +101 -0
- package/lib/base/database.js.map +1 -0
- package/lib/base/dialect.d.ts +34 -0
- package/lib/base/dialect.d.ts.map +1 -0
- package/lib/base/dialect.js +21 -0
- package/lib/base/dialect.js.map +1 -0
- package/lib/base/index.d.ts +6 -0
- package/lib/base/index.d.ts.map +1 -0
- package/lib/base/index.js +6 -0
- package/lib/base/index.js.map +1 -0
- package/lib/base/model.d.ts +53 -0
- package/lib/base/model.d.ts.map +1 -0
- package/lib/base/model.js +65 -0
- package/lib/base/model.js.map +1 -0
- package/lib/base/query-classes.d.ts +68 -0
- package/lib/base/query-classes.d.ts.map +1 -0
- package/lib/base/query-classes.js +178 -0
- package/lib/base/query-classes.js.map +1 -0
- package/lib/base/thenable.d.ts +15 -0
- package/lib/base/thenable.d.ts.map +1 -0
- package/lib/base/thenable.js +33 -0
- package/lib/base/thenable.js.map +1 -0
- package/lib/dialects/memory.d.ts +52 -0
- package/lib/dialects/memory.d.ts.map +1 -0
- package/lib/dialects/memory.js +772 -0
- package/lib/dialects/memory.js.map +1 -0
- package/lib/dialects/mongodb.d.ts +85 -0
- package/lib/dialects/mongodb.d.ts.map +1 -0
- package/lib/dialects/mongodb.js +461 -0
- package/lib/dialects/mongodb.js.map +1 -0
- package/lib/dialects/mysql.d.ts +33 -0
- package/lib/dialects/mysql.d.ts.map +1 -0
- package/lib/dialects/mysql.js +132 -0
- package/lib/dialects/mysql.js.map +1 -0
- package/lib/dialects/pg.d.ts +33 -0
- package/lib/dialects/pg.d.ts.map +1 -0
- package/lib/dialects/pg.js +132 -0
- package/lib/dialects/pg.js.map +1 -0
- package/lib/dialects/redis.d.ts +87 -0
- package/lib/dialects/redis.d.ts.map +1 -0
- package/lib/dialects/redis.js +500 -0
- package/lib/dialects/redis.js.map +1 -0
- package/lib/dialects/sqlite.d.ts +46 -0
- package/lib/dialects/sqlite.d.ts.map +1 -0
- package/lib/dialects/sqlite.js +201 -0
- package/lib/dialects/sqlite.js.map +1 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +18 -0
- package/lib/index.js.map +1 -0
- package/lib/registry.d.ts +37 -0
- package/lib/registry.d.ts.map +1 -0
- package/lib/registry.js +21 -0
- package/lib/registry.js.map +1 -0
- package/lib/type/document/database.d.ts +60 -0
- package/lib/type/document/database.d.ts.map +1 -0
- package/lib/type/document/database.js +242 -0
- package/lib/type/document/database.js.map +1 -0
- package/lib/type/document/model.d.ts +42 -0
- package/lib/type/document/model.d.ts.map +1 -0
- package/lib/type/document/model.js +65 -0
- package/lib/type/document/model.js.map +1 -0
- package/lib/type/keyvalue/database.d.ts +64 -0
- package/lib/type/keyvalue/database.d.ts.map +1 -0
- package/lib/type/keyvalue/database.js +214 -0
- package/lib/type/keyvalue/database.js.map +1 -0
- package/lib/type/keyvalue/model.d.ts +107 -0
- package/lib/type/keyvalue/model.d.ts.map +1 -0
- package/lib/type/keyvalue/model.js +261 -0
- package/lib/type/keyvalue/model.js.map +1 -0
- package/lib/type/related/database.d.ts +32 -0
- package/lib/type/related/database.d.ts.map +1 -0
- package/lib/type/related/database.js +334 -0
- package/lib/type/related/database.js.map +1 -0
- package/lib/type/related/model.d.ts +43 -0
- package/lib/type/related/model.d.ts.map +1 -0
- package/lib/type/related/model.js +108 -0
- package/lib/type/related/model.js.map +1 -0
- package/lib/types.d.ts +251 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +28 -0
- package/lib/types.js.map +1 -0
- package/package.json +54 -0
- package/src/base/database.ts +128 -0
- package/src/base/dialect.ts +76 -0
- package/src/base/index.ts +5 -0
- package/src/base/model.ts +89 -0
- package/src/base/query-classes.ts +217 -0
- package/src/base/thenable.ts +54 -0
- package/src/dialects/memory.ts +880 -0
- package/src/dialects/mongodb.ts +533 -0
- package/src/dialects/mysql.ts +157 -0
- package/src/dialects/pg.ts +157 -0
- package/src/dialects/redis.ts +598 -0
- package/src/dialects/sqlite.ts +233 -0
- package/src/index.ts +20 -0
- package/src/registry.ts +59 -0
- package/src/type/document/database.ts +283 -0
- package/src/type/document/model.ts +86 -0
- package/src/type/keyvalue/database.ts +261 -0
- package/src/type/keyvalue/model.ts +339 -0
- package/src/type/related/database.ts +392 -0
- package/src/type/related/model.ts +117 -0
- package/src/types.ts +403 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import {Dialect} from "../base";
|
|
2
|
+
import {Registry} from "../registry";
|
|
3
|
+
import {Database} from "../base";
|
|
4
|
+
import {Column} from "../types";
|
|
5
|
+
import {RelatedDatabase} from "../type/related/database";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export interface SQLiteDialectConfig {
|
|
9
|
+
filename: string;
|
|
10
|
+
mode?:string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class SQLiteDialect extends Dialect<SQLiteDialectConfig, string> {
|
|
14
|
+
private db: any = null;
|
|
15
|
+
|
|
16
|
+
constructor(config: SQLiteDialectConfig) {
|
|
17
|
+
super('sqlite', config);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Connection management
|
|
21
|
+
isConnected(): boolean {
|
|
22
|
+
return this.db !== null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async connect(): Promise<void> {
|
|
26
|
+
try {
|
|
27
|
+
const { default: sqlite3 } = await import('sqlite3');
|
|
28
|
+
this.db = new sqlite3.Database(this.config.filename);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('forgot install sqlite3 ?');
|
|
31
|
+
throw new Error(`SQLite 连接失败: ${error}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async disconnect(): Promise<void> {
|
|
36
|
+
this.db = null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async healthCheck(): Promise<boolean> {
|
|
40
|
+
return this.isConnected();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async query<U = any>(sql: string, params?: any[]): Promise<U> {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
this.db.all(sql, params, (err: any, rows: any) => {
|
|
46
|
+
if (err) {
|
|
47
|
+
reject(err);
|
|
48
|
+
} else {
|
|
49
|
+
// 对查询结果进行后处理,移除多余的引号
|
|
50
|
+
const processedRows = this.processQueryResults(rows);
|
|
51
|
+
resolve(processedRows as U);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
this.db.get(sql, params, (err: any, row: any) => {
|
|
55
|
+
if (err) {
|
|
56
|
+
reject(err);
|
|
57
|
+
} else {
|
|
58
|
+
// 对单行结果进行后处理
|
|
59
|
+
const processedRow = this.processQueryResults(row);
|
|
60
|
+
resolve(processedRow as U);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 处理查询结果,移除字符串字段的多余引号
|
|
68
|
+
*/
|
|
69
|
+
private processQueryResults(data: any): any {
|
|
70
|
+
if (!data) return data;
|
|
71
|
+
|
|
72
|
+
if (Array.isArray(data)) {
|
|
73
|
+
return data.map(row => this.processRowData(row));
|
|
74
|
+
} else if (typeof data === 'object') {
|
|
75
|
+
return this.processRowData(data);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return data;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 处理单行数据
|
|
83
|
+
*/
|
|
84
|
+
private processRowData(row: any): any {
|
|
85
|
+
if (!row || typeof row !== 'object') return row;
|
|
86
|
+
|
|
87
|
+
const processedRow: any = {};
|
|
88
|
+
|
|
89
|
+
for (const [key, value] of Object.entries(row)) {
|
|
90
|
+
processedRow[key] = this.processFieldValue(value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return processedRow;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 处理字段值,移除多余的引号并解析 JSON
|
|
98
|
+
*/
|
|
99
|
+
private processFieldValue(value: any): any {
|
|
100
|
+
if (typeof value !== 'string') return value;
|
|
101
|
+
|
|
102
|
+
// 移除字符串两端的引号(如果存在)
|
|
103
|
+
if ((value.startsWith("'") && value.endsWith("'")) ||
|
|
104
|
+
(value.startsWith('"') && value.endsWith('"'))) {
|
|
105
|
+
const unquoted = value.slice(1, -1);
|
|
106
|
+
|
|
107
|
+
// 尝试解析为 JSON(用于 json 类型字段)
|
|
108
|
+
if (unquoted.startsWith('{') || unquoted.startsWith('[')) {
|
|
109
|
+
try {
|
|
110
|
+
return JSON.parse(unquoted);
|
|
111
|
+
} catch {
|
|
112
|
+
// 如果解析失败,返回去除引号的字符串
|
|
113
|
+
return unquoted;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return unquoted;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async dispose(): Promise<void> {
|
|
124
|
+
if (this.db) {
|
|
125
|
+
await this.db.close();
|
|
126
|
+
this.db = null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// SQL generation methods
|
|
131
|
+
mapColumnType(type: string): string {
|
|
132
|
+
const typeMap: Record<string, string> = {
|
|
133
|
+
'text': 'TEXT',
|
|
134
|
+
'integer': 'INTEGER',
|
|
135
|
+
'float': 'REAL',
|
|
136
|
+
'boolean': 'INTEGER',
|
|
137
|
+
'date': 'TEXT',
|
|
138
|
+
'json': 'TEXT'
|
|
139
|
+
};
|
|
140
|
+
return typeMap[type.toLowerCase()] || 'TEXT';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
quoteIdentifier(identifier: string): string {
|
|
144
|
+
return `"${identifier}"`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getParameterPlaceholder(index: number): string {
|
|
148
|
+
return '?';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
getStatementTerminator(): string {
|
|
152
|
+
return ';';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
formatBoolean(value: boolean): string {
|
|
156
|
+
return value ? '1' : '0';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
formatDate(value: Date): string {
|
|
160
|
+
return `'${value.toISOString()}'`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
formatJson(value: any): string {
|
|
164
|
+
return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
escapeString(value: string): string {
|
|
168
|
+
return value.replace(/'/g, "''");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
formatDefaultValue(value: any): string {
|
|
172
|
+
if (typeof value === 'string') {
|
|
173
|
+
return `'${this.escapeString(value)}'`;
|
|
174
|
+
} else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
175
|
+
return value.toString();
|
|
176
|
+
} else if (value instanceof Date) {
|
|
177
|
+
return this.formatDate(value);
|
|
178
|
+
} else if (value === null) {
|
|
179
|
+
return 'NULL';
|
|
180
|
+
} else if (typeof value === 'object') {
|
|
181
|
+
return this.formatJson(value);
|
|
182
|
+
} else {
|
|
183
|
+
throw new Error(`Unsupported default value type: ${typeof value}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
formatLimit(limit: number): string {
|
|
188
|
+
return `LIMIT ${limit}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
formatOffset(offset: number): string {
|
|
192
|
+
return `OFFSET ${offset}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
formatLimitOffset(limit: number, offset: number): string {
|
|
196
|
+
return `LIMIT ${limit} OFFSET ${offset}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
formatCreateTable(tableName: string, columns: string[]): string {
|
|
200
|
+
return `CREATE TABLE IF NOT EXISTS ${this.quoteIdentifier(tableName)} (${columns.join(', ')})`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
formatColumnDefinition(field: string, column: Column<any>): string {
|
|
204
|
+
const name = this.quoteIdentifier(String(field));
|
|
205
|
+
const type = this.mapColumnType(column.type);
|
|
206
|
+
const length = column.length ? `(${column.length})` : '';
|
|
207
|
+
const nullable = column.nullable === false ? ' NOT NULL' : '';
|
|
208
|
+
const primary = column.primary ? ' PRIMARY KEY' : '';
|
|
209
|
+
const unique = column.unique ? ' UNIQUE' : '';
|
|
210
|
+
const defaultVal = column.default !== undefined
|
|
211
|
+
? ` DEFAULT ${this.formatDefaultValue(column.default)}`
|
|
212
|
+
: '';
|
|
213
|
+
|
|
214
|
+
return `${name} ${type}${length}${primary}${unique}${nullable}${defaultVal}`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
formatAlterTable(tableName: string, alterations: string[]): string {
|
|
218
|
+
return `ALTER TABLE ${this.quoteIdentifier(tableName)} ${alterations.join(', ')}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
formatDropTable(tableName: string, ifExists?: boolean): string {
|
|
222
|
+
const ifExistsClause = ifExists ? 'IF EXISTS ' : '';
|
|
223
|
+
return `DROP TABLE ${ifExistsClause}${this.quoteIdentifier(tableName)}`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
formatDropIndex(indexName: string, tableName: string, ifExists?: boolean): string {
|
|
227
|
+
const ifExistsClause = ifExists ? 'IF EXISTS ' : '';
|
|
228
|
+
return `DROP INDEX ${ifExistsClause}${this.quoteIdentifier(indexName)}`;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
Registry.register('sqlite', (config: SQLiteDialectConfig, schemas?: Database.Schemas<Record<string, object>>) => {
|
|
232
|
+
return new RelatedDatabase(new SQLiteDialect(config), schemas);
|
|
233
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './base/model.js';
|
|
3
|
+
export * from './base/dialect.js';
|
|
4
|
+
export * from './base/database.js';
|
|
5
|
+
export * from './type/related/database.js';
|
|
6
|
+
export * from './type/related/model.js';
|
|
7
|
+
export * from './type/document/database.js';
|
|
8
|
+
export * from './type/document/model.js';
|
|
9
|
+
export * from './type/keyvalue/database.js';
|
|
10
|
+
export * from './type/keyvalue/model.js';
|
|
11
|
+
export * from './registry.js';
|
|
12
|
+
export * from './dialects/memory.js';
|
|
13
|
+
export * from './dialects/mysql.js';
|
|
14
|
+
export * from './dialects/pg.js';
|
|
15
|
+
export * from './dialects/redis.js';
|
|
16
|
+
export * from './dialects/sqlite.js';
|
|
17
|
+
export * from './dialects/mongodb.js';
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Database } from './base/database.js';
|
|
2
|
+
import type {MongoDBDialectConfig} from './dialects/mongodb';
|
|
3
|
+
import {RelatedDatabase} from "./type/related/database";
|
|
4
|
+
import {MemoryConfig} from "./types";
|
|
5
|
+
import {DocumentDatabase} from "./type/document/database";
|
|
6
|
+
import {MySQLDialectConfig} from "./dialects/mysql";
|
|
7
|
+
import {PostgreSQLDialectConfig} from "./dialects/pg";
|
|
8
|
+
import {RedisDialectConfig} from "./dialects/redis";
|
|
9
|
+
import {SQLiteDialectConfig} from "./dialects/sqlite";
|
|
10
|
+
import {KeyValueDatabase} from "./type/keyvalue/database";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 数据库注册表接口
|
|
14
|
+
* 支持模块声明扩展
|
|
15
|
+
*/
|
|
16
|
+
export interface Databases<S extends Record<string, object> = Record<string, object>> {
|
|
17
|
+
memory: RelatedDatabase<MemoryConfig,S>;
|
|
18
|
+
mongodb: DocumentDatabase<MongoDBDialectConfig,S>;
|
|
19
|
+
mysql: RelatedDatabase<MySQLDialectConfig,S>;
|
|
20
|
+
pg: RelatedDatabase<PostgreSQLDialectConfig,S>;
|
|
21
|
+
redis: KeyValueDatabase<RedisDialectConfig,S>;
|
|
22
|
+
sqlite: RelatedDatabase<SQLiteDialectConfig>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 数据库注册表命名空间
|
|
27
|
+
*/
|
|
28
|
+
export type Creator<D, S extends Record<string, object>> = (config: D, schemas?: Database.Schemas<S>) => Database<any, S, any>;
|
|
29
|
+
export type Constructor<D, S extends Record<string, object>> = new (config: D, schemas?: Database.Schemas<S>) => Database<any, S, any>;
|
|
30
|
+
export type Factory<D, S extends Record<string, object>> = Creator<any, S> | Constructor<any, S>;
|
|
31
|
+
|
|
32
|
+
export namespace Registry {
|
|
33
|
+
export const factories=new Map();
|
|
34
|
+
export type Config<T extends Database<any, any, any>> = T extends Database<infer D, any, any> ? D: any;
|
|
35
|
+
export type DatabaseType = 'related' | 'document' | 'keyvalue';
|
|
36
|
+
|
|
37
|
+
export function register<D extends string, S extends Record<string, object>>(
|
|
38
|
+
dialect: D,
|
|
39
|
+
factory: Factory<any, S>
|
|
40
|
+
): void {
|
|
41
|
+
factories.set(dialect, factory as Factory<any, S>);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function create<D extends string, S extends Record<string, object>>(
|
|
45
|
+
dialect: D,
|
|
46
|
+
config: any,
|
|
47
|
+
schemas?: Database.Schemas<S>
|
|
48
|
+
): any {
|
|
49
|
+
const factory = factories.get(dialect) as Factory<any, S> | undefined;
|
|
50
|
+
if (!factory) {
|
|
51
|
+
throw new Error(`database dialect ${dialect} not registered`);
|
|
52
|
+
}
|
|
53
|
+
return (isConstructor(factory) ? new factory(config, schemas) : factory(config, schemas)) as any;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isConstructor<D, S extends Record<string, object>>(fn: Factory<D, S>): fn is Constructor<D, S> {
|
|
57
|
+
return fn.prototype && fn.prototype.constructor === fn;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { Database, Dialect } from '../../base';
|
|
2
|
+
import { DocumentModel } from './model.js';
|
|
3
|
+
import {
|
|
4
|
+
QueryParams,
|
|
5
|
+
BuildQueryResult,
|
|
6
|
+
DocumentQueryResult,
|
|
7
|
+
CreateQueryParams,
|
|
8
|
+
SelectQueryParams,
|
|
9
|
+
InsertQueryParams,
|
|
10
|
+
UpdateQueryParams,
|
|
11
|
+
DeleteQueryParams,
|
|
12
|
+
AlterQueryParams,
|
|
13
|
+
DropTableQueryParams,
|
|
14
|
+
DropIndexQueryParams
|
|
15
|
+
} from '../../types.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 文档型数据库类
|
|
19
|
+
* 支持集合、文档的文档型数据模型
|
|
20
|
+
*/
|
|
21
|
+
export class DocumentDatabase<
|
|
22
|
+
D = any,
|
|
23
|
+
S extends Record<string, object> = Record<string, object>
|
|
24
|
+
> extends Database<D, S, DocumentQueryResult> {
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
dialect: Dialect<D,DocumentQueryResult>,
|
|
28
|
+
schemas?: Database.Schemas<S>,
|
|
29
|
+
) {
|
|
30
|
+
super(dialect, schemas);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
protected async initialize(): Promise<void> {
|
|
34
|
+
// 文档数据库不需要预定义表结构
|
|
35
|
+
// 集合会在第一次使用时自动创建
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 构建查询(重写基类方法)
|
|
40
|
+
*/
|
|
41
|
+
buildQuery<U extends object = any>(params: QueryParams<U>): BuildQueryResult<DocumentQueryResult> {
|
|
42
|
+
switch (params.type) {
|
|
43
|
+
case 'create':
|
|
44
|
+
return this.buildCreateQuery(params as CreateQueryParams<U>);
|
|
45
|
+
case 'select':
|
|
46
|
+
return this.buildSelectQuery(params as SelectQueryParams<U>);
|
|
47
|
+
case 'insert':
|
|
48
|
+
return this.buildInsertQuery(params as InsertQueryParams<U>);
|
|
49
|
+
case 'update':
|
|
50
|
+
return this.buildUpdateQuery(params as UpdateQueryParams<U>);
|
|
51
|
+
case 'delete':
|
|
52
|
+
return this.buildDeleteQuery(params as DeleteQueryParams<U>);
|
|
53
|
+
case 'alter':
|
|
54
|
+
return this.buildAlterQuery(params as AlterQueryParams<U>);
|
|
55
|
+
case 'drop_table':
|
|
56
|
+
return this.buildDropTableQuery(params as DropTableQueryParams<U>);
|
|
57
|
+
case 'drop_index':
|
|
58
|
+
return this.buildDropIndexQuery(params as DropIndexQueryParams);
|
|
59
|
+
default:
|
|
60
|
+
throw new Error(`Unsupported query type: ${(params as any).type}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 构建创建集合查询
|
|
66
|
+
*/
|
|
67
|
+
protected buildCreateQuery<T extends object>(params: CreateQueryParams<T>): BuildQueryResult<DocumentQueryResult> {
|
|
68
|
+
return {
|
|
69
|
+
query: {
|
|
70
|
+
collection: params.tableName,
|
|
71
|
+
filter: {},
|
|
72
|
+
projection: {}
|
|
73
|
+
},
|
|
74
|
+
params: []
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 构建查询文档查询
|
|
80
|
+
*/
|
|
81
|
+
protected buildSelectQuery<T extends object>(params: SelectQueryParams<T>): BuildQueryResult<DocumentQueryResult> {
|
|
82
|
+
const filter: Record<string, any> = {};
|
|
83
|
+
|
|
84
|
+
// 转换条件为文档查询格式
|
|
85
|
+
if (params.conditions) {
|
|
86
|
+
this.convertConditionToFilter(params.conditions, filter);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const query: DocumentQueryResult = {
|
|
90
|
+
collection: params.tableName,
|
|
91
|
+
filter
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (params.orderings) {
|
|
95
|
+
query.sort = {};
|
|
96
|
+
for (const order of params.orderings) {
|
|
97
|
+
query.sort[order.field as string] = order.direction === 'ASC' ? 1 : -1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (params.limitCount) {
|
|
102
|
+
query.limit = params.limitCount;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (params.offsetCount) {
|
|
106
|
+
query.skip = params.offsetCount;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (params.fields && params.fields.length > 0) {
|
|
110
|
+
query.projection = {};
|
|
111
|
+
for (const field of params.fields) {
|
|
112
|
+
query.projection[field as string] = 1;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
query,
|
|
118
|
+
params: []
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 构建插入文档查询
|
|
124
|
+
*/
|
|
125
|
+
protected buildInsertQuery<T extends object>(params: InsertQueryParams<T>): BuildQueryResult<DocumentQueryResult> {
|
|
126
|
+
return {
|
|
127
|
+
query: {
|
|
128
|
+
collection: params.tableName,
|
|
129
|
+
filter: {},
|
|
130
|
+
projection: {}
|
|
131
|
+
},
|
|
132
|
+
params: [params.data]
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 构建更新文档查询
|
|
138
|
+
*/
|
|
139
|
+
protected buildUpdateQuery<T extends object>(params: UpdateQueryParams<T>): BuildQueryResult<DocumentQueryResult> {
|
|
140
|
+
const filter: Record<string, any> = {};
|
|
141
|
+
|
|
142
|
+
if (params.conditions) {
|
|
143
|
+
this.convertConditionToFilter(params.conditions, filter);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
query: {
|
|
148
|
+
collection: params.tableName,
|
|
149
|
+
filter,
|
|
150
|
+
projection: {}
|
|
151
|
+
},
|
|
152
|
+
params: [params.update]
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 构建删除文档查询
|
|
158
|
+
*/
|
|
159
|
+
protected buildDeleteQuery<T extends object>(params: DeleteQueryParams<T>): BuildQueryResult<DocumentQueryResult> {
|
|
160
|
+
const filter: Record<string, any> = {};
|
|
161
|
+
|
|
162
|
+
if (params.conditions) {
|
|
163
|
+
this.convertConditionToFilter(params.conditions, filter);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
query: {
|
|
168
|
+
collection: params.tableName,
|
|
169
|
+
filter,
|
|
170
|
+
projection: {}
|
|
171
|
+
},
|
|
172
|
+
params: []
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 构建修改集合查询
|
|
178
|
+
*/
|
|
179
|
+
protected buildAlterQuery<T extends object>(params: AlterQueryParams<T>): BuildQueryResult<DocumentQueryResult> {
|
|
180
|
+
return {
|
|
181
|
+
query: {
|
|
182
|
+
collection: params.tableName,
|
|
183
|
+
filter: {},
|
|
184
|
+
projection: {}
|
|
185
|
+
},
|
|
186
|
+
params: [params.alterations]
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 构建删除集合查询
|
|
192
|
+
*/
|
|
193
|
+
protected buildDropTableQuery<T extends object>(params: DropTableQueryParams<T>): BuildQueryResult<DocumentQueryResult> {
|
|
194
|
+
return {
|
|
195
|
+
query: {
|
|
196
|
+
collection: params.tableName,
|
|
197
|
+
filter: {},
|
|
198
|
+
projection: {}
|
|
199
|
+
},
|
|
200
|
+
params: []
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 构建删除索引查询
|
|
206
|
+
*/
|
|
207
|
+
protected buildDropIndexQuery(params: DropIndexQueryParams): BuildQueryResult<DocumentQueryResult> {
|
|
208
|
+
return {
|
|
209
|
+
query: {
|
|
210
|
+
collection: params.tableName,
|
|
211
|
+
filter: {},
|
|
212
|
+
projection: {}
|
|
213
|
+
},
|
|
214
|
+
params: [params.indexName]
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 转换条件为文档查询格式
|
|
220
|
+
*/
|
|
221
|
+
private convertConditionToFilter(condition: any, filter: Record<string, any>): void {
|
|
222
|
+
if (typeof condition !== 'object' || condition === null) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
for (const [key, value] of Object.entries(condition)) {
|
|
227
|
+
if (key.startsWith('$')) {
|
|
228
|
+
// 逻辑操作符
|
|
229
|
+
if (key === '$and' && Array.isArray(value)) {
|
|
230
|
+
filter.$and = value.map(cond => {
|
|
231
|
+
const subFilter: Record<string, any> = {};
|
|
232
|
+
this.convertConditionToFilter(cond, subFilter);
|
|
233
|
+
return subFilter;
|
|
234
|
+
});
|
|
235
|
+
} else if (key === '$or' && Array.isArray(value)) {
|
|
236
|
+
filter.$or = value.map(cond => {
|
|
237
|
+
const subFilter: Record<string, any> = {};
|
|
238
|
+
this.convertConditionToFilter(cond, subFilter);
|
|
239
|
+
return subFilter;
|
|
240
|
+
});
|
|
241
|
+
} else if (key === '$not') {
|
|
242
|
+
const subFilter: Record<string, any> = {};
|
|
243
|
+
this.convertConditionToFilter(value, subFilter);
|
|
244
|
+
filter.$not = subFilter;
|
|
245
|
+
}
|
|
246
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
247
|
+
// 比较操作符
|
|
248
|
+
const fieldFilter: Record<string, any> = {};
|
|
249
|
+
for (const [op, opValue] of Object.entries(value)) {
|
|
250
|
+
if (op.startsWith('$')) {
|
|
251
|
+
fieldFilter[op] = opValue;
|
|
252
|
+
} else {
|
|
253
|
+
fieldFilter.$eq = value;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
filter[key] = Object.keys(fieldFilter).length > 0 ? fieldFilter : value;
|
|
257
|
+
} else {
|
|
258
|
+
// 简单相等
|
|
259
|
+
filter[key] = { $eq: value };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* 获取模型
|
|
266
|
+
*/
|
|
267
|
+
model<T extends keyof S>(name: T): DocumentModel<S[T], D> {
|
|
268
|
+
let model = this.models.get(name as string);
|
|
269
|
+
if (!model) {
|
|
270
|
+
model = new DocumentModel(this as unknown as DocumentDatabase<D>, name as string);
|
|
271
|
+
this.models.set(name as string, model);
|
|
272
|
+
}
|
|
273
|
+
return model as unknown as DocumentModel<S[T], D>;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* 获取所有模型名称
|
|
278
|
+
*/
|
|
279
|
+
getModelNames(): string[] {
|
|
280
|
+
return Object.keys(this.schemas || {});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Model} from '../../base';
|
|
2
|
+
import { DocumentDatabase } from './database.js';
|
|
3
|
+
import { DocumentQueryResult, Condition } from '../../types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 文档型模型类
|
|
7
|
+
* 继承自 Model,提供文档型数据库特有的操作
|
|
8
|
+
*/
|
|
9
|
+
export class DocumentModel<T extends object = object, D =any> extends Model<D, T, DocumentQueryResult> {
|
|
10
|
+
constructor(
|
|
11
|
+
database: DocumentDatabase<D>,
|
|
12
|
+
name: string
|
|
13
|
+
) {
|
|
14
|
+
super(database, name);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 创建文档
|
|
19
|
+
*/
|
|
20
|
+
async create(document: T): Promise<T & { _id: string }>;
|
|
21
|
+
async create(documents: T[]): Promise<(T & { _id: string })[]>;
|
|
22
|
+
async create(documents: T | T[]): Promise<(T & { _id: string }) | (T & { _id: string })[]> {
|
|
23
|
+
const isArray = Array.isArray(documents);
|
|
24
|
+
const docs = isArray ? documents : [documents];
|
|
25
|
+
|
|
26
|
+
const results = [];
|
|
27
|
+
for (const doc of docs) {
|
|
28
|
+
const _id = this.generateId();
|
|
29
|
+
const docWithId = { ...doc, _id };
|
|
30
|
+
|
|
31
|
+
await this.dialect.query(
|
|
32
|
+
{
|
|
33
|
+
operation: 'insertOne',
|
|
34
|
+
filter: {},
|
|
35
|
+
projection: {},
|
|
36
|
+
collection: this.name,
|
|
37
|
+
},
|
|
38
|
+
[docWithId]
|
|
39
|
+
);
|
|
40
|
+
results.push(docWithId);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return isArray ? results : results[0];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 查找单个文档
|
|
48
|
+
*/
|
|
49
|
+
async selectOne<K extends keyof T>(...fields: Array<K>): Promise<(Pick<T, K> & { _id: string }) | null> {
|
|
50
|
+
const results = await this.select(...fields).limit(1);
|
|
51
|
+
return results.length > 0 ? results[0] as (Pick<T, K> & { _id: string }) : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 根据ID查找文档
|
|
56
|
+
*/
|
|
57
|
+
async selectById(id: string){
|
|
58
|
+
return this.select('_id' as any).where({
|
|
59
|
+
_id: id,
|
|
60
|
+
}).limit(1).then((results: any) => results[0] || null);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 根据ID更新文档
|
|
64
|
+
*/
|
|
65
|
+
async updateById(id: string, update: Partial<T>): Promise<number> {
|
|
66
|
+
return this.update(update).where({
|
|
67
|
+
_id: id,
|
|
68
|
+
} as Condition<T>)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 根据ID删除文档
|
|
73
|
+
*/
|
|
74
|
+
async deleteById(id: string): Promise<number> {
|
|
75
|
+
return this.delete({
|
|
76
|
+
_id: id,
|
|
77
|
+
} as Condition<T>)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 生成文档ID
|
|
82
|
+
*/
|
|
83
|
+
private generateId(): string {
|
|
84
|
+
return Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
|
|
85
|
+
}
|
|
86
|
+
}
|