@cheetah.js/orm 0.1.144 → 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 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,60 +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
- 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 |
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 |
213
215
  | ------ | ---- |--------------------------------------------------------------------------------------------|
214
216
  | nullable | boolean | Defines if the property is nullable. |
215
217
  | unique | boolean | Defines if the property is unique. |
@@ -333,6 +335,189 @@ await repo.find({ where: { name: 'John' }, cache: new Date(Date.now() + 60_000)
333
335
  // Infinite/driver-default cache
334
336
  await repo.find({ where: { name: 'John' }, cache: true });
335
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
+
336
521
  Is Required to implement the validate method, that returns a boolean value.
337
522
  To use the Value Object in the Entity, just add the ValueObject type to the property:
338
523
 
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
- exports.CheetahOrm = new core_1.Cheetah({ exports: [orm_1.Orm, orm_service_1.OrmService, entities_1.EntityStorage] });
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,4 @@
1
+ import { CheetahMiddleware, Context } from '@cheetah.js/core';
2
+ export declare class IdentityMapMiddleware implements CheetahMiddleware {
3
+ handle(ctx: Context, next: () => void): Promise<void>;
4
+ }
@@ -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);
@@ -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
- createInstances(model, statement) {
19
- const instance = this.createInstance(model);
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
- const instance = new model();
30
- instance.$_isPersisted = true;
31
- return instance;
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 joinInstance = this.createInstance(join.joinEntity);
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.144",
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": "73bccb890079ef872f5dcb9f9df1a1a9dfad5912"
58
+ "gitHead": "01d694ffaf36a91a0367d2d79c3f87f43402f62a"
59
59
  }