metal-orm 1.0.8 → 1.0.9
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 +12 -1
- package/dist/decorators/index.cjs +2564 -0
- package/dist/decorators/index.cjs.map +1 -0
- package/dist/decorators/index.d.cts +53 -0
- package/dist/decorators/index.d.ts +53 -0
- package/dist/decorators/index.js +2530 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/index.cjs +4227 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +701 -0
- package/dist/index.d.ts +701 -0
- package/dist/index.js +4131 -0
- package/dist/index.js.map +1 -0
- package/dist/select-654m4qy8.d.cts +1522 -0
- package/dist/select-654m4qy8.d.ts +1522 -0
- package/package.json +27 -20
- package/src/codegen/typescript.ts +405 -393
- package/src/core/ast/aggregate-functions.ts +30 -0
- package/src/core/ast/builders.ts +43 -0
- package/src/core/ast/expression-builders.ts +310 -0
- package/src/core/ast/expression-nodes.ts +211 -0
- package/src/core/ast/expression-visitor.ts +99 -0
- package/src/core/ast/expression.ts +5 -0
- package/src/{utils → core/ast}/join-node.ts +20 -20
- package/src/{ast → core/ast}/join.ts +18 -18
- package/src/{ast → core/ast}/query.ts +113 -113
- package/src/core/ast/window-functions.ts +140 -0
- package/src/{dialect → core/dialect}/abstract.ts +94 -94
- package/src/{dialect → core/dialect}/mssql/index.ts +31 -31
- package/src/{dialect → core/dialect}/mysql/index.ts +31 -31
- package/src/{dialect → core/dialect}/postgres/index.ts +45 -45
- package/src/{dialect → core/dialect}/sqlite/index.ts +45 -45
- package/src/{constants → core/sql}/sql-operator-config.ts +39 -39
- package/src/decorators/bootstrap.ts +126 -0
- package/src/decorators/column.ts +78 -0
- package/src/decorators/entity.ts +36 -0
- package/src/decorators/index.ts +4 -0
- package/src/decorators/relations.ts +107 -0
- package/src/global.d.ts +1 -0
- package/src/index.ts +22 -22
- package/src/orm/db-executor.ts +11 -0
- package/src/orm/domain-event-bus.ts +52 -0
- package/src/{runtime → orm}/entity-meta.ts +52 -52
- package/src/orm/entity-metadata.ts +140 -0
- package/src/{runtime → orm}/entity.ts +252 -252
- package/src/{runtime → orm}/execute.ts +36 -36
- package/src/{runtime → orm}/hydration.ts +103 -103
- package/src/orm/identity-map.ts +37 -0
- package/src/{runtime → orm}/lazy-batch.ts +205 -205
- package/src/orm/orm-context.ts +154 -0
- package/src/orm/relation-change-processor.ts +140 -0
- package/src/{runtime → orm}/relations/belongs-to.ts +92 -92
- package/src/{runtime → orm}/relations/has-many.ts +111 -111
- package/src/{runtime → orm}/relations/many-to-many.ts +149 -149
- package/src/orm/runtime-types.ts +39 -0
- package/src/orm/transaction-runner.ts +17 -0
- package/src/orm/unit-of-work.ts +232 -0
- package/src/{builder/operations → query-builder}/column-selector.ts +78 -78
- package/src/{builder → query-builder}/delete-query-state.ts +38 -42
- package/src/{builder → query-builder}/delete.ts +46 -57
- package/src/{builder → query-builder}/hydration-manager.ts +87 -87
- package/src/{builder → query-builder}/hydration-planner.ts +182 -182
- package/src/{builder → query-builder}/insert-query-state.ts +51 -62
- package/src/{builder → query-builder}/insert.ts +48 -59
- package/src/{builder → query-builder}/query-ast-service.ts +208 -226
- package/src/{utils → query-builder}/raw-column-parser.ts +32 -32
- package/src/{builder → query-builder}/relation-conditions.ts +112 -112
- package/src/{builder/operations → query-builder}/relation-manager.ts +82 -82
- package/src/{builder → query-builder}/relation-projection-helper.ts +101 -101
- package/src/{builder → query-builder}/relation-service.ts +284 -284
- package/src/{builder → query-builder}/relation-types.ts +21 -21
- package/src/{builder → query-builder}/relation-utils.ts +12 -12
- package/src/{builder → query-builder}/select-query-builder-deps.ts +112 -94
- package/src/{builder → query-builder}/select-query-state.ts +179 -179
- package/src/{builder → query-builder}/select.ts +78 -69
- package/src/{builder → query-builder}/update-query-state.ts +55 -59
- package/src/{builder → query-builder}/update.ts +50 -61
- package/src/schema/column.ts +25 -25
- package/src/schema/relation.ts +116 -116
- package/src/schema/table.ts +34 -34
- package/src/schema/types.ts +76 -76
- package/.github/workflows/publish-metal-orm.yml +0 -38
- package/ROADMAP.md +0 -125
- package/docs/CHANGES.md +0 -104
- package/docs/advanced-features.md +0 -176
- package/docs/api-reference.md +0 -31
- package/docs/dml-operations.md +0 -156
- package/docs/getting-started.md +0 -171
- package/docs/hydration.md +0 -115
- package/docs/index.md +0 -36
- package/docs/multi-dialect-support.md +0 -59
- package/docs/query-builder.md +0 -135
- package/docs/runtime.md +0 -105
- package/docs/schema-definition.md +0 -112
- package/metadata.json +0 -5
- package/playground/api/playground-api.ts +0 -94
- package/playground/index.html +0 -15
- package/playground/src/App.css +0 -1
- package/playground/src/App.tsx +0 -114
- package/playground/src/components/CodeDisplay.tsx +0 -43
- package/playground/src/components/QueryExecutor.tsx +0 -189
- package/playground/src/components/ResultsTable.tsx +0 -67
- package/playground/src/components/ResultsTabs.tsx +0 -105
- package/playground/src/components/ScenarioList.tsx +0 -56
- package/playground/src/components/logo.svg +0 -45
- package/playground/src/data/scenarios.ts +0 -2
- package/playground/src/main.tsx +0 -9
- package/playground/src/services/PlaygroundApiService.ts +0 -60
- package/postcss.config.cjs +0 -5
- package/sql_sql-ansi-cheatsheet-2025.md +0 -264
- package/src/ast/expression.ts +0 -658
- package/src/builder/operations/cte-manager.ts +0 -34
- package/src/builder/operations/filter-manager.ts +0 -68
- package/src/builder/operations/join-manager.ts +0 -36
- package/src/builder/operations/pagination-manager.ts +0 -36
- package/src/playground/features/playground/api/types.ts +0 -16
- package/src/playground/features/playground/clients/MockClient.ts +0 -17
- package/src/playground/features/playground/clients/SqliteClient.ts +0 -57
- package/src/playground/features/playground/common/IDatabaseClient.ts +0 -10
- package/src/playground/features/playground/data/scenarios/aggregation.ts +0 -36
- package/src/playground/features/playground/data/scenarios/basics.ts +0 -25
- package/src/playground/features/playground/data/scenarios/edge_cases.ts +0 -57
- package/src/playground/features/playground/data/scenarios/filtering.ts +0 -94
- package/src/playground/features/playground/data/scenarios/hydration.ts +0 -27
- package/src/playground/features/playground/data/scenarios/index.ts +0 -29
- package/src/playground/features/playground/data/scenarios/ordering.ts +0 -25
- package/src/playground/features/playground/data/scenarios/pagination.ts +0 -16
- package/src/playground/features/playground/data/scenarios/relationships.ts +0 -75
- package/src/playground/features/playground/data/scenarios/types.ts +0 -70
- package/src/playground/features/playground/data/schema.ts +0 -91
- package/src/playground/features/playground/data/seed.ts +0 -104
- package/src/playground/features/playground/services/QueryExecutionService.ts +0 -121
- package/src/runtime/orm-context.ts +0 -539
- package/tests/belongs-to-many.test.ts +0 -57
- package/tests/between.test.ts +0 -43
- package/tests/case-expression.test.ts +0 -58
- package/tests/complex-exists.test.ts +0 -230
- package/tests/cte.test.ts +0 -118
- package/tests/dml.test.ts +0 -206
- package/tests/exists.test.ts +0 -127
- package/tests/like.test.ts +0 -33
- package/tests/orm-runtime.test.ts +0 -254
- package/tests/postgres.test.ts +0 -30
- package/tests/right-join.test.ts +0 -89
- package/tests/subquery-having.test.ts +0 -193
- package/tests/window-function.test.ts +0 -151
- package/tsconfig.json +0 -30
- package/tsup.config.ts +0 -10
- package/vite.config.ts +0 -22
- package/vitest.config.ts +0 -14
- /package/src/{constants → core/sql}/sql.ts +0 -0
- /package/src/{runtime → orm}/als.ts +0 -0
- /package/src/{utils → query-builder}/relation-alias.ts +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { TableHooks } from '../schema/table.js';
|
|
2
|
+
import { setEntityTableName } from '../orm/entity-metadata.js';
|
|
3
|
+
|
|
4
|
+
export interface EntityOptions {
|
|
5
|
+
tableName?: string;
|
|
6
|
+
hooks?: TableHooks;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const toSnakeCase = (value: string): string => {
|
|
10
|
+
return value
|
|
11
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
12
|
+
.replace(/[^a-z0-9_]+/gi, '_')
|
|
13
|
+
.replace(/__+/g, '_')
|
|
14
|
+
.replace(/^_|_$/g, '')
|
|
15
|
+
.toLowerCase();
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const deriveTableNameFromConstructor = (ctor: Function): string => {
|
|
19
|
+
const fallback = 'unknown';
|
|
20
|
+
const rawName = ctor.name || fallback;
|
|
21
|
+
const strippedName = rawName.replace(/Entity$/i, '');
|
|
22
|
+
const normalized = toSnakeCase(strippedName || rawName);
|
|
23
|
+
if (!normalized) {
|
|
24
|
+
return fallback;
|
|
25
|
+
}
|
|
26
|
+
return normalized.endsWith('s') ? normalized : `${normalized}s`;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function Entity(options: EntityOptions = {}) {
|
|
30
|
+
const decorator = <T extends new (...args: any[]) => any>(value: T) => {
|
|
31
|
+
const tableName = options.tableName ?? deriveTableNameFromConstructor(value);
|
|
32
|
+
setEntityTableName(value, tableName, options.hooks);
|
|
33
|
+
return value;
|
|
34
|
+
};
|
|
35
|
+
return decorator as unknown as ClassDecorator;
|
|
36
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { CascadeMode, RelationKinds } from '../schema/relation.js';
|
|
2
|
+
import {
|
|
3
|
+
addRelationMetadata,
|
|
4
|
+
EntityConstructor,
|
|
5
|
+
EntityOrTableTargetResolver,
|
|
6
|
+
RelationMetadata
|
|
7
|
+
} from '../orm/entity-metadata.js';
|
|
8
|
+
|
|
9
|
+
interface BaseRelationOptions {
|
|
10
|
+
target: EntityOrTableTargetResolver;
|
|
11
|
+
cascade?: CascadeMode;
|
|
12
|
+
localKey?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface HasManyOptions extends BaseRelationOptions {
|
|
16
|
+
foreignKey: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface BelongsToOptions extends BaseRelationOptions {
|
|
20
|
+
foreignKey: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface BelongsToManyOptions {
|
|
24
|
+
target: EntityOrTableTargetResolver;
|
|
25
|
+
pivotTable: EntityOrTableTargetResolver;
|
|
26
|
+
pivotForeignKeyToRoot: string;
|
|
27
|
+
pivotForeignKeyToTarget: string;
|
|
28
|
+
localKey?: string;
|
|
29
|
+
targetKey?: string;
|
|
30
|
+
pivotPrimaryKey?: string;
|
|
31
|
+
defaultPivotColumns?: string[];
|
|
32
|
+
cascade?: CascadeMode;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const normalizePropertyName = (name: string | symbol): string => {
|
|
36
|
+
if (typeof name === 'symbol') {
|
|
37
|
+
return name.description ?? name.toString();
|
|
38
|
+
}
|
|
39
|
+
return name;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const resolveConstructor = (instanceOrCtor: unknown): EntityConstructor | undefined => {
|
|
43
|
+
if (typeof instanceOrCtor === 'function') {
|
|
44
|
+
return instanceOrCtor as EntityConstructor;
|
|
45
|
+
}
|
|
46
|
+
if (instanceOrCtor && typeof (instanceOrCtor as any).constructor === 'function') {
|
|
47
|
+
return (instanceOrCtor as any).constructor as EntityConstructor;
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const registerRelation = (ctor: EntityConstructor, propertyName: string, metadata: RelationMetadata): void => {
|
|
53
|
+
addRelationMetadata(ctor, propertyName, metadata);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const createFieldDecorator = (
|
|
57
|
+
metadataFactory: (propertyName: string) => RelationMetadata
|
|
58
|
+
) => {
|
|
59
|
+
const decorator = (target: object, propertyKey: string | symbol) => {
|
|
60
|
+
const propertyName = normalizePropertyName(propertyKey);
|
|
61
|
+
const ctor = resolveConstructor(target);
|
|
62
|
+
if (!ctor) {
|
|
63
|
+
throw new Error('Unable to resolve constructor when registering relation metadata');
|
|
64
|
+
}
|
|
65
|
+
registerRelation(ctor, propertyName, metadataFactory(propertyName));
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return decorator as PropertyDecorator;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function HasMany(options: HasManyOptions) {
|
|
72
|
+
return createFieldDecorator(propertyName => ({
|
|
73
|
+
kind: RelationKinds.HasMany,
|
|
74
|
+
propertyKey: propertyName,
|
|
75
|
+
target: options.target,
|
|
76
|
+
foreignKey: options.foreignKey,
|
|
77
|
+
localKey: options.localKey,
|
|
78
|
+
cascade: options.cascade
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function BelongsTo(options: BelongsToOptions) {
|
|
83
|
+
return createFieldDecorator(propertyName => ({
|
|
84
|
+
kind: RelationKinds.BelongsTo,
|
|
85
|
+
propertyKey: propertyName,
|
|
86
|
+
target: options.target,
|
|
87
|
+
foreignKey: options.foreignKey,
|
|
88
|
+
localKey: options.localKey,
|
|
89
|
+
cascade: options.cascade
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function BelongsToMany(options: BelongsToManyOptions) {
|
|
94
|
+
return createFieldDecorator(propertyName => ({
|
|
95
|
+
kind: RelationKinds.BelongsToMany,
|
|
96
|
+
propertyKey: propertyName,
|
|
97
|
+
target: options.target,
|
|
98
|
+
pivotTable: options.pivotTable,
|
|
99
|
+
pivotForeignKeyToRoot: options.pivotForeignKeyToRoot,
|
|
100
|
+
pivotForeignKeyToTarget: options.pivotForeignKeyToTarget,
|
|
101
|
+
localKey: options.localKey,
|
|
102
|
+
targetKey: options.targetKey,
|
|
103
|
+
pivotPrimaryKey: options.pivotPrimaryKey,
|
|
104
|
+
defaultPivotColumns: options.defaultPivotColumns,
|
|
105
|
+
cascade: options.cascade
|
|
106
|
+
}));
|
|
107
|
+
}
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module 'tsconfig-paths/register';
|
package/src/index.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
export * from './schema/table';
|
|
3
|
-
export * from './schema/column';
|
|
4
|
-
export * from './schema/relation';
|
|
5
|
-
export * from './schema/types';
|
|
6
|
-
export * from './builder/select';
|
|
7
|
-
export * from './builder/insert';
|
|
8
|
-
export * from './builder/update';
|
|
9
|
-
export * from './builder/delete';
|
|
10
|
-
export * from './ast/expression';
|
|
11
|
-
export * from './dialect/mysql';
|
|
12
|
-
export * from './dialect/mssql';
|
|
13
|
-
export * from './dialect/sqlite';
|
|
14
|
-
export * from './
|
|
15
|
-
export * from './
|
|
16
|
-
export * from './codegen/typescript';
|
|
17
|
-
export * from './
|
|
18
|
-
export * from './
|
|
19
|
-
export * from './
|
|
20
|
-
export * from './
|
|
21
|
-
export * from './
|
|
22
|
-
export * from './
|
|
23
|
-
export * from './
|
|
2
|
+
export * from './schema/table.js';
|
|
3
|
+
export * from './schema/column.js';
|
|
4
|
+
export * from './schema/relation.js';
|
|
5
|
+
export * from './schema/types.js';
|
|
6
|
+
export * from './query-builder/select.js';
|
|
7
|
+
export * from './query-builder/insert.js';
|
|
8
|
+
export * from './query-builder/update.js';
|
|
9
|
+
export * from './query-builder/delete.js';
|
|
10
|
+
export * from './core/ast/expression.js';
|
|
11
|
+
export * from './core/dialect/mysql/index.js';
|
|
12
|
+
export * from './core/dialect/mssql/index.js';
|
|
13
|
+
export * from './core/dialect/sqlite/index.js';
|
|
14
|
+
export * from './orm/als.js';
|
|
15
|
+
export * from './orm/hydration.js';
|
|
16
|
+
export * from './codegen/typescript.js';
|
|
17
|
+
export * from './orm/orm-context.js';
|
|
18
|
+
export * from './orm/entity.js';
|
|
19
|
+
export * from './orm/lazy-batch.js';
|
|
20
|
+
export * from './orm/relations/has-many.js';
|
|
21
|
+
export * from './orm/relations/belongs-to.js';
|
|
22
|
+
export * from './orm/relations/many-to-many.js';
|
|
23
|
+
export * from './orm/execute.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type QueryResult = {
|
|
2
|
+
columns: string[];
|
|
3
|
+
values: unknown[][];
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export interface DbExecutor {
|
|
7
|
+
executeSql(sql: string, params?: unknown[]): Promise<QueryResult[]>;
|
|
8
|
+
beginTransaction?(): Promise<void>;
|
|
9
|
+
commitTransaction?(): Promise<void>;
|
|
10
|
+
rollbackTransaction?(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { HasDomainEvents, TrackedEntity } from './runtime-types.js';
|
|
2
|
+
|
|
3
|
+
export type DomainEventHandler<Context> = (event: any, ctx: Context) => Promise<void> | void;
|
|
4
|
+
|
|
5
|
+
export class DomainEventBus<Context> {
|
|
6
|
+
private readonly handlers = new Map<string, DomainEventHandler<Context>[]>();
|
|
7
|
+
|
|
8
|
+
constructor(initialHandlers?: Record<string, DomainEventHandler<Context>[]>) {
|
|
9
|
+
const handlers = initialHandlers ?? {};
|
|
10
|
+
Object.entries(handlers).forEach(([name, list]) => {
|
|
11
|
+
this.handlers.set(name, [...list]);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
register(name: string, handler: DomainEventHandler<Context>): void {
|
|
16
|
+
const existing = this.handlers.get(name) ?? [];
|
|
17
|
+
existing.push(handler);
|
|
18
|
+
this.handlers.set(name, existing);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async dispatch(trackedEntities: Iterable<TrackedEntity>, ctx: Context): Promise<void> {
|
|
22
|
+
for (const tracked of trackedEntities) {
|
|
23
|
+
const entity = tracked.entity as HasDomainEvents;
|
|
24
|
+
if (!entity.domainEvents || !entity.domainEvents.length) continue;
|
|
25
|
+
|
|
26
|
+
for (const event of entity.domainEvents) {
|
|
27
|
+
const eventName = this.getEventName(event);
|
|
28
|
+
const handlers = this.handlers.get(eventName);
|
|
29
|
+
if (!handlers) continue;
|
|
30
|
+
|
|
31
|
+
for (const handler of handlers) {
|
|
32
|
+
await handler(event, ctx);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
entity.domainEvents = [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private getEventName(event: any): string {
|
|
41
|
+
if (!event) return 'Unknown';
|
|
42
|
+
if (typeof event === 'string') return event;
|
|
43
|
+
return event.constructor?.name ?? 'Unknown';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const addDomainEvent = (entity: HasDomainEvents, event: any): void => {
|
|
48
|
+
if (!entity.domainEvents) {
|
|
49
|
+
entity.domainEvents = [];
|
|
50
|
+
}
|
|
51
|
+
entity.domainEvents.push(event);
|
|
52
|
+
};
|
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table';
|
|
2
|
-
import { OrmContext } from './orm-context';
|
|
3
|
-
import { RelationMap } from '../schema/types';
|
|
4
|
-
|
|
5
|
-
export const ENTITY_META = Symbol('EntityMeta');
|
|
6
|
-
|
|
7
|
-
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
8
|
-
|
|
9
|
-
export interface EntityMeta<TTable extends TableDef> {
|
|
10
|
-
ctx: OrmContext;
|
|
11
|
-
table: TTable;
|
|
12
|
-
lazyRelations: (keyof RelationMap<TTable>)[];
|
|
13
|
-
relationCache: Map<string, Promise<any>>;
|
|
14
|
-
relationHydration: Map<string, Map<string, any>>;
|
|
15
|
-
relationWrappers: Map<string, unknown>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const getHydrationRows = <TTable extends TableDef>(
|
|
19
|
-
meta: EntityMeta<TTable>,
|
|
20
|
-
relationName: string,
|
|
21
|
-
key: unknown
|
|
22
|
-
): Record<string, any>[] | undefined => {
|
|
23
|
-
const map = meta.relationHydration.get(relationName);
|
|
24
|
-
if (!map) return undefined;
|
|
25
|
-
const rows = map.get(toKey(key));
|
|
26
|
-
if (!rows) return undefined;
|
|
27
|
-
return Array.isArray(rows) ? rows : undefined;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export const getHydrationRecord = <TTable extends TableDef>(
|
|
31
|
-
meta: EntityMeta<TTable>,
|
|
32
|
-
relationName: string,
|
|
33
|
-
key: unknown
|
|
34
|
-
): Record<string, any> | undefined => {
|
|
35
|
-
const map = meta.relationHydration.get(relationName);
|
|
36
|
-
if (!map) return undefined;
|
|
37
|
-
const value = map.get(toKey(key));
|
|
38
|
-
if (!value) return undefined;
|
|
39
|
-
if (Array.isArray(value)) {
|
|
40
|
-
return value[0];
|
|
41
|
-
}
|
|
42
|
-
return value;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export const getEntityMeta = <TTable extends TableDef>(entity: any): EntityMeta<TTable> | undefined => {
|
|
46
|
-
if (!entity || typeof entity !== 'object') return undefined;
|
|
47
|
-
return (entity as any)[ENTITY_META];
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export const hasEntityMeta = (entity: any): entity is { [ENTITY_META]: EntityMeta<TableDef> } => {
|
|
51
|
-
return Boolean(getEntityMeta(entity));
|
|
52
|
-
};
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { OrmContext } from './orm-context.js';
|
|
3
|
+
import { RelationMap } from '../schema/types.js';
|
|
4
|
+
|
|
5
|
+
export const ENTITY_META = Symbol('EntityMeta');
|
|
6
|
+
|
|
7
|
+
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
8
|
+
|
|
9
|
+
export interface EntityMeta<TTable extends TableDef> {
|
|
10
|
+
ctx: OrmContext;
|
|
11
|
+
table: TTable;
|
|
12
|
+
lazyRelations: (keyof RelationMap<TTable>)[];
|
|
13
|
+
relationCache: Map<string, Promise<any>>;
|
|
14
|
+
relationHydration: Map<string, Map<string, any>>;
|
|
15
|
+
relationWrappers: Map<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const getHydrationRows = <TTable extends TableDef>(
|
|
19
|
+
meta: EntityMeta<TTable>,
|
|
20
|
+
relationName: string,
|
|
21
|
+
key: unknown
|
|
22
|
+
): Record<string, any>[] | undefined => {
|
|
23
|
+
const map = meta.relationHydration.get(relationName);
|
|
24
|
+
if (!map) return undefined;
|
|
25
|
+
const rows = map.get(toKey(key));
|
|
26
|
+
if (!rows) return undefined;
|
|
27
|
+
return Array.isArray(rows) ? rows : undefined;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const getHydrationRecord = <TTable extends TableDef>(
|
|
31
|
+
meta: EntityMeta<TTable>,
|
|
32
|
+
relationName: string,
|
|
33
|
+
key: unknown
|
|
34
|
+
): Record<string, any> | undefined => {
|
|
35
|
+
const map = meta.relationHydration.get(relationName);
|
|
36
|
+
if (!map) return undefined;
|
|
37
|
+
const value = map.get(toKey(key));
|
|
38
|
+
if (!value) return undefined;
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
return value[0];
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const getEntityMeta = <TTable extends TableDef>(entity: any): EntityMeta<TTable> | undefined => {
|
|
46
|
+
if (!entity || typeof entity !== 'object') return undefined;
|
|
47
|
+
return (entity as any)[ENTITY_META];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const hasEntityMeta = (entity: any): entity is { [ENTITY_META]: EntityMeta<TableDef> } => {
|
|
51
|
+
return Boolean(getEntityMeta(entity));
|
|
52
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { ColumnType, ColumnDef } from '../schema/column.js';
|
|
2
|
+
import { defineTable, TableDef, TableHooks } from '../schema/table.js';
|
|
3
|
+
import { CascadeMode, RelationKinds } from '../schema/relation.js';
|
|
4
|
+
|
|
5
|
+
export type EntityConstructor = new (...args: any[]) => any;
|
|
6
|
+
export type EntityOrTableTarget = EntityConstructor | TableDef;
|
|
7
|
+
export type EntityOrTableTargetResolver = EntityOrTableTarget | (() => EntityOrTableTarget);
|
|
8
|
+
|
|
9
|
+
export interface ColumnDefLike {
|
|
10
|
+
type: ColumnType;
|
|
11
|
+
args?: ColumnDef['args'];
|
|
12
|
+
primary?: boolean;
|
|
13
|
+
notNull?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface BaseRelationMetadata {
|
|
17
|
+
propertyKey: string;
|
|
18
|
+
target: EntityOrTableTargetResolver;
|
|
19
|
+
cascade?: CascadeMode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface HasManyRelationMetadata extends BaseRelationMetadata {
|
|
23
|
+
kind: typeof RelationKinds.HasMany;
|
|
24
|
+
foreignKey: string;
|
|
25
|
+
localKey?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface BelongsToRelationMetadata extends BaseRelationMetadata {
|
|
29
|
+
kind: typeof RelationKinds.BelongsTo;
|
|
30
|
+
foreignKey: string;
|
|
31
|
+
localKey?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface BelongsToManyRelationMetadata extends BaseRelationMetadata {
|
|
35
|
+
kind: typeof RelationKinds.BelongsToMany;
|
|
36
|
+
pivotTable: EntityOrTableTargetResolver;
|
|
37
|
+
pivotForeignKeyToRoot: string;
|
|
38
|
+
pivotForeignKeyToTarget: string;
|
|
39
|
+
localKey?: string;
|
|
40
|
+
targetKey?: string;
|
|
41
|
+
pivotPrimaryKey?: string;
|
|
42
|
+
defaultPivotColumns?: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type RelationMetadata =
|
|
46
|
+
| HasManyRelationMetadata
|
|
47
|
+
| BelongsToRelationMetadata
|
|
48
|
+
| BelongsToManyRelationMetadata;
|
|
49
|
+
|
|
50
|
+
export interface EntityMetadata {
|
|
51
|
+
target: EntityConstructor;
|
|
52
|
+
tableName: string;
|
|
53
|
+
columns: Record<string, ColumnDefLike>;
|
|
54
|
+
relations: Record<string, RelationMetadata>;
|
|
55
|
+
hooks?: TableHooks;
|
|
56
|
+
table?: TableDef;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const metadataMap = new Map<EntityConstructor, EntityMetadata>();
|
|
60
|
+
|
|
61
|
+
export const registerEntityMetadata = (meta: EntityMetadata): void => {
|
|
62
|
+
metadataMap.set(meta.target, meta);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const ensureEntityMetadata = (target: EntityConstructor): EntityMetadata => {
|
|
66
|
+
let meta = metadataMap.get(target);
|
|
67
|
+
if (!meta) {
|
|
68
|
+
meta = {
|
|
69
|
+
target,
|
|
70
|
+
tableName: target.name || 'unknown',
|
|
71
|
+
columns: {},
|
|
72
|
+
relations: {}
|
|
73
|
+
};
|
|
74
|
+
metadataMap.set(target, meta);
|
|
75
|
+
}
|
|
76
|
+
return meta;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const getEntityMetadata = (target: EntityConstructor): EntityMetadata | undefined => {
|
|
80
|
+
return metadataMap.get(target);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const getAllEntityMetadata = (): EntityMetadata[] => {
|
|
84
|
+
return Array.from(metadataMap.values());
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const clearEntityMetadata = (): void => {
|
|
88
|
+
metadataMap.clear();
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const addColumnMetadata = (
|
|
92
|
+
target: EntityConstructor,
|
|
93
|
+
propertyKey: string,
|
|
94
|
+
column: ColumnDefLike
|
|
95
|
+
): void => {
|
|
96
|
+
const meta = ensureEntityMetadata(target);
|
|
97
|
+
meta.columns[propertyKey] = { ...column };
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const addRelationMetadata = (
|
|
101
|
+
target: EntityConstructor,
|
|
102
|
+
propertyKey: string,
|
|
103
|
+
relation: RelationMetadata
|
|
104
|
+
): void => {
|
|
105
|
+
const meta = ensureEntityMetadata(target);
|
|
106
|
+
meta.relations[propertyKey] = relation;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const setEntityTableName = (
|
|
110
|
+
target: EntityConstructor,
|
|
111
|
+
tableName: string,
|
|
112
|
+
hooks?: TableHooks
|
|
113
|
+
): void => {
|
|
114
|
+
const meta = ensureEntityMetadata(target);
|
|
115
|
+
if (tableName && tableName.length > 0) {
|
|
116
|
+
meta.tableName = tableName;
|
|
117
|
+
}
|
|
118
|
+
if (hooks) {
|
|
119
|
+
meta.hooks = hooks;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const buildTableDef = (meta: EntityMetadata): TableDef => {
|
|
124
|
+
if (meta.table) {
|
|
125
|
+
return meta.table;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const columns = Object.entries(meta.columns).reduce<Record<string, ColumnDef>>((acc, [key, def]) => {
|
|
129
|
+
acc[key] = {
|
|
130
|
+
...def,
|
|
131
|
+
name: key,
|
|
132
|
+
table: meta.tableName
|
|
133
|
+
};
|
|
134
|
+
return acc;
|
|
135
|
+
}, {});
|
|
136
|
+
|
|
137
|
+
const table = defineTable(meta.tableName, columns, {}, meta.hooks);
|
|
138
|
+
meta.table = table;
|
|
139
|
+
return table;
|
|
140
|
+
};
|