@cheetah.js/orm 0.1.144 → 0.1.147
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 -55
- package/dist/decorators/unique.decorator.d.ts +9 -0
- package/dist/decorators/unique.decorator.js +44 -0
- package/dist/domain/entities.d.ts +2 -1
- package/dist/domain/entities.js +28 -0
- package/dist/driver/bun-mysql.driver.d.ts +7 -0
- package/dist/driver/bun-mysql.driver.js +11 -0
- package/dist/driver/bun-pg.driver.d.ts +7 -0
- package/dist/driver/bun-pg.driver.js +11 -0
- package/dist/driver/driver.interface.d.ts +12 -0
- 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 +3 -0
- package/dist/index.js +6 -1
- package/dist/middleware/identity-map.middleware.d.ts +4 -0
- package/dist/middleware/identity-map.middleware.js +22 -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,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
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type UniqueDefinition = {
|
|
2
|
+
name: string;
|
|
3
|
+
properties: string[];
|
|
4
|
+
};
|
|
5
|
+
type UniqueOptions<T> = {
|
|
6
|
+
properties: (keyof T)[];
|
|
7
|
+
} | (keyof T)[] | undefined;
|
|
8
|
+
export declare function Unique<T>(options?: UniqueOptions<T>): ClassDecorator & PropertyDecorator;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Unique = Unique;
|
|
4
|
+
const core_1 = require("@cheetah.js/core");
|
|
5
|
+
function getCtor(target) {
|
|
6
|
+
return typeof target === "function" ? target : target.constructor;
|
|
7
|
+
}
|
|
8
|
+
function buildFromOptions(options) {
|
|
9
|
+
const props = Array.isArray(options) ? options : options?.properties;
|
|
10
|
+
if (!props || props.length === 0) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const keys = props;
|
|
14
|
+
return {
|
|
15
|
+
name: `${keys.join('_')}_unique`,
|
|
16
|
+
properties: keys,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function buildFromProperty(propertyKey) {
|
|
20
|
+
const name = String(propertyKey);
|
|
21
|
+
return {
|
|
22
|
+
name: `${name}_unique`,
|
|
23
|
+
properties: [name],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function resolveUnique(options, propertyKey) {
|
|
27
|
+
const fromOptions = buildFromOptions(options);
|
|
28
|
+
if (fromOptions) {
|
|
29
|
+
return fromOptions;
|
|
30
|
+
}
|
|
31
|
+
if (!propertyKey) {
|
|
32
|
+
throw new Error("@Unique on class requires properties option");
|
|
33
|
+
}
|
|
34
|
+
return buildFromProperty(propertyKey);
|
|
35
|
+
}
|
|
36
|
+
function Unique(options) {
|
|
37
|
+
return (target, propertyKey) => {
|
|
38
|
+
const ctor = getCtor(target);
|
|
39
|
+
const uniques = [...(core_1.Metadata.get("uniques", ctor) || [])];
|
|
40
|
+
const unique = resolveUnique(options, propertyKey);
|
|
41
|
+
uniques.push(unique);
|
|
42
|
+
core_1.Metadata.set("uniques", uniques, ctor);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PropertyOptions } from "../decorators/property.decorator";
|
|
2
|
-
import { Relationship, SnapshotIndexInfo, SnapshotTable } from "../driver/driver.interface";
|
|
2
|
+
import { Relationship, SnapshotIndexInfo, SnapshotTable, SnapshotUniqueInfo } from "../driver/driver.interface";
|
|
3
3
|
export type Property = {
|
|
4
4
|
options: PropertyOptions;
|
|
5
5
|
type: Function;
|
|
@@ -10,6 +10,7 @@ export type Options = {
|
|
|
10
10
|
};
|
|
11
11
|
hideProperties: string[];
|
|
12
12
|
indexes?: SnapshotIndexInfo[];
|
|
13
|
+
uniques?: SnapshotUniqueInfo[];
|
|
13
14
|
relations: Relationship<any>[];
|
|
14
15
|
tableName: string;
|
|
15
16
|
hooks?: {
|
package/dist/domain/entities.js
CHANGED
|
@@ -77,6 +77,31 @@ function buildIndexWhere(where, columnMap) {
|
|
|
77
77
|
const builder = new index_condition_builder_1.IndexConditionBuilder(columnMap);
|
|
78
78
|
return builder.build(where);
|
|
79
79
|
}
|
|
80
|
+
function mapUniqueDefinitions(uniques, entityName, columnMap) {
|
|
81
|
+
return uniques.map((unique) => toSnapshotUnique(unique, entityName, columnMap));
|
|
82
|
+
}
|
|
83
|
+
function toSnapshotUnique(unique, entityName, columnMap) {
|
|
84
|
+
const columns = resolveUniqueColumns(unique, columnMap);
|
|
85
|
+
const uniqueName = resolveUniqueName(unique.name, entityName, columns);
|
|
86
|
+
return {
|
|
87
|
+
table: entityName,
|
|
88
|
+
uniqueName,
|
|
89
|
+
columnName: columns.join(","),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function resolveUniqueColumns(unique, columnMap) {
|
|
93
|
+
return unique.properties.map((propName) => resolveUniqueColumn(propName, columnMap));
|
|
94
|
+
}
|
|
95
|
+
function resolveUniqueColumn(propName, columnMap) {
|
|
96
|
+
const mapped = columnMap[propName];
|
|
97
|
+
if (mapped) {
|
|
98
|
+
return mapped;
|
|
99
|
+
}
|
|
100
|
+
return (0, utils_1.toSnakeCase)(propName);
|
|
101
|
+
}
|
|
102
|
+
function resolveUniqueName(name, entityName, columns) {
|
|
103
|
+
return `${columns.join("_")}_unique`;
|
|
104
|
+
}
|
|
80
105
|
let EntityStorage = EntityStorage_1 = class EntityStorage {
|
|
81
106
|
constructor() {
|
|
82
107
|
this.entities = new Map();
|
|
@@ -85,6 +110,7 @@ let EntityStorage = EntityStorage_1 = class EntityStorage {
|
|
|
85
110
|
add(entity, properties, relations, hooks) {
|
|
86
111
|
const entityName = entity.options?.tableName || (0, utils_1.toSnakeCase)(entity.target.name);
|
|
87
112
|
const indexes = core_1.Metadata.get("indexes", entity.target) || [];
|
|
113
|
+
const uniques = core_1.Metadata.get("uniques", entity.target) || [];
|
|
88
114
|
const columnMap = buildIndexColumnMap(properties, relations);
|
|
89
115
|
this.entities.set(entity.target, {
|
|
90
116
|
properties: properties,
|
|
@@ -93,6 +119,7 @@ let EntityStorage = EntityStorage_1 = class EntityStorage {
|
|
|
93
119
|
.map(([key]) => key),
|
|
94
120
|
relations,
|
|
95
121
|
indexes: mapIndexDefinitions(indexes, entityName, columnMap),
|
|
122
|
+
uniques: mapUniqueDefinitions(uniques, entityName, columnMap),
|
|
96
123
|
hooks,
|
|
97
124
|
tableName: entityName,
|
|
98
125
|
...entity.options,
|
|
@@ -116,6 +143,7 @@ let EntityStorage = EntityStorage_1 = class EntityStorage {
|
|
|
116
143
|
tableName: values.tableName,
|
|
117
144
|
schema: values.schema || "public",
|
|
118
145
|
indexes: values.indexes || [],
|
|
146
|
+
uniques: values.uniques || [],
|
|
119
147
|
columns: this.snapshotColumns(values),
|
|
120
148
|
};
|
|
121
149
|
}
|
|
@@ -26,6 +26,13 @@ export declare class BunMysqlDriver extends BunDriverBase implements DriverInter
|
|
|
26
26
|
name: string;
|
|
27
27
|
properties?: string[];
|
|
28
28
|
}, schema: string | undefined, tableName: string): string;
|
|
29
|
+
getCreateUniqueConstraint(unique: {
|
|
30
|
+
name: string;
|
|
31
|
+
properties?: string[];
|
|
32
|
+
}, schema: string | undefined, tableName: string): string;
|
|
33
|
+
getDropUniqueConstraint(unique: {
|
|
34
|
+
name: string;
|
|
35
|
+
}, schema: string | undefined, tableName: string): string;
|
|
29
36
|
getAlterTableType(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string;
|
|
30
37
|
getAlterTableDefaultInstruction(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string;
|
|
31
38
|
getAlterTablePrimaryKeyInstruction(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string;
|
|
@@ -141,6 +141,17 @@ class BunMysqlDriver extends bun_driver_base_1.BunDriverBase {
|
|
|
141
141
|
getDropIndex(index, schema, tableName) {
|
|
142
142
|
return `ALTER TABLE \`${schema}\`.\`${tableName}\` DROP INDEX \`${index.name}\`;`;
|
|
143
143
|
}
|
|
144
|
+
getCreateUniqueConstraint(unique, schema, tableName) {
|
|
145
|
+
const properties = unique.properties || [];
|
|
146
|
+
if (properties.length === 0) {
|
|
147
|
+
throw new Error("Unique properties are required.");
|
|
148
|
+
}
|
|
149
|
+
const columns = properties.map((prop) => `\`${prop}\``).join(', ');
|
|
150
|
+
return `ALTER TABLE \`${schema}\`.\`${tableName}\` ADD CONSTRAINT \`${unique.name}\` UNIQUE (${columns});`;
|
|
151
|
+
}
|
|
152
|
+
getDropUniqueConstraint(unique, schema, tableName) {
|
|
153
|
+
return `ALTER TABLE \`${schema}\`.\`${tableName}\` DROP INDEX \`${unique.name}\`;`;
|
|
154
|
+
}
|
|
144
155
|
getAlterTableType(schema, tableName, colName, colDiff) {
|
|
145
156
|
return `ALTER TABLE \`${schema}\`.\`${tableName}\` MODIFY COLUMN \`${colName}\` ${colDiff.colType}${colDiff.colLength ? `(${colDiff.colLength})` : ''};`;
|
|
146
157
|
}
|
|
@@ -25,6 +25,13 @@ export declare class BunPgDriver extends BunDriverBase implements DriverInterfac
|
|
|
25
25
|
name: string;
|
|
26
26
|
properties?: string[];
|
|
27
27
|
}, schema: string | undefined, tableName: string): string;
|
|
28
|
+
getCreateUniqueConstraint(unique: {
|
|
29
|
+
name: string;
|
|
30
|
+
properties?: string[];
|
|
31
|
+
}, schema: string | undefined, tableName: string): string;
|
|
32
|
+
getDropUniqueConstraint(unique: {
|
|
33
|
+
name: string;
|
|
34
|
+
}, schema: string | undefined, tableName: string): string;
|
|
28
35
|
getAlterTableType(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string;
|
|
29
36
|
getAlterTableDefaultInstruction(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string;
|
|
30
37
|
getAlterTablePrimaryKeyInstruction(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string;
|
|
@@ -114,6 +114,17 @@ class BunPgDriver extends bun_driver_base_1.BunDriverBase {
|
|
|
114
114
|
getDropIndex(index, schema, tableName) {
|
|
115
115
|
return this.getDropConstraint(index, schema, tableName);
|
|
116
116
|
}
|
|
117
|
+
getCreateUniqueConstraint(unique, schema, tableName) {
|
|
118
|
+
const properties = unique.properties || [];
|
|
119
|
+
if (properties.length === 0) {
|
|
120
|
+
throw new Error("Unique properties are required.");
|
|
121
|
+
}
|
|
122
|
+
const columns = properties.map((prop) => `"${prop}"`).join(', ');
|
|
123
|
+
return `ALTER TABLE "${schema}"."${tableName}" ADD CONSTRAINT "${unique.name}" UNIQUE (${columns});`;
|
|
124
|
+
}
|
|
125
|
+
getDropUniqueConstraint(unique, schema, tableName) {
|
|
126
|
+
return this.getDropConstraint(unique, schema, tableName);
|
|
127
|
+
}
|
|
117
128
|
getAlterTableType(schema, tableName, colName, colDiff) {
|
|
118
129
|
return `ALTER TABLE "${schema}"."${tableName}" ALTER COLUMN "${colName}" TYPE ${colDiff.colType}${colDiff.colLength ? `(${colDiff.colLength})` : ''};`;
|
|
119
130
|
}
|
|
@@ -20,6 +20,8 @@ export interface DriverInterface {
|
|
|
20
20
|
getAddColumn(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff, colDiffInstructions: string[]): void;
|
|
21
21
|
getDropColumn(colDiffInstructions: string[], schema: string | undefined, tableName: string, colName: string): void;
|
|
22
22
|
getDropIndex(index: IndexStatement, schema: string | undefined, tableName: string): string;
|
|
23
|
+
getCreateUniqueConstraint(unique: UniqueStatement, schema: string | undefined, tableName: string): string;
|
|
24
|
+
getDropUniqueConstraint(unique: UniqueStatement, schema: string | undefined, tableName: string): string;
|
|
23
25
|
getAlterTableType(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string;
|
|
24
26
|
getAlterTableDefaultInstruction(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string;
|
|
25
27
|
getAlterTablePrimaryKeyInstruction(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string;
|
|
@@ -136,6 +138,7 @@ export type SnapshotTable = {
|
|
|
136
138
|
schema?: string;
|
|
137
139
|
columns: ColumnsInfo[];
|
|
138
140
|
indexes: SnapshotIndexInfo[];
|
|
141
|
+
uniques?: SnapshotUniqueInfo[];
|
|
139
142
|
foreignKeys?: ForeignKeyInfo[];
|
|
140
143
|
};
|
|
141
144
|
export type SnapshotIndexInfo = {
|
|
@@ -144,11 +147,20 @@ export type SnapshotIndexInfo = {
|
|
|
144
147
|
columnName: string;
|
|
145
148
|
where?: string;
|
|
146
149
|
};
|
|
150
|
+
export type SnapshotUniqueInfo = {
|
|
151
|
+
table: string;
|
|
152
|
+
uniqueName: string;
|
|
153
|
+
columnName: string;
|
|
154
|
+
};
|
|
147
155
|
export type IndexStatement = {
|
|
148
156
|
name: string;
|
|
149
157
|
properties?: string[];
|
|
150
158
|
where?: string;
|
|
151
159
|
};
|
|
160
|
+
export type UniqueStatement = {
|
|
161
|
+
name: string;
|
|
162
|
+
properties?: string[];
|
|
163
|
+
};
|
|
152
164
|
export type ForeignKeyInfo = {
|
|
153
165
|
referencedTableName: string;
|
|
154
166
|
referencedColumnName: string;
|
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
|
@@ -3,6 +3,7 @@ export * from './decorators/property.decorator';
|
|
|
3
3
|
export * from './decorators/primary-key.decorator';
|
|
4
4
|
export * from './decorators/one-many.decorator';
|
|
5
5
|
export * from './decorators/index.decorator';
|
|
6
|
+
export * from './decorators/unique.decorator';
|
|
6
7
|
export * from './decorators/event-hook.decorator';
|
|
7
8
|
export * from './decorators/enum.decorator';
|
|
8
9
|
export * from './decorators/computed.decorator';
|
|
@@ -23,3 +24,5 @@ export * from './common/email.vo';
|
|
|
23
24
|
export * from './common/uuid';
|
|
24
25
|
export * from './repository/Repository';
|
|
25
26
|
export { transactionContext } from './transaction/transaction-context';
|
|
27
|
+
export { IdentityMapMiddleware } from './middleware/identity-map.middleware';
|
|
28
|
+
export { identityMapContext } from './identity-map';
|
package/dist/index.js
CHANGED
|
@@ -14,12 +14,13 @@ 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);
|
|
21
21
|
__exportStar(require("./decorators/one-many.decorator"), exports);
|
|
22
22
|
__exportStar(require("./decorators/index.decorator"), exports);
|
|
23
|
+
__exportStar(require("./decorators/unique.decorator"), exports);
|
|
23
24
|
__exportStar(require("./decorators/event-hook.decorator"), exports);
|
|
24
25
|
__exportStar(require("./decorators/enum.decorator"), exports);
|
|
25
26
|
__exportStar(require("./decorators/computed.decorator"), exports);
|
|
@@ -41,3 +42,7 @@ __exportStar(require("./common/uuid"), exports);
|
|
|
41
42
|
__exportStar(require("./repository/Repository"), exports);
|
|
42
43
|
var transaction_context_1 = require("./transaction/transaction-context");
|
|
43
44
|
Object.defineProperty(exports, "transactionContext", { enumerable: true, get: function () { return transaction_context_1.transactionContext; } });
|
|
45
|
+
var identity_map_middleware_1 = require("./middleware/identity-map.middleware");
|
|
46
|
+
Object.defineProperty(exports, "IdentityMapMiddleware", { enumerable: true, get: function () { return identity_map_middleware_1.IdentityMapMiddleware; } });
|
|
47
|
+
var identity_map_1 = require("./identity-map");
|
|
48
|
+
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);
|
|
@@ -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.147",
|
|
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": "9efbea28c5bb4bdc91e8aa6ba03a634f0a4ab00f"
|
|
59
59
|
}
|