@cheetah.js/orm 0.1.143 → 0.1.146
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 +240 -29
- package/dist/decorators/index.decorator.d.ts +4 -2
- package/dist/domain/entities.js +9 -1
- package/dist/entry.js +6 -1
- package/dist/identity-map/entity-key-generator.d.ts +10 -0
- package/dist/identity-map/entity-key-generator.js +45 -0
- package/dist/identity-map/entity-registry.d.ts +11 -0
- package/dist/identity-map/entity-registry.js +41 -0
- package/dist/identity-map/identity-map-context.d.ts +9 -0
- package/dist/identity-map/identity-map-context.js +22 -0
- package/dist/identity-map/identity-map-integration.d.ts +5 -0
- package/dist/identity-map/identity-map-integration.js +36 -0
- package/dist/identity-map/identity-map.d.ts +10 -0
- package/dist/identity-map/identity-map.js +31 -0
- package/dist/identity-map/index.d.ts +5 -0
- package/dist/identity-map/index.js +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -1
- package/dist/middleware/identity-map.middleware.d.ts +4 -0
- package/dist/middleware/identity-map.middleware.js +22 -0
- package/dist/query/index-condition-builder.d.ts +41 -0
- package/dist/query/index-condition-builder.js +235 -0
- package/dist/query/model-transformer.d.ts +5 -0
- package/dist/query/model-transformer.js +48 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@ Cheetah.js ORM is a simple and powerful ORM for Cheetah.js and Bun.
|
|
|
9
9
|
- [Value Objects](#value-objects)
|
|
10
10
|
- [Hooks](#hooks)
|
|
11
11
|
- [Usage](#usage)
|
|
12
|
+
- [Caching](#caching)
|
|
13
|
+
- [Identity Map](#identity-map)
|
|
12
14
|
- [Migrations](#migrations)
|
|
13
15
|
|
|
14
16
|
### [Installation](#install)
|
|
@@ -124,7 +126,7 @@ export class User {
|
|
|
124
126
|
}
|
|
125
127
|
```
|
|
126
128
|
|
|
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):
|
|
129
|
+
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
130
|
|
|
129
131
|
```javascript
|
|
130
132
|
@Entity()
|
|
@@ -156,34 +158,60 @@ export class User {
|
|
|
156
158
|
email: string;
|
|
157
159
|
}
|
|
158
160
|
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
161
|
+
// Backward compatible usage (array):
|
|
162
|
+
// @Index(['name', 'email'])
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
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):
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
@Entity()
|
|
169
|
+
@Index<{ User }>({
|
|
170
|
+
properties: ['email'],
|
|
171
|
+
where: (columns) => `${columns.isActive} = true`,
|
|
172
|
+
})
|
|
173
|
+
export class User {
|
|
174
|
+
@PrimaryKey()
|
|
175
|
+
id: number;
|
|
176
|
+
|
|
177
|
+
@Property()
|
|
178
|
+
email: string;
|
|
179
|
+
|
|
180
|
+
@Property()
|
|
181
|
+
isActive: boolean;
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
You can also use the ORM filter syntax in `where` (with `$in`, `$or`, `$nor`, etc.):
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
@Entity()
|
|
189
|
+
@Index<{ User }>({
|
|
190
|
+
properties: ['email'],
|
|
191
|
+
where: {
|
|
192
|
+
isActive: true,
|
|
193
|
+
status: { $in: ['active', 'pending'] },
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
export class User {
|
|
197
|
+
@PrimaryKey()
|
|
198
|
+
id: number;
|
|
199
|
+
|
|
200
|
+
@Property()
|
|
201
|
+
email: string;
|
|
202
|
+
|
|
203
|
+
@Property()
|
|
204
|
+
isActive: boolean;
|
|
205
|
+
|
|
206
|
+
@Property()
|
|
207
|
+
status: string;
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Note: MySQL does not support partial indexes; using `where` with the MySQL driver will throw.
|
|
212
|
+
|
|
213
|
+
#### Property options
|
|
214
|
+
| Option | Type | Description |
|
|
187
215
|
| ------ | ---- |--------------------------------------------------------------------------------------------|
|
|
188
216
|
| nullable | boolean | Defines if the property is nullable. |
|
|
189
217
|
| unique | boolean | Defines if the property is unique. |
|
|
@@ -307,6 +335,189 @@ await repo.find({ where: { name: 'John' }, cache: new Date(Date.now() + 60_000)
|
|
|
307
335
|
// Infinite/driver-default cache
|
|
308
336
|
await repo.find({ where: { name: 'John' }, cache: true });
|
|
309
337
|
```
|
|
338
|
+
|
|
339
|
+
### Identity Map
|
|
340
|
+
|
|
341
|
+
The Identity Map is an in-memory cache that ensures each entity is loaded only once per request context. This pattern reduces database queries and guarantees that all references to the same entity point to the same object instance.
|
|
342
|
+
|
|
343
|
+
#### Key Benefits
|
|
344
|
+
|
|
345
|
+
- **Reduced Database Queries**: When querying for an entity that was already loaded in the same context, the cached instance is returned instead of executing another query
|
|
346
|
+
- **Consistent Entity References**: All parts of your code working with the same entity will share the same instance
|
|
347
|
+
- **Memory Efficient**: Uses WeakRef internally, allowing garbage collection of unreferenced entities
|
|
348
|
+
- **Per-Request Isolation**: Each request has its own identity map, preventing data leakage between requests
|
|
349
|
+
|
|
350
|
+
#### Automatic Activation (Recommended)
|
|
351
|
+
|
|
352
|
+
**The identity map is automatically enabled for all routes when you use `CheetahOrm`!** No additional configuration needed:
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
import { Cheetah } from '@cheetah.js/core';
|
|
356
|
+
import { CheetahOrm } from '@cheetah.js/orm';
|
|
357
|
+
|
|
358
|
+
new Cheetah()
|
|
359
|
+
.use(CheetahOrm) // ← Identity map automatically active for all routes
|
|
360
|
+
.listen();
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Now all your controllers automatically benefit from the identity map:
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
import { Controller, Get } from '@cheetah.js/core';
|
|
367
|
+
|
|
368
|
+
@Controller('/users')
|
|
369
|
+
export class UserController {
|
|
370
|
+
@Get('/:id/posts')
|
|
371
|
+
async getUserPosts(id: number) {
|
|
372
|
+
// Identity map is AUTOMATICALLY active - no decorator needed!
|
|
373
|
+
const user = await User.findOne({ id });
|
|
374
|
+
const posts = await Post.findAll({ userId: id }, { load: ['user'] });
|
|
375
|
+
|
|
376
|
+
// posts[0].user === user (same instance, no extra query)
|
|
377
|
+
return { user, posts };
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
**That's it!** The identity map works transparently across your entire application.
|
|
383
|
+
|
|
384
|
+
#### Manual Usage (Advanced)
|
|
385
|
+
|
|
386
|
+
For custom scenarios, use `identityMapContext.run()` directly:
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
import { identityMapContext } from '@cheetah.js/orm';
|
|
390
|
+
|
|
391
|
+
async function processUserData(userId: number) {
|
|
392
|
+
await identityMapContext.run(async () => {
|
|
393
|
+
// All queries within this context share the same identity map
|
|
394
|
+
const user = await User.findOne({ id: userId });
|
|
395
|
+
const posts = await Post.findAll({ userId }, { load: ['user'] });
|
|
396
|
+
|
|
397
|
+
// posts[0].user === user (same instance, no extra query)
|
|
398
|
+
return { user, posts };
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
#### How It Works
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
await identityMapContext.run(async () => {
|
|
407
|
+
// First query - fetches from database and caches
|
|
408
|
+
const user1 = await User.findOne({ id: 1 });
|
|
409
|
+
|
|
410
|
+
// Second query - returns cached instance (no database query)
|
|
411
|
+
const user2 = await User.findOne({ id: 1 });
|
|
412
|
+
|
|
413
|
+
console.log(user1 === user2); // true - same object instance
|
|
414
|
+
|
|
415
|
+
// Modifications are reflected everywhere
|
|
416
|
+
user1.name = 'Updated Name';
|
|
417
|
+
console.log(user2.name); // 'Updated Name'
|
|
418
|
+
});
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
#### Relationship Loading
|
|
422
|
+
|
|
423
|
+
The identity map automatically caches entities loaded through relationships:
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
await identityMapContext.run(async () => {
|
|
427
|
+
// Load user first
|
|
428
|
+
const user = await User.findOne({ id: 1 });
|
|
429
|
+
|
|
430
|
+
// Load posts with user relationship
|
|
431
|
+
const posts = await Post.findAll(
|
|
432
|
+
{ userId: 1 },
|
|
433
|
+
{ load: ['user'] }
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
// The user loaded through posts is the same cached instance
|
|
437
|
+
console.log(posts[0].user === user); // true
|
|
438
|
+
});
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
#### Context Isolation
|
|
442
|
+
|
|
443
|
+
Each `identityMapContext.run()` creates an isolated scope:
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
let user1, user2;
|
|
447
|
+
|
|
448
|
+
// First context
|
|
449
|
+
await identityMapContext.run(async () => {
|
|
450
|
+
user1 = await User.findOne({ id: 1 });
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// Second context - completely separate identity map
|
|
454
|
+
await identityMapContext.run(async () => {
|
|
455
|
+
user2 = await User.findOne({ id: 1 });
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// Different contexts = different instances
|
|
459
|
+
console.log(user1 === user2); // false
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
#### Without Identity Map Context
|
|
463
|
+
|
|
464
|
+
When not using `identityMapContext.run()`, the ORM behaves normally without caching:
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
// Without context wrapper
|
|
468
|
+
const user1 = await User.findOne({ id: 1 });
|
|
469
|
+
const user2 = await User.findOne({ id: 1 });
|
|
470
|
+
|
|
471
|
+
console.log(user1 === user2); // false - different instances
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
#### Advanced: Disabling or Customizing
|
|
475
|
+
|
|
476
|
+
If you need to disable the identity map for specific routes, you can use manual context management:
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
import { Controller, Get, Middleware } from '@cheetah.js/core';
|
|
480
|
+
import { identityMapContext, IdentityMapMiddleware } from '@cheetah.js/orm';
|
|
481
|
+
|
|
482
|
+
@Controller('/users')
|
|
483
|
+
export class UserController {
|
|
484
|
+
@Get('/:id')
|
|
485
|
+
async getUser(id: number) {
|
|
486
|
+
// Identity map active (global middleware)
|
|
487
|
+
return User.findOne({ id });
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
@Get('/legacy')
|
|
491
|
+
async getLegacyUsers() {
|
|
492
|
+
// To bypass identity map, just query normally
|
|
493
|
+
// The global middleware is still active, but you can
|
|
494
|
+
// control when to use it via manual context management
|
|
495
|
+
return User.findAll({});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
For other frameworks (Express, Fastify, etc.), use the manual approach:
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
// Express example
|
|
504
|
+
import { identityMapContext } from '@cheetah.js/orm';
|
|
505
|
+
|
|
506
|
+
app.use(async (req, res, next) => {
|
|
507
|
+
await identityMapContext.run(async () => {
|
|
508
|
+
await next();
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
#### Performance Considerations
|
|
514
|
+
|
|
515
|
+
- The identity map uses O(1) lookup time via Map-based storage
|
|
516
|
+
- WeakRef ensures entities can be garbage collected when no longer referenced
|
|
517
|
+
- FinalizationRegistry automatically cleans up expired cache entries
|
|
518
|
+
- Per-request scope prevents memory buildup across requests
|
|
519
|
+
- No configuration needed - works transparently with existing queries
|
|
520
|
+
|
|
310
521
|
Is Required to implement the validate method, that returns a boolean value.
|
|
311
522
|
To use the Value Object in the Entity, just add the ValueObject type to the property:
|
|
312
523
|
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
import { FilterQuery } from "../driver/driver.interface";
|
|
1
2
|
export type IndexColumnMap<T> = {
|
|
2
3
|
[K in keyof T as K extends symbol ? never : K]: string;
|
|
3
4
|
};
|
|
4
5
|
export type IndexPredicate<T> = string | ((columns: IndexColumnMap<T>) => string);
|
|
6
|
+
export type IndexWhere<T> = IndexPredicate<T> | FilterQuery<T>;
|
|
5
7
|
export type IndexDefinition = {
|
|
6
8
|
name: string;
|
|
7
9
|
properties: string[];
|
|
8
|
-
where?:
|
|
10
|
+
where?: IndexWhere<any>;
|
|
9
11
|
};
|
|
10
12
|
type IndexOptions<T> = {
|
|
11
13
|
properties: (keyof T)[];
|
|
12
|
-
where?:
|
|
14
|
+
where?: IndexWhere<T>;
|
|
13
15
|
} | (keyof T)[] | undefined;
|
|
14
16
|
export declare function Index<T>(options?: IndexOptions<T>): ClassDecorator & PropertyDecorator;
|
|
15
17
|
export {};
|
package/dist/domain/entities.js
CHANGED
|
@@ -14,6 +14,7 @@ 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");
|
|
17
18
|
function buildIndexColumnMap(properties, relations) {
|
|
18
19
|
const map = mapPropertyColumns(properties);
|
|
19
20
|
addRelationColumns(map, relations);
|
|
@@ -67,7 +68,14 @@ function resolveIndexWhere(where, columnMap) {
|
|
|
67
68
|
if (typeof where === "string") {
|
|
68
69
|
return where;
|
|
69
70
|
}
|
|
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);
|
|
71
79
|
}
|
|
72
80
|
let EntityStorage = EntityStorage_1 = class EntityStorage {
|
|
73
81
|
constructor() {
|
package/dist/entry.js
CHANGED
|
@@ -5,4 +5,9 @@ const core_1 = require("@cheetah.js/core");
|
|
|
5
5
|
const orm_1 = require("./orm");
|
|
6
6
|
const orm_service_1 = require("./orm.service");
|
|
7
7
|
const entities_1 = require("./domain/entities");
|
|
8
|
-
|
|
8
|
+
const identity_map_middleware_1 = require("./middleware/identity-map.middleware");
|
|
9
|
+
exports.CheetahOrm = new core_1.Cheetah({
|
|
10
|
+
exports: [orm_1.Orm, orm_service_1.OrmService, entities_1.EntityStorage],
|
|
11
|
+
providers: [identity_map_middleware_1.IdentityMapMiddleware],
|
|
12
|
+
globalMiddlewares: [identity_map_middleware_1.IdentityMapMiddleware],
|
|
13
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class EntityKeyGenerator {
|
|
2
|
+
private entityStorage;
|
|
3
|
+
constructor();
|
|
4
|
+
generate(entityClass: Function, pk: any): string;
|
|
5
|
+
generateForEntity(entity: any): string;
|
|
6
|
+
extractPrimaryKey(entity: any): any;
|
|
7
|
+
private getPrimaryKeyName;
|
|
8
|
+
private serializePrimaryKey;
|
|
9
|
+
private getClassName;
|
|
10
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EntityKeyGenerator = void 0;
|
|
4
|
+
const entities_1 = require("../domain/entities");
|
|
5
|
+
class EntityKeyGenerator {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.entityStorage = entities_1.EntityStorage.getInstance();
|
|
8
|
+
}
|
|
9
|
+
generate(entityClass, pk) {
|
|
10
|
+
const className = this.getClassName(entityClass);
|
|
11
|
+
const keyValue = this.serializePrimaryKey(pk);
|
|
12
|
+
return `${className}:${keyValue}`;
|
|
13
|
+
}
|
|
14
|
+
generateForEntity(entity) {
|
|
15
|
+
const pk = this.extractPrimaryKey(entity);
|
|
16
|
+
return this.generate(entity.constructor, pk);
|
|
17
|
+
}
|
|
18
|
+
extractPrimaryKey(entity) {
|
|
19
|
+
const pkName = this.getPrimaryKeyName(entity.constructor);
|
|
20
|
+
return entity[pkName];
|
|
21
|
+
}
|
|
22
|
+
getPrimaryKeyName(entityClass) {
|
|
23
|
+
const options = this.entityStorage.get(entityClass);
|
|
24
|
+
if (!options) {
|
|
25
|
+
return 'id';
|
|
26
|
+
}
|
|
27
|
+
for (const prop in options.properties) {
|
|
28
|
+
const property = options.properties[prop];
|
|
29
|
+
if (property.options.isPrimary) {
|
|
30
|
+
return prop;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return 'id';
|
|
34
|
+
}
|
|
35
|
+
serializePrimaryKey(pk) {
|
|
36
|
+
if (Array.isArray(pk)) {
|
|
37
|
+
return pk.join(':');
|
|
38
|
+
}
|
|
39
|
+
return String(pk);
|
|
40
|
+
}
|
|
41
|
+
getClassName(entityClass) {
|
|
42
|
+
return entityClass.name;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.EntityKeyGenerator = EntityKeyGenerator;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class EntityRegistry {
|
|
2
|
+
private store;
|
|
3
|
+
private finalizationRegistry;
|
|
4
|
+
constructor();
|
|
5
|
+
private setupFinalizationRegistry;
|
|
6
|
+
get<T>(key: string): T | undefined;
|
|
7
|
+
set<T extends object>(key: string, entity: T): void;
|
|
8
|
+
has(key: string): boolean;
|
|
9
|
+
remove(key: string): void;
|
|
10
|
+
clear(): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EntityRegistry = void 0;
|
|
4
|
+
class EntityRegistry {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.store = new Map();
|
|
7
|
+
this.setupFinalizationRegistry();
|
|
8
|
+
}
|
|
9
|
+
setupFinalizationRegistry() {
|
|
10
|
+
this.finalizationRegistry = new FinalizationRegistry((key) => {
|
|
11
|
+
this.store.delete(key);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
get(key) {
|
|
15
|
+
const weakRef = this.store.get(key);
|
|
16
|
+
if (!weakRef) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const entity = weakRef.deref();
|
|
20
|
+
if (!entity) {
|
|
21
|
+
this.store.delete(key);
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return entity;
|
|
25
|
+
}
|
|
26
|
+
set(key, entity) {
|
|
27
|
+
const weakRef = new WeakRef(entity);
|
|
28
|
+
this.store.set(key, weakRef);
|
|
29
|
+
this.finalizationRegistry.register(entity, key, entity);
|
|
30
|
+
}
|
|
31
|
+
has(key) {
|
|
32
|
+
return this.get(key) !== undefined;
|
|
33
|
+
}
|
|
34
|
+
remove(key) {
|
|
35
|
+
this.store.delete(key);
|
|
36
|
+
}
|
|
37
|
+
clear() {
|
|
38
|
+
this.store.clear();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.EntityRegistry = EntityRegistry;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { IdentityMap } from './identity-map';
|
|
2
|
+
export declare class IdentityMapContext {
|
|
3
|
+
private storage;
|
|
4
|
+
constructor();
|
|
5
|
+
run<T>(callback: () => Promise<T>): Promise<T>;
|
|
6
|
+
getIdentityMap(): IdentityMap | undefined;
|
|
7
|
+
hasContext(): boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare const identityMapContext: IdentityMapContext;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.identityMapContext = exports.IdentityMapContext = void 0;
|
|
4
|
+
const async_hooks_1 = require("async_hooks");
|
|
5
|
+
const identity_map_1 = require("./identity-map");
|
|
6
|
+
class IdentityMapContext {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.storage = new async_hooks_1.AsyncLocalStorage();
|
|
9
|
+
}
|
|
10
|
+
run(callback) {
|
|
11
|
+
const identityMap = new identity_map_1.IdentityMap();
|
|
12
|
+
return this.storage.run(identityMap, callback);
|
|
13
|
+
}
|
|
14
|
+
getIdentityMap() {
|
|
15
|
+
return this.storage.getStore();
|
|
16
|
+
}
|
|
17
|
+
hasContext() {
|
|
18
|
+
return this.storage.getStore() !== undefined;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.IdentityMapContext = IdentityMapContext;
|
|
22
|
+
exports.identityMapContext = new IdentityMapContext();
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare class IdentityMapIntegration {
|
|
2
|
+
static getOrCreateInstance<T>(model: new () => T, primaryKey: any, factory: () => T): T;
|
|
3
|
+
static registerEntity<T extends object>(entity: T): void;
|
|
4
|
+
static getEntity<T>(entityClass: new () => T, primaryKey: any): T | undefined;
|
|
5
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IdentityMapIntegration = void 0;
|
|
4
|
+
const identity_map_context_1 = require("./identity-map-context");
|
|
5
|
+
class IdentityMapIntegration {
|
|
6
|
+
static getOrCreateInstance(model, primaryKey, factory) {
|
|
7
|
+
const identityMap = identity_map_context_1.identityMapContext.getIdentityMap();
|
|
8
|
+
if (!identityMap) {
|
|
9
|
+
return factory();
|
|
10
|
+
}
|
|
11
|
+
if (primaryKey === undefined || primaryKey === null) {
|
|
12
|
+
return factory();
|
|
13
|
+
}
|
|
14
|
+
const cached = identityMap.get(model, primaryKey);
|
|
15
|
+
if (cached) {
|
|
16
|
+
return cached;
|
|
17
|
+
}
|
|
18
|
+
const instance = factory();
|
|
19
|
+
return instance;
|
|
20
|
+
}
|
|
21
|
+
static registerEntity(entity) {
|
|
22
|
+
const identityMap = identity_map_context_1.identityMapContext.getIdentityMap();
|
|
23
|
+
if (!identityMap) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
identityMap.set(entity);
|
|
27
|
+
}
|
|
28
|
+
static getEntity(entityClass, primaryKey) {
|
|
29
|
+
const identityMap = identity_map_context_1.identityMapContext.getIdentityMap();
|
|
30
|
+
if (!identityMap) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return identityMap.get(entityClass, primaryKey);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.IdentityMapIntegration = IdentityMapIntegration;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class IdentityMap {
|
|
2
|
+
private registry;
|
|
3
|
+
private keyGenerator;
|
|
4
|
+
constructor();
|
|
5
|
+
get<T>(entityClass: Function, pk: any): T | undefined;
|
|
6
|
+
set<T extends object>(entity: T): void;
|
|
7
|
+
has(entityClass: Function, pk: any): boolean;
|
|
8
|
+
remove(entityClass: Function, pk: any): void;
|
|
9
|
+
clear(): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IdentityMap = void 0;
|
|
4
|
+
const entity_key_generator_1 = require("./entity-key-generator");
|
|
5
|
+
const entity_registry_1 = require("./entity-registry");
|
|
6
|
+
class IdentityMap {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.registry = new entity_registry_1.EntityRegistry();
|
|
9
|
+
this.keyGenerator = new entity_key_generator_1.EntityKeyGenerator();
|
|
10
|
+
}
|
|
11
|
+
get(entityClass, pk) {
|
|
12
|
+
const key = this.keyGenerator.generate(entityClass, pk);
|
|
13
|
+
return this.registry.get(key);
|
|
14
|
+
}
|
|
15
|
+
set(entity) {
|
|
16
|
+
const key = this.keyGenerator.generateForEntity(entity);
|
|
17
|
+
this.registry.set(key, entity);
|
|
18
|
+
}
|
|
19
|
+
has(entityClass, pk) {
|
|
20
|
+
const key = this.keyGenerator.generate(entityClass, pk);
|
|
21
|
+
return this.registry.has(key);
|
|
22
|
+
}
|
|
23
|
+
remove(entityClass, pk) {
|
|
24
|
+
const key = this.keyGenerator.generate(entityClass, pk);
|
|
25
|
+
this.registry.remove(key);
|
|
26
|
+
}
|
|
27
|
+
clear() {
|
|
28
|
+
this.registry.clear();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.IdentityMap = IdentityMap;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { IdentityMap } from './identity-map';
|
|
2
|
+
export { EntityKeyGenerator } from './entity-key-generator';
|
|
3
|
+
export { EntityRegistry } from './entity-registry';
|
|
4
|
+
export { IdentityMapContext, identityMapContext } from './identity-map-context';
|
|
5
|
+
export { IdentityMapIntegration } from './identity-map-integration';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IdentityMapIntegration = exports.identityMapContext = exports.IdentityMapContext = exports.EntityRegistry = exports.EntityKeyGenerator = exports.IdentityMap = void 0;
|
|
4
|
+
var identity_map_1 = require("./identity-map");
|
|
5
|
+
Object.defineProperty(exports, "IdentityMap", { enumerable: true, get: function () { return identity_map_1.IdentityMap; } });
|
|
6
|
+
var entity_key_generator_1 = require("./entity-key-generator");
|
|
7
|
+
Object.defineProperty(exports, "EntityKeyGenerator", { enumerable: true, get: function () { return entity_key_generator_1.EntityKeyGenerator; } });
|
|
8
|
+
var entity_registry_1 = require("./entity-registry");
|
|
9
|
+
Object.defineProperty(exports, "EntityRegistry", { enumerable: true, get: function () { return entity_registry_1.EntityRegistry; } });
|
|
10
|
+
var identity_map_context_1 = require("./identity-map-context");
|
|
11
|
+
Object.defineProperty(exports, "IdentityMapContext", { enumerable: true, get: function () { return identity_map_context_1.IdentityMapContext; } });
|
|
12
|
+
Object.defineProperty(exports, "identityMapContext", { enumerable: true, get: function () { return identity_map_context_1.identityMapContext; } });
|
|
13
|
+
var identity_map_integration_1 = require("./identity-map-integration");
|
|
14
|
+
Object.defineProperty(exports, "IdentityMapIntegration", { enumerable: true, get: function () { return identity_map_integration_1.IdentityMapIntegration; } });
|
package/dist/index.d.ts
CHANGED
|
@@ -23,3 +23,5 @@ export * from './common/email.vo';
|
|
|
23
23
|
export * from './common/uuid';
|
|
24
24
|
export * from './repository/Repository';
|
|
25
25
|
export { transactionContext } from './transaction/transaction-context';
|
|
26
|
+
export { IdentityMapMiddleware } from './middleware/identity-map.middleware';
|
|
27
|
+
export { identityMapContext } from './identity-map';
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.transactionContext = exports.EntityStorage = void 0;
|
|
17
|
+
exports.identityMapContext = exports.IdentityMapMiddleware = exports.transactionContext = exports.EntityStorage = void 0;
|
|
18
18
|
__exportStar(require("./decorators/entity.decorator"), exports);
|
|
19
19
|
__exportStar(require("./decorators/property.decorator"), exports);
|
|
20
20
|
__exportStar(require("./decorators/primary-key.decorator"), exports);
|
|
@@ -41,3 +41,7 @@ __exportStar(require("./common/uuid"), exports);
|
|
|
41
41
|
__exportStar(require("./repository/Repository"), exports);
|
|
42
42
|
var transaction_context_1 = require("./transaction/transaction-context");
|
|
43
43
|
Object.defineProperty(exports, "transactionContext", { enumerable: true, get: function () { return transaction_context_1.transactionContext; } });
|
|
44
|
+
var identity_map_middleware_1 = require("./middleware/identity-map.middleware");
|
|
45
|
+
Object.defineProperty(exports, "IdentityMapMiddleware", { enumerable: true, get: function () { return identity_map_middleware_1.IdentityMapMiddleware; } });
|
|
46
|
+
var identity_map_1 = require("./identity-map");
|
|
47
|
+
Object.defineProperty(exports, "identityMapContext", { enumerable: true, get: function () { return identity_map_1.identityMapContext; } });
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.IdentityMapMiddleware = void 0;
|
|
10
|
+
const core_1 = require("@cheetah.js/core");
|
|
11
|
+
const identity_map_1 = require("../identity-map");
|
|
12
|
+
let IdentityMapMiddleware = class IdentityMapMiddleware {
|
|
13
|
+
async handle(ctx, next) {
|
|
14
|
+
await identity_map_1.identityMapContext.run(async () => {
|
|
15
|
+
await next();
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
exports.IdentityMapMiddleware = IdentityMapMiddleware;
|
|
20
|
+
exports.IdentityMapMiddleware = IdentityMapMiddleware = __decorate([
|
|
21
|
+
(0, core_1.Injectable)()
|
|
22
|
+
], IdentityMapMiddleware);
|
|
@@ -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;
|
|
@@ -4,9 +4,14 @@ export declare class ModelTransformer {
|
|
|
4
4
|
private entityStorage;
|
|
5
5
|
constructor(entityStorage: EntityStorage);
|
|
6
6
|
transform<T>(model: any, statement: Statement<any>, data: any): T;
|
|
7
|
+
private registerInstancesInIdentityMap;
|
|
7
8
|
private createInstances;
|
|
8
9
|
private createInstance;
|
|
9
10
|
private addJoinedInstances;
|
|
11
|
+
private extractPrimaryKeyFromData;
|
|
12
|
+
private extractPrimaryKeyValue;
|
|
13
|
+
private getPrimaryKeyFromData;
|
|
14
|
+
private findPrimaryKeyProperty;
|
|
10
15
|
private buildOptionsMap;
|
|
11
16
|
private populateProperties;
|
|
12
17
|
private parseColumnKey;
|
|
@@ -3,39 +3,77 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ModelTransformer = void 0;
|
|
4
4
|
const value_object_1 = require("../common/value-object");
|
|
5
5
|
const utils_1 = require("../utils");
|
|
6
|
+
const identity_map_1 = require("../identity-map");
|
|
6
7
|
class ModelTransformer {
|
|
7
8
|
constructor(entityStorage) {
|
|
8
9
|
this.entityStorage = entityStorage;
|
|
9
10
|
}
|
|
10
11
|
transform(model, statement, data) {
|
|
11
|
-
const instanceMap = this.createInstances(model, statement);
|
|
12
|
+
const instanceMap = this.createInstances(model, statement, data);
|
|
12
13
|
const optionsMap = this.buildOptionsMap(instanceMap);
|
|
13
14
|
this.populateProperties(data, instanceMap, optionsMap);
|
|
14
15
|
this.linkJoinedEntities(statement, instanceMap, optionsMap);
|
|
15
16
|
this.resetChangedValues(instanceMap);
|
|
17
|
+
this.registerInstancesInIdentityMap(instanceMap);
|
|
16
18
|
return instanceMap[statement.alias];
|
|
17
19
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
registerInstancesInIdentityMap(instanceMap) {
|
|
21
|
+
Object.values(instanceMap).forEach(instance => {
|
|
22
|
+
identity_map_1.IdentityMapIntegration.registerEntity(instance);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
createInstances(model, statement, data) {
|
|
26
|
+
const primaryKey = this.extractPrimaryKeyFromData(model, statement.alias, data);
|
|
27
|
+
const instance = this.createInstance(model, primaryKey);
|
|
20
28
|
const instanceMap = {
|
|
21
29
|
[statement.alias]: instance,
|
|
22
30
|
};
|
|
23
31
|
if (statement.join) {
|
|
24
|
-
this.addJoinedInstances(statement, instanceMap);
|
|
32
|
+
this.addJoinedInstances(statement, instanceMap, data);
|
|
25
33
|
}
|
|
26
34
|
return instanceMap;
|
|
27
35
|
}
|
|
28
|
-
createInstance(model) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
createInstance(model, primaryKey) {
|
|
37
|
+
return identity_map_1.IdentityMapIntegration.getOrCreateInstance(model, primaryKey, () => {
|
|
38
|
+
const instance = new model();
|
|
39
|
+
instance.$_isPersisted = true;
|
|
40
|
+
return instance;
|
|
41
|
+
});
|
|
32
42
|
}
|
|
33
|
-
addJoinedInstances(statement, instanceMap) {
|
|
43
|
+
addJoinedInstances(statement, instanceMap, data) {
|
|
34
44
|
statement.join.forEach(join => {
|
|
35
|
-
const
|
|
45
|
+
const primaryKey = this.extractPrimaryKeyFromData(join.joinEntity, join.joinAlias, data);
|
|
46
|
+
const joinInstance = this.createInstance(join.joinEntity, primaryKey);
|
|
36
47
|
instanceMap[join.joinAlias] = joinInstance;
|
|
37
48
|
});
|
|
38
49
|
}
|
|
50
|
+
extractPrimaryKeyFromData(model, alias, data) {
|
|
51
|
+
const options = this.entityStorage.get(model);
|
|
52
|
+
if (!options) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
return this.extractPrimaryKeyValue(options, alias, data);
|
|
56
|
+
}
|
|
57
|
+
extractPrimaryKeyValue(options, alias, data) {
|
|
58
|
+
const pkProperty = this.findPrimaryKeyProperty(options);
|
|
59
|
+
if (!pkProperty) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return this.getPrimaryKeyFromData(pkProperty, alias, data);
|
|
63
|
+
}
|
|
64
|
+
getPrimaryKeyFromData(pkProperty, alias, data) {
|
|
65
|
+
const pkColumnName = pkProperty.options.columnName;
|
|
66
|
+
const pkKey = `${alias}_${pkColumnName}`;
|
|
67
|
+
return data[pkKey];
|
|
68
|
+
}
|
|
69
|
+
findPrimaryKeyProperty(options) {
|
|
70
|
+
for (const prop in options.properties) {
|
|
71
|
+
if (options.properties[prop].options.isPrimary) {
|
|
72
|
+
return options.properties[prop];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
39
77
|
buildOptionsMap(instanceMap) {
|
|
40
78
|
const optionsMap = new Map();
|
|
41
79
|
for (const [alias, instance] of Object.entries(instanceMap)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cheetah.js/orm",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.146",
|
|
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": "01d694ffaf36a91a0367d2d79c3f87f43402f62a"
|
|
59
59
|
}
|