@cheetah.js/orm 0.1.142 → 0.1.144
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 +55 -7
- package/dist/decorators/index.decorator.d.ts +12 -0
- package/dist/decorators/index.decorator.js +2 -1
- package/dist/domain/entities.js +65 -30
- package/dist/driver/bun-mysql.driver.d.ts +2 -5
- package/dist/driver/bun-mysql.driver.js +9 -1
- package/dist/driver/bun-pg.driver.d.ts +2 -5
- package/dist/driver/bun-pg.driver.js +7 -1
- package/dist/driver/driver.interface.d.ts +8 -8
- package/dist/query/index-condition-builder.d.ts +41 -0
- package/dist/query/index-condition-builder.js +235 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -124,7 +124,7 @@ export class User {
|
|
|
124
124
|
}
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
For define a index for a multiple properties, add the @Index decorator. You can use it on a property or on the class. It accepts either an array of property names (legacy) or an object with a properties field (recommended):
|
|
127
|
+
For define a index for a multiple properties, add the @Index decorator. You can use it on a property or on the class. It accepts either an array of property names (legacy) or an object with a properties field (recommended):
|
|
128
128
|
|
|
129
129
|
```javascript
|
|
130
130
|
@Entity()
|
|
@@ -156,12 +156,60 @@ export class User {
|
|
|
156
156
|
email: string;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
// Backward compatible usage (array):
|
|
160
|
-
// @Index(['name', 'email'])
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
159
|
+
// Backward compatible usage (array):
|
|
160
|
+
// @Index(['name', 'email'])
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Partial indexes (Postgres only) can be declared with `where`. You can provide a raw SQL string or a typed callback that receives the column map (with autocomplete based on your entity):
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
@Entity()
|
|
167
|
+
@Index<{ User }>({
|
|
168
|
+
properties: ['email'],
|
|
169
|
+
where: (columns) => `${columns.isActive} = true`,
|
|
170
|
+
})
|
|
171
|
+
export class User {
|
|
172
|
+
@PrimaryKey()
|
|
173
|
+
id: number;
|
|
174
|
+
|
|
175
|
+
@Property()
|
|
176
|
+
email: string;
|
|
177
|
+
|
|
178
|
+
@Property()
|
|
179
|
+
isActive: boolean;
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
You can also use the ORM filter syntax in `where` (with `$in`, `$or`, `$nor`, etc.):
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
@Entity()
|
|
187
|
+
@Index<{ User }>({
|
|
188
|
+
properties: ['email'],
|
|
189
|
+
where: {
|
|
190
|
+
isActive: true,
|
|
191
|
+
status: { $in: ['active', 'pending'] },
|
|
192
|
+
},
|
|
193
|
+
})
|
|
194
|
+
export class User {
|
|
195
|
+
@PrimaryKey()
|
|
196
|
+
id: number;
|
|
197
|
+
|
|
198
|
+
@Property()
|
|
199
|
+
email: string;
|
|
200
|
+
|
|
201
|
+
@Property()
|
|
202
|
+
isActive: boolean;
|
|
203
|
+
|
|
204
|
+
@Property()
|
|
205
|
+
status: string;
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Note: MySQL does not support partial indexes; using `where` with the MySQL driver will throw.
|
|
210
|
+
|
|
211
|
+
#### Property options
|
|
212
|
+
| Option | Type | Description |
|
|
165
213
|
| ------ | ---- |--------------------------------------------------------------------------------------------|
|
|
166
214
|
| nullable | boolean | Defines if the property is nullable. |
|
|
167
215
|
| unique | boolean | Defines if the property is unique. |
|
|
@@ -1,5 +1,17 @@
|
|
|
1
|
+
import { FilterQuery } from "../driver/driver.interface";
|
|
2
|
+
export type IndexColumnMap<T> = {
|
|
3
|
+
[K in keyof T as K extends symbol ? never : K]: string;
|
|
4
|
+
};
|
|
5
|
+
export type IndexPredicate<T> = string | ((columns: IndexColumnMap<T>) => string);
|
|
6
|
+
export type IndexWhere<T> = IndexPredicate<T> | FilterQuery<T>;
|
|
7
|
+
export type IndexDefinition = {
|
|
8
|
+
name: string;
|
|
9
|
+
properties: string[];
|
|
10
|
+
where?: IndexWhere<any>;
|
|
11
|
+
};
|
|
1
12
|
type IndexOptions<T> = {
|
|
2
13
|
properties: (keyof T)[];
|
|
14
|
+
where?: IndexWhere<T>;
|
|
3
15
|
} | (keyof T)[] | undefined;
|
|
4
16
|
export declare function Index<T>(options?: IndexOptions<T>): ClassDecorator & PropertyDecorator;
|
|
5
17
|
export {};
|
|
@@ -10,7 +10,8 @@ function buildFromOptions(options) {
|
|
|
10
10
|
if (!props || props.length === 0)
|
|
11
11
|
return null;
|
|
12
12
|
const keys = props;
|
|
13
|
-
|
|
13
|
+
const where = Array.isArray(options) ? undefined : options?.where;
|
|
14
|
+
return { name: `${keys.join('_')}_index`, properties: keys, where };
|
|
14
15
|
}
|
|
15
16
|
function buildFromProperty(propertyKey) {
|
|
16
17
|
const name = String(propertyKey);
|
package/dist/domain/entities.js
CHANGED
|
@@ -14,6 +14,69 @@ exports.EntityStorage = void 0;
|
|
|
14
14
|
const core_1 = require("@cheetah.js/core");
|
|
15
15
|
const orm_session_context_1 = require("../orm-session-context");
|
|
16
16
|
const utils_1 = require("../utils");
|
|
17
|
+
const index_condition_builder_1 = require("../query/index-condition-builder");
|
|
18
|
+
function buildIndexColumnMap(properties, relations) {
|
|
19
|
+
const map = mapPropertyColumns(properties);
|
|
20
|
+
addRelationColumns(map, relations);
|
|
21
|
+
return map;
|
|
22
|
+
}
|
|
23
|
+
function mapPropertyColumns(properties) {
|
|
24
|
+
const map = {};
|
|
25
|
+
Object.entries(properties).forEach(([key, value]) => {
|
|
26
|
+
map[key] = value.options.columnName;
|
|
27
|
+
});
|
|
28
|
+
return map;
|
|
29
|
+
}
|
|
30
|
+
function addRelationColumns(map, relations) {
|
|
31
|
+
relations.forEach((relation) => {
|
|
32
|
+
map[String(relation.propertyKey)] = relation.columnName;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function mapIndexDefinitions(indexes, entityName, columnMap) {
|
|
36
|
+
return indexes.map((index) => toSnapshotIndex(index, entityName, columnMap));
|
|
37
|
+
}
|
|
38
|
+
function toSnapshotIndex(index, entityName, columnMap) {
|
|
39
|
+
const columns = resolveIndexColumns(index, columnMap);
|
|
40
|
+
const indexName = resolveIndexName(index.name, entityName, columns);
|
|
41
|
+
return {
|
|
42
|
+
table: entityName,
|
|
43
|
+
indexName,
|
|
44
|
+
columnName: columns.join(","),
|
|
45
|
+
where: resolveIndexWhere(index.where, columnMap),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function resolveIndexColumns(index, columnMap) {
|
|
49
|
+
return index.properties.map((propName) => resolveIndexColumn(propName, columnMap));
|
|
50
|
+
}
|
|
51
|
+
function resolveIndexColumn(propName, columnMap) {
|
|
52
|
+
const mapped = columnMap[propName];
|
|
53
|
+
if (mapped) {
|
|
54
|
+
return mapped;
|
|
55
|
+
}
|
|
56
|
+
return (0, utils_1.toSnakeCase)(propName);
|
|
57
|
+
}
|
|
58
|
+
function resolveIndexName(name, entityName, columns) {
|
|
59
|
+
if (name.includes('_pkey') || name.includes('[TABLE]')) {
|
|
60
|
+
return name.replace("[TABLE]", entityName);
|
|
61
|
+
}
|
|
62
|
+
return `${columns.join("_")}_index`;
|
|
63
|
+
}
|
|
64
|
+
function resolveIndexWhere(where, columnMap) {
|
|
65
|
+
if (!where) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
if (typeof where === "string") {
|
|
69
|
+
return where;
|
|
70
|
+
}
|
|
71
|
+
if (typeof where === "function") {
|
|
72
|
+
return where(columnMap);
|
|
73
|
+
}
|
|
74
|
+
return buildIndexWhere(where, columnMap);
|
|
75
|
+
}
|
|
76
|
+
function buildIndexWhere(where, columnMap) {
|
|
77
|
+
const builder = new index_condition_builder_1.IndexConditionBuilder(columnMap);
|
|
78
|
+
return builder.build(where);
|
|
79
|
+
}
|
|
17
80
|
let EntityStorage = EntityStorage_1 = class EntityStorage {
|
|
18
81
|
constructor() {
|
|
19
82
|
this.entities = new Map();
|
|
@@ -22,42 +85,14 @@ let EntityStorage = EntityStorage_1 = class EntityStorage {
|
|
|
22
85
|
add(entity, properties, relations, hooks) {
|
|
23
86
|
const entityName = entity.options?.tableName || (0, utils_1.toSnakeCase)(entity.target.name);
|
|
24
87
|
const indexes = core_1.Metadata.get("indexes", entity.target) || [];
|
|
88
|
+
const columnMap = buildIndexColumnMap(properties, relations);
|
|
25
89
|
this.entities.set(entity.target, {
|
|
26
90
|
properties: properties,
|
|
27
91
|
hideProperties: Object.entries(properties)
|
|
28
92
|
.filter(([_key, value]) => value.options.hidden)
|
|
29
93
|
.map(([key]) => key),
|
|
30
94
|
relations,
|
|
31
|
-
indexes: indexes
|
|
32
|
-
// Convert property names to database column names
|
|
33
|
-
const columnNames = index.properties.map(propName => {
|
|
34
|
-
// Check if it's a regular property
|
|
35
|
-
if (properties[propName]) {
|
|
36
|
-
return properties[propName].options.columnName;
|
|
37
|
-
}
|
|
38
|
-
// Check if it's a relation
|
|
39
|
-
const relation = relations.find(rel => String(rel.propertyKey) === propName);
|
|
40
|
-
if (relation) {
|
|
41
|
-
return relation.columnName;
|
|
42
|
-
}
|
|
43
|
-
// Fallback to snake_case conversion if not found
|
|
44
|
-
return (0, utils_1.toSnakeCase)(propName);
|
|
45
|
-
});
|
|
46
|
-
// Preserve the original index name if it's a primary key index (contains _pkey or [TABLE])
|
|
47
|
-
// Otherwise, generate index name using column names
|
|
48
|
-
let indexName;
|
|
49
|
-
if (index.name.includes('_pkey') || index.name.includes('[TABLE]')) {
|
|
50
|
-
indexName = index.name.replace("[TABLE]", entityName);
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
indexName = `${columnNames.join("_")}_index`;
|
|
54
|
-
}
|
|
55
|
-
return {
|
|
56
|
-
table: entityName,
|
|
57
|
-
indexName,
|
|
58
|
-
columnName: columnNames.join(","),
|
|
59
|
-
};
|
|
60
|
-
}),
|
|
95
|
+
indexes: mapIndexDefinitions(indexes, entityName, columnMap),
|
|
61
96
|
hooks,
|
|
62
97
|
tableName: entityName,
|
|
63
98
|
...entity.options,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ColDiff, ConnectionSettings, DriverInterface, ForeignKeyInfo, SnapshotConstraintInfo, SnapshotIndexInfo, SnapshotTable, Statement } from './driver.interface';
|
|
1
|
+
import { ColDiff, ConnectionSettings, DriverInterface, ForeignKeyInfo, IndexStatement, SnapshotConstraintInfo, SnapshotIndexInfo, SnapshotTable, Statement } from './driver.interface';
|
|
2
2
|
import { BunDriverBase } from './bun-driver.base';
|
|
3
3
|
export declare class BunMysqlDriver extends BunDriverBase implements DriverInterface {
|
|
4
4
|
readonly dbType: "mysql";
|
|
@@ -19,10 +19,7 @@ export declare class BunMysqlDriver extends BunDriverBase implements DriverInter
|
|
|
19
19
|
protected buildLimitAndOffsetClause(statement: Statement<any>): string;
|
|
20
20
|
getCreateTableInstruction(schema: string | undefined, tableName: string, creates: ColDiff[]): string;
|
|
21
21
|
getAlterTableFkInstruction(schema: string | undefined, tableName: string, colDiff: ColDiff, fk: ForeignKeyInfo): string;
|
|
22
|
-
getCreateIndex(index:
|
|
23
|
-
name: string;
|
|
24
|
-
properties: string[];
|
|
25
|
-
}, schema: string | undefined, tableName: string): string;
|
|
22
|
+
getCreateIndex(index: IndexStatement, schema: string | undefined, tableName: string): string;
|
|
26
23
|
getAddColumn(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff, colDiffInstructions: string[]): void;
|
|
27
24
|
getDropColumn(colDiffInstructions: string[], schema: string | undefined, tableName: string, colName: string): void;
|
|
28
25
|
getDropIndex(index: {
|
|
@@ -95,7 +95,15 @@ class BunMysqlDriver extends bun_driver_base_1.BunDriverBase {
|
|
|
95
95
|
return `ALTER TABLE \`${schema}\`.\`${tableName}\` ADD CONSTRAINT \`${tableName}_${colDiff.colName}_fk\` FOREIGN KEY (\`${colDiff.colName}\`) REFERENCES \`${fk.referencedTableName}\` (\`${fk.referencedColumnName}\`);`;
|
|
96
96
|
}
|
|
97
97
|
getCreateIndex(index, schema, tableName) {
|
|
98
|
-
|
|
98
|
+
const properties = index.properties || [];
|
|
99
|
+
if (properties.length === 0) {
|
|
100
|
+
throw new Error("Index properties are required.");
|
|
101
|
+
}
|
|
102
|
+
if (index.where) {
|
|
103
|
+
throw new Error("Partial indexes are only supported on postgres.");
|
|
104
|
+
}
|
|
105
|
+
const columns = properties.map((prop) => `\`${prop}\``).join(', ');
|
|
106
|
+
return `CREATE INDEX \`${index.name}\` ON \`${schema}\`.\`${tableName}\` (${columns});`;
|
|
99
107
|
}
|
|
100
108
|
getAddColumn(schema, tableName, colName, colDiff, colDiffInstructions) {
|
|
101
109
|
let sql = ``;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ColDiff, ConnectionSettings, DriverInterface, ForeignKeyInfo, SnapshotConstraintInfo, SnapshotIndexInfo, SnapshotTable, Statement } from './driver.interface';
|
|
1
|
+
import { ColDiff, ConnectionSettings, DriverInterface, ForeignKeyInfo, IndexStatement, SnapshotConstraintInfo, SnapshotIndexInfo, SnapshotTable, Statement } from './driver.interface';
|
|
2
2
|
import { BunDriverBase } from './bun-driver.base';
|
|
3
3
|
export declare class BunPgDriver extends BunDriverBase implements DriverInterface {
|
|
4
4
|
readonly dbType: "postgres";
|
|
@@ -18,10 +18,7 @@ export declare class BunPgDriver extends BunDriverBase implements DriverInterfac
|
|
|
18
18
|
}>;
|
|
19
19
|
getCreateTableInstruction(schema: string | undefined, tableName: string, creates: ColDiff[]): string;
|
|
20
20
|
getAlterTableFkInstruction(schema: string | undefined, tableName: string, colDiff: ColDiff, fk: ForeignKeyInfo): string;
|
|
21
|
-
getCreateIndex(index:
|
|
22
|
-
name: string;
|
|
23
|
-
properties: string[];
|
|
24
|
-
}, schema: string | undefined, tableName: string): string;
|
|
21
|
+
getCreateIndex(index: IndexStatement, schema: string | undefined, tableName: string): string;
|
|
25
22
|
getAddColumn(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff, colDiffInstructions: string[]): void;
|
|
26
23
|
getDropColumn(colDiffInstructions: string[], schema: string | undefined, tableName: string, colName: string): void;
|
|
27
24
|
getDropIndex(index: {
|
|
@@ -70,7 +70,13 @@ class BunPgDriver extends bun_driver_base_1.BunDriverBase {
|
|
|
70
70
|
return `ALTER TABLE "${schema}"."${tableName}" ADD CONSTRAINT "${tableName}_${colDiff.colName}_fk" FOREIGN KEY ("${colDiff.colName}") REFERENCES "${fk.referencedTableName}" ("${fk.referencedColumnName}");`;
|
|
71
71
|
}
|
|
72
72
|
getCreateIndex(index, schema, tableName) {
|
|
73
|
-
|
|
73
|
+
const properties = index.properties || [];
|
|
74
|
+
if (properties.length === 0) {
|
|
75
|
+
throw new Error("Index properties are required.");
|
|
76
|
+
}
|
|
77
|
+
const columns = properties.map((prop) => `"${prop}"`).join(', ');
|
|
78
|
+
const where = this.buildWhereClause(index.where);
|
|
79
|
+
return `CREATE INDEX "${index.name}" ON "${schema}"."${tableName}" (${columns})${where};`;
|
|
74
80
|
}
|
|
75
81
|
getAddColumn(schema, tableName, colName, colDiff, colDiffInstructions) {
|
|
76
82
|
let beforeSql = ``;
|
|
@@ -16,16 +16,10 @@ export interface DriverInterface {
|
|
|
16
16
|
snapshot(tableName: string, options: any): Promise<SnapshotTable | undefined>;
|
|
17
17
|
getCreateTableInstruction(schema: string | undefined, tableName: string, creates: ColDiff[]): string;
|
|
18
18
|
getAlterTableFkInstruction(schema: string | undefined, tableName: string, colDiff: ColDiff, fk: ForeignKeyInfo): string;
|
|
19
|
-
getCreateIndex(index:
|
|
20
|
-
name: string;
|
|
21
|
-
properties?: string[];
|
|
22
|
-
}, schema: string | undefined, tableName: string): string;
|
|
19
|
+
getCreateIndex(index: IndexStatement, schema: string | undefined, tableName: string): string;
|
|
23
20
|
getAddColumn(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff, colDiffInstructions: string[]): void;
|
|
24
21
|
getDropColumn(colDiffInstructions: string[], schema: string | undefined, tableName: string, colName: string): void;
|
|
25
|
-
getDropIndex(index:
|
|
26
|
-
name: string;
|
|
27
|
-
properties?: string[];
|
|
28
|
-
}, schema: string | undefined, tableName: string): string;
|
|
22
|
+
getDropIndex(index: IndexStatement, schema: string | undefined, tableName: string): string;
|
|
29
23
|
getAlterTableType(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string;
|
|
30
24
|
getAlterTableDefaultInstruction(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string;
|
|
31
25
|
getAlterTablePrimaryKeyInstruction(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string;
|
|
@@ -148,6 +142,12 @@ export type SnapshotIndexInfo = {
|
|
|
148
142
|
table: string;
|
|
149
143
|
indexName: string;
|
|
150
144
|
columnName: string;
|
|
145
|
+
where?: string;
|
|
146
|
+
};
|
|
147
|
+
export type IndexStatement = {
|
|
148
|
+
name: string;
|
|
149
|
+
properties?: string[];
|
|
150
|
+
where?: string;
|
|
151
151
|
};
|
|
152
152
|
export type ForeignKeyInfo = {
|
|
153
153
|
referencedTableName: string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { FilterQuery } from '../driver/driver.interface';
|
|
2
|
+
export declare class IndexConditionBuilder<T> {
|
|
3
|
+
private columnMap;
|
|
4
|
+
private readonly OPERATORS;
|
|
5
|
+
private lastKeyNotOperator;
|
|
6
|
+
constructor(columnMap: Record<string, string>);
|
|
7
|
+
build(condition: FilterQuery<T>): string;
|
|
8
|
+
private processConditions;
|
|
9
|
+
private getEntries;
|
|
10
|
+
private processEntries;
|
|
11
|
+
private processEntry;
|
|
12
|
+
private handleScalarValue;
|
|
13
|
+
private handleObjectValue;
|
|
14
|
+
private buildLogicalOperatorCondition;
|
|
15
|
+
private buildOperatorConditions;
|
|
16
|
+
private buildOperatorCondition;
|
|
17
|
+
private buildSimpleCondition;
|
|
18
|
+
private buildInCondition;
|
|
19
|
+
private buildNotInCondition;
|
|
20
|
+
private buildLikeCondition;
|
|
21
|
+
private buildComparisonCondition;
|
|
22
|
+
private buildNestedLogicalCondition;
|
|
23
|
+
private buildNorCondition;
|
|
24
|
+
private wrapWithLogicalOperator;
|
|
25
|
+
private extractValueFromValueObject;
|
|
26
|
+
private formatValue;
|
|
27
|
+
private formatDate;
|
|
28
|
+
private isNullish;
|
|
29
|
+
private isPrimitive;
|
|
30
|
+
private formatPrimitive;
|
|
31
|
+
private formatJson;
|
|
32
|
+
private escapeString;
|
|
33
|
+
private isScalarValue;
|
|
34
|
+
private isArrayValue;
|
|
35
|
+
private isLogicalOperator;
|
|
36
|
+
private isNorOperator;
|
|
37
|
+
private extractLogicalOperator;
|
|
38
|
+
private trackLastNonOperatorKey;
|
|
39
|
+
private resolveColumnName;
|
|
40
|
+
private buildNullCondition;
|
|
41
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IndexConditionBuilder = void 0;
|
|
4
|
+
const value_object_1 = require("../common/value-object");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
class IndexConditionBuilder {
|
|
7
|
+
constructor(columnMap) {
|
|
8
|
+
this.columnMap = columnMap;
|
|
9
|
+
this.OPERATORS = [
|
|
10
|
+
'$eq',
|
|
11
|
+
'$ne',
|
|
12
|
+
'$in',
|
|
13
|
+
'$nin',
|
|
14
|
+
'$like',
|
|
15
|
+
'$gt',
|
|
16
|
+
'$gte',
|
|
17
|
+
'$lt',
|
|
18
|
+
'$lte',
|
|
19
|
+
'$and',
|
|
20
|
+
'$or',
|
|
21
|
+
'$nor',
|
|
22
|
+
];
|
|
23
|
+
this.lastKeyNotOperator = '';
|
|
24
|
+
}
|
|
25
|
+
build(condition) {
|
|
26
|
+
const sqlParts = this.processConditions(condition);
|
|
27
|
+
if (sqlParts.length === 0) {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
return this.wrapWithLogicalOperator(sqlParts, 'AND');
|
|
31
|
+
}
|
|
32
|
+
processConditions(condition) {
|
|
33
|
+
const entries = this.getEntries(condition);
|
|
34
|
+
return this.processEntries(entries);
|
|
35
|
+
}
|
|
36
|
+
getEntries(condition) {
|
|
37
|
+
if (!condition || typeof condition !== 'object') {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
return Object.entries(condition);
|
|
41
|
+
}
|
|
42
|
+
processEntries(entries) {
|
|
43
|
+
const sqlParts = [];
|
|
44
|
+
for (const [key, value] of entries) {
|
|
45
|
+
const extractedValue = this.extractValueFromValueObject(value);
|
|
46
|
+
const conditionSql = this.processEntry(key, extractedValue);
|
|
47
|
+
if (conditionSql) {
|
|
48
|
+
sqlParts.push(conditionSql);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return sqlParts;
|
|
52
|
+
}
|
|
53
|
+
processEntry(key, value) {
|
|
54
|
+
this.trackLastNonOperatorKey(key);
|
|
55
|
+
if (this.isScalarValue(value)) {
|
|
56
|
+
return this.handleScalarValue(key, value);
|
|
57
|
+
}
|
|
58
|
+
if (this.isArrayValue(key, value)) {
|
|
59
|
+
return this.buildInCondition(key, value);
|
|
60
|
+
}
|
|
61
|
+
return this.handleObjectValue(key, value);
|
|
62
|
+
}
|
|
63
|
+
handleScalarValue(key, value) {
|
|
64
|
+
if (key === '$eq') {
|
|
65
|
+
return this.buildSimpleCondition(this.lastKeyNotOperator, value, '=');
|
|
66
|
+
}
|
|
67
|
+
return this.buildSimpleCondition(key, value, '=');
|
|
68
|
+
}
|
|
69
|
+
handleObjectValue(key, value) {
|
|
70
|
+
if (this.isLogicalOperator(key)) {
|
|
71
|
+
return this.buildLogicalOperatorCondition(key, value);
|
|
72
|
+
}
|
|
73
|
+
if (this.isNorOperator(key)) {
|
|
74
|
+
return this.buildNorCondition(value);
|
|
75
|
+
}
|
|
76
|
+
return this.buildOperatorConditions(key, value);
|
|
77
|
+
}
|
|
78
|
+
buildLogicalOperatorCondition(key, value) {
|
|
79
|
+
const conditions = value.map((cond) => this.build(cond));
|
|
80
|
+
const operator = this.extractLogicalOperator(key);
|
|
81
|
+
return this.wrapWithLogicalOperator(conditions, operator);
|
|
82
|
+
}
|
|
83
|
+
buildOperatorConditions(key, value) {
|
|
84
|
+
const parts = [];
|
|
85
|
+
for (const operator of this.OPERATORS) {
|
|
86
|
+
if (operator in value) {
|
|
87
|
+
const condition = this.buildOperatorCondition(key, operator, value[operator]);
|
|
88
|
+
parts.push(condition);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return parts.join(' AND ');
|
|
92
|
+
}
|
|
93
|
+
buildOperatorCondition(key, operator, value) {
|
|
94
|
+
switch (operator) {
|
|
95
|
+
case '$eq':
|
|
96
|
+
return this.buildSimpleCondition(key, value, '=');
|
|
97
|
+
case '$ne':
|
|
98
|
+
return this.buildSimpleCondition(key, value, '!=');
|
|
99
|
+
case '$in':
|
|
100
|
+
return this.buildInCondition(key, value);
|
|
101
|
+
case '$nin':
|
|
102
|
+
return this.buildNotInCondition(key, value);
|
|
103
|
+
case '$like':
|
|
104
|
+
return this.buildLikeCondition(key, value);
|
|
105
|
+
case '$gt':
|
|
106
|
+
return this.buildComparisonCondition(key, value, '>');
|
|
107
|
+
case '$gte':
|
|
108
|
+
return this.buildComparisonCondition(key, value, '>=');
|
|
109
|
+
case '$lt':
|
|
110
|
+
return this.buildComparisonCondition(key, value, '<');
|
|
111
|
+
case '$lte':
|
|
112
|
+
return this.buildComparisonCondition(key, value, '<=');
|
|
113
|
+
case '$and':
|
|
114
|
+
case '$or':
|
|
115
|
+
return this.buildNestedLogicalCondition(operator, value);
|
|
116
|
+
case '$nor':
|
|
117
|
+
return this.buildNorCondition(value);
|
|
118
|
+
default:
|
|
119
|
+
return '';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
buildSimpleCondition(key, value, operator) {
|
|
123
|
+
const column = this.resolveColumnName(key);
|
|
124
|
+
if (this.isNullish(value))
|
|
125
|
+
return this.buildNullCondition(column, operator);
|
|
126
|
+
const formattedValue = this.formatValue(value);
|
|
127
|
+
return `${column} ${operator} ${formattedValue}`;
|
|
128
|
+
}
|
|
129
|
+
buildInCondition(key, values) {
|
|
130
|
+
const column = this.resolveColumnName(key);
|
|
131
|
+
const formattedValues = values.map((val) => this.formatValue(val)).join(', ');
|
|
132
|
+
return `${column} IN (${formattedValues})`;
|
|
133
|
+
}
|
|
134
|
+
buildNotInCondition(key, values) {
|
|
135
|
+
const column = this.resolveColumnName(key);
|
|
136
|
+
const formattedValues = values.map((val) => this.formatValue(val)).join(', ');
|
|
137
|
+
return `${column} NOT IN (${formattedValues})`;
|
|
138
|
+
}
|
|
139
|
+
buildLikeCondition(key, value) {
|
|
140
|
+
const column = this.resolveColumnName(key);
|
|
141
|
+
return `${column} LIKE '${value}'`;
|
|
142
|
+
}
|
|
143
|
+
buildComparisonCondition(key, value, operator) {
|
|
144
|
+
const column = this.resolveColumnName(key);
|
|
145
|
+
if (this.isNullish(value))
|
|
146
|
+
return this.buildNullCondition(column, operator);
|
|
147
|
+
const formattedValue = this.formatValue(value);
|
|
148
|
+
return `${column} ${operator} ${formattedValue}`;
|
|
149
|
+
}
|
|
150
|
+
buildNestedLogicalCondition(operator, value) {
|
|
151
|
+
const conditions = value.map((cond) => this.build(cond));
|
|
152
|
+
const logicalOp = this.extractLogicalOperator(operator);
|
|
153
|
+
return this.wrapWithLogicalOperator(conditions, logicalOp);
|
|
154
|
+
}
|
|
155
|
+
buildNorCondition(value) {
|
|
156
|
+
const conditions = value.map((cond) => this.build(cond));
|
|
157
|
+
const wrapped = this.wrapWithLogicalOperator(conditions, 'OR');
|
|
158
|
+
return `NOT ${wrapped}`;
|
|
159
|
+
}
|
|
160
|
+
wrapWithLogicalOperator(conditions, operator) {
|
|
161
|
+
return `(${conditions.join(` ${operator} `)})`;
|
|
162
|
+
}
|
|
163
|
+
extractValueFromValueObject(value) {
|
|
164
|
+
if ((0, utils_1.extendsFrom)(value_object_1.ValueObject, value?.constructor?.prototype)) {
|
|
165
|
+
return value.getValue();
|
|
166
|
+
}
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
formatValue(value) {
|
|
170
|
+
if (value instanceof Date)
|
|
171
|
+
return this.formatDate(value);
|
|
172
|
+
if (this.isNullish(value))
|
|
173
|
+
return 'NULL';
|
|
174
|
+
if (this.isPrimitive(value))
|
|
175
|
+
return this.formatPrimitive(value);
|
|
176
|
+
return this.formatJson(value);
|
|
177
|
+
}
|
|
178
|
+
formatDate(value) {
|
|
179
|
+
return `'${value.toISOString()}'`;
|
|
180
|
+
}
|
|
181
|
+
isNullish(value) {
|
|
182
|
+
return value === null || value === undefined;
|
|
183
|
+
}
|
|
184
|
+
isPrimitive(value) {
|
|
185
|
+
return ['string', 'number', 'boolean', 'bigint'].includes(typeof value);
|
|
186
|
+
}
|
|
187
|
+
formatPrimitive(value) {
|
|
188
|
+
if (typeof value === 'string')
|
|
189
|
+
return `'${this.escapeString(value)}'`;
|
|
190
|
+
return `${value}`;
|
|
191
|
+
}
|
|
192
|
+
formatJson(value) {
|
|
193
|
+
return `'${this.escapeString(JSON.stringify(value))}'`;
|
|
194
|
+
}
|
|
195
|
+
escapeString(value) {
|
|
196
|
+
return value.replace(/'/g, "''");
|
|
197
|
+
}
|
|
198
|
+
isScalarValue(value) {
|
|
199
|
+
const isDate = value instanceof Date;
|
|
200
|
+
return typeof value !== 'object' || value === null || isDate;
|
|
201
|
+
}
|
|
202
|
+
isArrayValue(key, value) {
|
|
203
|
+
return !this.OPERATORS.includes(key) && Array.isArray(value);
|
|
204
|
+
}
|
|
205
|
+
isLogicalOperator(key) {
|
|
206
|
+
return ['$or', '$and'].includes(key);
|
|
207
|
+
}
|
|
208
|
+
isNorOperator(key) {
|
|
209
|
+
return key === '$nor';
|
|
210
|
+
}
|
|
211
|
+
extractLogicalOperator(key) {
|
|
212
|
+
return key.toUpperCase().replace('$', '');
|
|
213
|
+
}
|
|
214
|
+
trackLastNonOperatorKey(key) {
|
|
215
|
+
if (!this.OPERATORS.includes(key)) {
|
|
216
|
+
this.lastKeyNotOperator = key;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
resolveColumnName(property) {
|
|
220
|
+
if (property.startsWith('$')) {
|
|
221
|
+
return property;
|
|
222
|
+
}
|
|
223
|
+
const column = this.columnMap[property];
|
|
224
|
+
if (column) {
|
|
225
|
+
return column;
|
|
226
|
+
}
|
|
227
|
+
return (0, utils_1.toSnakeCase)(property);
|
|
228
|
+
}
|
|
229
|
+
buildNullCondition(column, operator) {
|
|
230
|
+
if (operator === '!=' || operator === '<>')
|
|
231
|
+
return `${column} IS NOT NULL`;
|
|
232
|
+
return `${column} IS NULL`;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
exports.IndexConditionBuilder = IndexConditionBuilder;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cheetah.js/orm",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.144",
|
|
4
4
|
"description": "A simple ORM for Cheetah.js.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"bun",
|
|
56
56
|
"value-object"
|
|
57
57
|
],
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "73bccb890079ef872f5dcb9f9df1a1a9dfad5912"
|
|
59
59
|
}
|