@aurios/mizzle 1.1.1 → 1.1.3
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/.turbo/turbo-build.log +37 -0
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/dist/chunk-AQVECMXP.js +1 -0
- package/dist/chunk-DU7UPWBW.js +1 -0
- package/dist/chunk-GPYZK4WY.js +1 -0
- package/dist/chunk-NPPZW6VT.js +1 -0
- package/dist/chunk-TOYV2M4M.js +1 -0
- package/dist/chunk-UM3YF5EC.js +1 -0
- package/dist/columns.d.ts +1 -0
- package/dist/columns.js +1 -0
- package/dist/db-zHIHBm1E.d.ts +815 -0
- package/dist/db.d.ts +3 -0
- package/dist/db.js +1 -0
- package/dist/diff.d.ts +18 -0
- package/dist/diff.js +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +1 -0
- package/dist/introspection.d.ts +7 -0
- package/dist/introspection.js +1 -0
- package/dist/operators-BVreW0ky.d.ts +719 -0
- package/dist/snapshot.d.ts +24 -0
- package/dist/snapshot.js +1 -0
- package/dist/table.d.ts +1 -0
- package/dist/table.js +1 -0
- package/dist/transaction-RE7LXTGV.js +1 -0
- package/package.json +73 -24
- package/src/builders/base.ts +53 -56
- package/src/builders/batch-get.ts +63 -58
- package/src/builders/batch-write.ts +81 -78
- package/src/builders/delete.ts +46 -53
- package/src/builders/insert.ts +158 -150
- package/src/builders/query-promise.ts +26 -35
- package/src/builders/relational-builder.ts +214 -191
- package/src/builders/select.ts +250 -238
- package/src/builders/transaction.ts +170 -151
- package/src/builders/update.ts +198 -191
- package/src/columns/binary-set.ts +29 -38
- package/src/columns/binary.ts +25 -35
- package/src/columns/boolean.ts +25 -30
- package/src/columns/date.ts +57 -64
- package/src/columns/index.ts +15 -15
- package/src/columns/json.ts +39 -48
- package/src/columns/list.ts +26 -36
- package/src/columns/map.ts +26 -34
- package/src/columns/number-set.ts +29 -38
- package/src/columns/number.ts +33 -40
- package/src/columns/string-set.ts +38 -47
- package/src/columns/string.ts +37 -49
- package/src/columns/uuid.ts +26 -33
- package/src/core/client.ts +9 -9
- package/src/core/column-builder.ts +194 -220
- package/src/core/column.ts +127 -135
- package/src/core/diff.ts +40 -34
- package/src/core/errors.ts +20 -17
- package/src/core/introspection.ts +62 -58
- package/src/core/operations.ts +17 -23
- package/src/core/parser.ts +82 -88
- package/src/core/relations.ts +165 -152
- package/src/core/retry.ts +52 -52
- package/src/core/snapshot.ts +131 -130
- package/src/core/strategies.ts +222 -214
- package/src/core/table.ts +189 -202
- package/src/core/validation.ts +52 -52
- package/src/db.ts +216 -213
- package/src/expressions/actions.ts +26 -26
- package/src/expressions/builder.ts +62 -54
- package/src/expressions/operators.ts +48 -48
- package/src/expressions/update-builder.ts +79 -75
- package/src/index.ts +2 -1
- package/src/indexes.ts +8 -8
- package/test/batch-resilience.test.ts +138 -0
- package/test/builders/delete.test.ts +100 -0
- package/test/builders/insert.test.ts +216 -0
- package/test/builders/relational-types.test.ts +55 -0
- package/test/builders/relational.integration.test.ts +291 -0
- package/test/builders/relational.test.ts +66 -0
- package/test/builders/select.test.ts +411 -0
- package/test/builders/transaction-errors.test.ts +46 -0
- package/test/builders/transaction-execution.test.ts +99 -0
- package/test/builders/transaction-proxy.test.ts +41 -0
- package/test/builders/update-expression.test.ts +106 -0
- package/test/builders/update.test.ts +179 -0
- package/test/core/diff.test.ts +152 -0
- package/test/core/expressions.test.ts +64 -0
- package/test/core/introspection.test.ts +47 -0
- package/test/core/parser.test.ts +69 -0
- package/test/core/snapshot-gen.test.ts +155 -0
- package/test/core/snapshot.test.ts +52 -0
- package/test/date-column.test.ts +159 -0
- package/test/fluent-writes.integration.test.ts +148 -0
- package/test/integration-retry.test.ts +77 -0
- package/test/integration.test.ts +105 -0
- package/test/item-size-error.test.ts +16 -0
- package/test/item-size-validation.test.ts +82 -0
- package/test/item-size.test.ts +47 -0
- package/test/iterator-pagination.integration.test.ts +132 -0
- package/test/jsdoc-builders.test.ts +55 -0
- package/test/jsdoc-schema.test.ts +107 -0
- package/test/json-column.test.ts +51 -0
- package/test/metadata.test.ts +54 -0
- package/test/mizzle-package.test.ts +20 -0
- package/test/relational-centralized.test.ts +83 -0
- package/test/relational-definition.test.ts +75 -0
- package/test/relational-init.test.ts +30 -0
- package/test/relational-proxy.test.ts +52 -0
- package/test/relations.test.ts +45 -0
- package/test/resilience-config.test.ts +34 -0
- package/test/retry-handler.test.ts +63 -0
- package/test/transaction.integration.test.ts +153 -0
- package/test/unified-select.integration.test.ts +153 -0
- package/test/unified-update.integration.test.ts +139 -0
- package/test/update.integration.test.ts +132 -0
- package/tsconfig.json +12 -9
- package/tsup.config.ts +11 -11
- package/vitest.config.ts +8 -0
package/src/core/parser.ts
CHANGED
|
@@ -1,113 +1,107 @@
|
|
|
1
|
-
import { ENTITY_SYMBOLS, TABLE_SYMBOLS } from "@
|
|
1
|
+
import { ENTITY_SYMBOLS, TABLE_SYMBOLS } from "@repo/shared";
|
|
2
2
|
import type { InternalRelationalSchema } from "./relations";
|
|
3
3
|
import { type KeyStrategy } from "./strategies";
|
|
4
4
|
import { Entity } from "./table";
|
|
5
5
|
import { Column } from "./column";
|
|
6
6
|
|
|
7
7
|
interface MinimalPhysicalTable {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
[TABLE_SYMBOLS.PARTITION_KEY]: Column | undefined;
|
|
9
|
+
[TABLE_SYMBOLS.SORT_KEY]?: Column;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Parser for DynamoDB item collections (Single-Table Design).
|
|
14
14
|
*/
|
|
15
15
|
export class ItemCollectionParser {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
16
|
+
constructor(private schema: InternalRelationalSchema) {}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Parse a flat list of items into structured, nested objects.
|
|
20
|
+
*/
|
|
21
|
+
parse(
|
|
22
|
+
items: Record<string, unknown>[],
|
|
23
|
+
rootEntityName: string,
|
|
24
|
+
relations: Record<string, boolean | object> = {},
|
|
25
|
+
): Record<string, unknown>[] {
|
|
26
|
+
const rootEntityMeta = this.schema.entities[rootEntityName];
|
|
27
|
+
if (!rootEntityMeta) {
|
|
28
|
+
throw new Error(`Root entity ${rootEntityName} not found in schema`);
|
|
29
|
+
}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const primaryItems: Record<string, unknown>[] = [];
|
|
32
|
+
const otherItems: Record<string, unknown>[] = [];
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
// 1. Identify primary items vs related items
|
|
35
|
+
for (const item of items) {
|
|
36
|
+
if (this.isEntity(item, rootEntityMeta.entity)) {
|
|
37
|
+
primaryItems.push({ ...item });
|
|
38
|
+
} else {
|
|
39
|
+
otherItems.push(item);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const relConfig = rootEntityMeta.relations[relName];
|
|
49
|
-
if (!relConfig) continue;
|
|
50
|
-
|
|
51
|
-
const targetEntity = relConfig.config.to;
|
|
52
|
-
|
|
53
|
-
// Find items that match the target entity type
|
|
54
|
-
// In Single-Table Design Query, these items usually share the same PK
|
|
55
|
-
const relatedItems = otherItems.filter((item) =>
|
|
56
|
-
this.isEntity(item, targetEntity),
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
if (relatedItems.length > 0) {
|
|
60
|
-
if (relConfig.type === "many") {
|
|
61
|
-
primaryItem[relName] = relatedItems;
|
|
62
|
-
} else if (relConfig.type === "one") {
|
|
63
|
-
primaryItem[relName] = relatedItems[0] || null;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
43
|
+
// 2. For each primary item, find its relations
|
|
44
|
+
for (const primaryItem of primaryItems) {
|
|
45
|
+
for (const [relName, relOption] of Object.entries(relations)) {
|
|
46
|
+
if (!relOption) continue;
|
|
68
47
|
|
|
69
|
-
|
|
70
|
-
|
|
48
|
+
const relConfig = rootEntityMeta.relations[relName];
|
|
49
|
+
if (!relConfig) continue;
|
|
50
|
+
|
|
51
|
+
const targetEntity = relConfig.config.to;
|
|
71
52
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const pkMatch = this.matchStrategy(item[pkName], strategies.pk);
|
|
86
|
-
const skMatch = skName
|
|
87
|
-
? this.matchStrategy(item[skName], strategies.sk)
|
|
88
|
-
: true;
|
|
89
|
-
|
|
90
|
-
return pkMatch && skMatch;
|
|
53
|
+
// Find items that match the target entity type
|
|
54
|
+
// In Single-Table Design Query, these items usually share the same PK
|
|
55
|
+
const relatedItems = otherItems.filter((item) => this.isEntity(item, targetEntity));
|
|
56
|
+
|
|
57
|
+
if (relatedItems.length > 0) {
|
|
58
|
+
if (relConfig.type === "many") {
|
|
59
|
+
primaryItem[relName] = relatedItems;
|
|
60
|
+
} else if (relConfig.type === "one") {
|
|
61
|
+
primaryItem[relName] = relatedItems[0] || null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
91
65
|
}
|
|
92
66
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
*/
|
|
96
|
-
private matchStrategy(value: unknown, strategy?: KeyStrategy): boolean {
|
|
97
|
-
if (!strategy) return true;
|
|
98
|
-
if (value === undefined || value === null) return false;
|
|
67
|
+
return primaryItems;
|
|
68
|
+
}
|
|
99
69
|
|
|
100
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Check if an item matches an entity definition based on its key strategies.
|
|
72
|
+
*/
|
|
73
|
+
private isEntity(item: Record<string, unknown>, entity: Entity): boolean {
|
|
74
|
+
const strategies = entity[ENTITY_SYMBOLS.ENTITY_STRATEGY] as Record<string, KeyStrategy>;
|
|
75
|
+
const physicalTable = entity[ENTITY_SYMBOLS.PHYSICAL_TABLE] as unknown as MinimalPhysicalTable;
|
|
76
|
+
if (!physicalTable || !physicalTable[TABLE_SYMBOLS.PARTITION_KEY]) return false;
|
|
101
77
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
78
|
+
const pkName = physicalTable[TABLE_SYMBOLS.PARTITION_KEY]!.name;
|
|
79
|
+
const skName = physicalTable[TABLE_SYMBOLS.SORT_KEY]?.name;
|
|
105
80
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
81
|
+
const pkMatch = this.matchStrategy(item[pkName], strategies.pk);
|
|
82
|
+
const skMatch = skName ? this.matchStrategy(item[skName], strategies.sk) : true;
|
|
83
|
+
|
|
84
|
+
return pkMatch && skMatch;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Check if a value matches a key strategy.
|
|
89
|
+
*/
|
|
90
|
+
private matchStrategy(value: unknown, strategy?: KeyStrategy): boolean {
|
|
91
|
+
if (!strategy) return true;
|
|
92
|
+
if (value === undefined || value === null) return false;
|
|
110
93
|
|
|
111
|
-
|
|
94
|
+
const strValue = String(value);
|
|
95
|
+
|
|
96
|
+
if (strategy.type === "static") {
|
|
97
|
+
return strValue === strategy.segments[0];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (strategy.type === "prefix" || strategy.type === "composite") {
|
|
101
|
+
const prefix = strategy.segments[0];
|
|
102
|
+
return typeof prefix === "string" && strValue.startsWith(prefix);
|
|
112
103
|
}
|
|
104
|
+
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
113
107
|
}
|
package/src/core/relations.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RELATION_SYMBOLS } from "@
|
|
1
|
+
import { RELATION_SYMBOLS } from "@repo/shared";
|
|
2
2
|
import { Entity } from "./table";
|
|
3
3
|
import { Column } from "./column";
|
|
4
4
|
|
|
@@ -11,133 +11,135 @@ export type RelationType = "one" | "many";
|
|
|
11
11
|
* Configuration for a relationship.
|
|
12
12
|
*/
|
|
13
13
|
export interface RelationConfig {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The target entity of the relationship.
|
|
16
|
+
*/
|
|
17
|
+
to: Entity;
|
|
18
|
+
/**
|
|
19
|
+
* Local columns that link to the target entity.
|
|
20
|
+
*/
|
|
21
|
+
fields?: Column[];
|
|
22
|
+
/**
|
|
23
|
+
* Target columns that the local columns link to.
|
|
24
|
+
*/
|
|
25
|
+
references?: Column[];
|
|
26
|
+
/**
|
|
27
|
+
* Optional name for the relation.
|
|
28
|
+
*/
|
|
29
|
+
relationName?: string;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Represents a relationship between entities.
|
|
34
34
|
*/
|
|
35
35
|
export class Relation<TType extends RelationType = RelationType> {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
constructor(
|
|
37
|
+
public type: TType,
|
|
38
|
+
public config: RelationConfig,
|
|
39
|
+
) {}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Definition of all relations for a single entity.
|
|
44
44
|
*/
|
|
45
45
|
export interface RelationsDefinition<TEntity extends Entity = Entity> {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
/**
|
|
47
|
+
* The source entity.
|
|
48
|
+
*/
|
|
49
|
+
entity: TEntity;
|
|
50
|
+
/**
|
|
51
|
+
* Map of relation names to their configurations.
|
|
52
|
+
*/
|
|
53
|
+
config: Record<string, Relation>;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* Definition of relations for multiple entities in a schema.
|
|
58
58
|
*/
|
|
59
|
-
export interface MultiRelationsDefinition<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
export interface MultiRelationsDefinition<
|
|
60
|
+
TSchema extends Record<string, Entity> = Record<string, Entity>,
|
|
61
|
+
> {
|
|
62
|
+
/**
|
|
63
|
+
* The schema containing all entities.
|
|
64
|
+
*/
|
|
65
|
+
schema: TSchema;
|
|
66
|
+
/**
|
|
67
|
+
* Map of entity names to their relation configurations.
|
|
68
|
+
*/
|
|
69
|
+
definitions: {
|
|
70
|
+
[K in keyof TSchema]?: Record<string, Relation>;
|
|
71
|
+
};
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
/**
|
|
73
75
|
* Helpers provided to the defineRelations callback.
|
|
74
76
|
*/
|
|
75
77
|
export type RelationsHelpers<TSchema extends Record<string, Entity>> = {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Define a one-to-one relationship.
|
|
80
|
+
*/
|
|
81
|
+
one: {
|
|
82
|
+
[K in keyof TSchema]: (config?: Omit<RelationConfig, "to">) => Relation<"one">;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Define a one-to-many relationship.
|
|
86
|
+
*/
|
|
87
|
+
many: {
|
|
88
|
+
[K in keyof TSchema]: (config?: Omit<RelationConfig, "to">) => Relation<"many">;
|
|
89
|
+
};
|
|
88
90
|
} & {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Access an entity in the schema to get its columns.
|
|
93
|
+
*/
|
|
94
|
+
[K in keyof TSchema]: TSchema[K];
|
|
93
95
|
};
|
|
94
96
|
|
|
95
97
|
/**
|
|
96
98
|
* Callback function to define relations for a single entity.
|
|
97
99
|
*/
|
|
98
100
|
export type RelationsCallback = (helpers: {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Define a one-to-one relationship.
|
|
103
|
+
*/
|
|
104
|
+
one: (to: Entity, config?: Omit<RelationConfig, "to">) => Relation<"one">;
|
|
105
|
+
/**
|
|
106
|
+
* Define a one-to-many relationship.
|
|
107
|
+
*/
|
|
108
|
+
many: (to: Entity, config?: Omit<RelationConfig, "to">) => Relation<"many">;
|
|
107
109
|
}) => Record<string, Relation>;
|
|
108
110
|
|
|
109
111
|
/**
|
|
110
112
|
* Callback function to define relations for multiple entities.
|
|
111
113
|
*/
|
|
112
114
|
export type MultiRelationsCallback<TSchema extends Record<string, Entity>> = (
|
|
113
|
-
|
|
115
|
+
helpers: RelationsHelpers<TSchema>,
|
|
114
116
|
) => {
|
|
115
|
-
|
|
117
|
+
[K in keyof TSchema]?: Record<string, Relation>;
|
|
116
118
|
};
|
|
117
119
|
|
|
118
120
|
/**
|
|
119
121
|
* Defines relationships for a single entity.
|
|
120
|
-
*
|
|
122
|
+
*
|
|
121
123
|
* @example
|
|
122
124
|
* ```ts
|
|
123
125
|
* export const usersRelations = defineRelations(users, ({ many }) => ({
|
|
124
126
|
* posts: many(posts),
|
|
125
127
|
* }));
|
|
126
128
|
* ```
|
|
127
|
-
*
|
|
129
|
+
*
|
|
128
130
|
* @param entity The source entity.
|
|
129
131
|
* @param relations A callback function to define relations using provided helpers.
|
|
130
132
|
* @returns A relations definition for the entity.
|
|
131
133
|
*/
|
|
132
134
|
export function defineRelations<TEntity extends Entity>(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
+
entity: TEntity,
|
|
136
|
+
relations: RelationsCallback,
|
|
135
137
|
): RelationsDefinition<TEntity>;
|
|
136
138
|
|
|
137
139
|
/**
|
|
138
140
|
* Defines relationships for multiple entities in a centralized schema-aware way.
|
|
139
141
|
* This approach helps resolve circular dependencies between entities.
|
|
140
|
-
*
|
|
142
|
+
*
|
|
141
143
|
* @example
|
|
142
144
|
* ```ts
|
|
143
145
|
* export const relations = defineRelations({ users, posts }, (r) => ({
|
|
@@ -152,126 +154,137 @@ export function defineRelations<TEntity extends Entity>(
|
|
|
152
154
|
* },
|
|
153
155
|
* }));
|
|
154
156
|
* ```
|
|
155
|
-
*
|
|
157
|
+
*
|
|
156
158
|
* @param schema An object mapping names to entity definitions.
|
|
157
159
|
* @param relations A callback function to define relations for all entities in the schema.
|
|
158
160
|
* @returns A multi-entity relations definition.
|
|
159
161
|
*/
|
|
160
162
|
export function defineRelations<TSchema extends Record<string, Entity>>(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
+
schema: TSchema,
|
|
164
|
+
relations: MultiRelationsCallback<TSchema>,
|
|
163
165
|
): MultiRelationsDefinition<TSchema>;
|
|
164
166
|
|
|
165
167
|
/**
|
|
166
168
|
* Implementation of defineRelations.
|
|
167
169
|
*/
|
|
168
170
|
export function defineRelations(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
):
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
config,
|
|
182
|
-
[RELATION_SYMBOLS.RELATION_CONFIG]: true
|
|
183
|
-
};
|
|
184
|
-
} else {
|
|
185
|
-
// Multi-entity mode
|
|
186
|
-
const schema = first as Record<string, Entity>;
|
|
187
|
-
|
|
188
|
-
// Build helpers
|
|
189
|
-
const helpers: any = {
|
|
190
|
-
one: {},
|
|
191
|
-
many: {},
|
|
192
|
-
};
|
|
171
|
+
first: Entity | Record<string, Entity>,
|
|
172
|
+
callback: RelationsCallback | MultiRelationsCallback<any>,
|
|
173
|
+
): RelationsDefinition | MultiRelationsDefinition {
|
|
174
|
+
if (first instanceof Entity) {
|
|
175
|
+
// Single entity mode
|
|
176
|
+
const cb = callback as RelationsCallback;
|
|
177
|
+
const config = cb({
|
|
178
|
+
one: (to: Entity, config?: Omit<RelationConfig, "to">) =>
|
|
179
|
+
new Relation("one", { to, ...config }),
|
|
180
|
+
many: (to: Entity, config?: Omit<RelationConfig, "to">) =>
|
|
181
|
+
new Relation("many", { to, ...config }),
|
|
182
|
+
});
|
|
193
183
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
184
|
+
return {
|
|
185
|
+
entity: first,
|
|
186
|
+
config,
|
|
187
|
+
[RELATION_SYMBOLS.RELATION_CONFIG]: true,
|
|
188
|
+
};
|
|
189
|
+
} else {
|
|
190
|
+
// Multi-entity mode
|
|
191
|
+
const schema = first as Record<string, Entity>;
|
|
192
|
+
const cb = callback as MultiRelationsCallback<typeof schema>;
|
|
199
193
|
|
|
200
|
-
|
|
194
|
+
// Build helpers
|
|
195
|
+
const helpers: Record<string, unknown> = {
|
|
196
|
+
one: {},
|
|
197
|
+
many: {},
|
|
198
|
+
};
|
|
201
199
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
};
|
|
200
|
+
for (const [key, entity] of Object.entries(schema)) {
|
|
201
|
+
(helpers.one as Record<string, unknown>)[key] = (config?: Omit<RelationConfig, "to">) =>
|
|
202
|
+
new Relation("one", { to: entity, ...config });
|
|
203
|
+
(helpers.many as Record<string, unknown>)[key] = (config?: Omit<RelationConfig, "to">) =>
|
|
204
|
+
new Relation("many", { to: entity, ...config });
|
|
205
|
+
helpers[key] = entity;
|
|
207
206
|
}
|
|
207
|
+
|
|
208
|
+
const definitions = cb(helpers as unknown as RelationsHelpers<typeof schema>);
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
schema,
|
|
212
|
+
definitions: definitions as Record<string, Record<string, Relation>>,
|
|
213
|
+
[RELATION_SYMBOLS.RELATION_CONFIG]: true,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
208
216
|
}
|
|
209
217
|
|
|
210
218
|
/**
|
|
211
219
|
* Metadata for an entity and its relationships.
|
|
212
220
|
*/
|
|
213
221
|
export interface EntityMetadata {
|
|
214
|
-
|
|
215
|
-
|
|
222
|
+
entity: Entity;
|
|
223
|
+
relations: Record<string, Relation>;
|
|
216
224
|
}
|
|
217
225
|
|
|
218
226
|
/**
|
|
219
227
|
* Internal relational schema mapping entity names to their metadata.
|
|
220
228
|
*/
|
|
221
229
|
export interface InternalRelationalSchema {
|
|
222
|
-
|
|
230
|
+
entities: Record<string, EntityMetadata>;
|
|
223
231
|
}
|
|
224
232
|
|
|
225
233
|
/**
|
|
226
234
|
* Extract metadata from a flat schema definition.
|
|
227
235
|
*/
|
|
228
236
|
export function extractMetadata(schema: Record<string, unknown>): InternalRelationalSchema {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
237
|
+
const metadata: InternalRelationalSchema = {
|
|
238
|
+
entities: {},
|
|
239
|
+
};
|
|
232
240
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
+
// First pass: identify entities
|
|
242
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
243
|
+
if (value instanceof Entity) {
|
|
244
|
+
metadata.entities[key] = {
|
|
245
|
+
entity: value,
|
|
246
|
+
relations: {},
|
|
247
|
+
};
|
|
241
248
|
}
|
|
249
|
+
}
|
|
242
250
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
251
|
+
// Second pass: identify relations
|
|
252
|
+
for (const [, value] of Object.entries(schema)) {
|
|
253
|
+
if (
|
|
254
|
+
value &&
|
|
255
|
+
typeof value === "object" &&
|
|
256
|
+
(value as Record<string | symbol, unknown>)[RELATION_SYMBOLS.RELATION_CONFIG]
|
|
257
|
+
) {
|
|
258
|
+
const relationConfig = value as Record<string | symbol, unknown>;
|
|
259
|
+
if (relationConfig.entity) {
|
|
260
|
+
// Single entity definition
|
|
261
|
+
const definition = value as RelationsDefinition;
|
|
262
|
+
const entityEntry = Object.entries(metadata.entities).find(
|
|
263
|
+
([_, meta]) => meta.entity === definition.entity,
|
|
264
|
+
);
|
|
252
265
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
+
if (entityEntry) {
|
|
267
|
+
const [, meta] = entityEntry;
|
|
268
|
+
meta.relations = { ...meta.relations, ...definition.config };
|
|
269
|
+
}
|
|
270
|
+
} else if (relationConfig.definitions) {
|
|
271
|
+
// Multi-entity definition
|
|
272
|
+
const multiDef = value as MultiRelationsDefinition;
|
|
273
|
+
for (const [entityName, relations] of Object.entries(multiDef.definitions)) {
|
|
274
|
+
// Try to find the entity in our metadata by matching the entity from the schema
|
|
275
|
+
const entityInSchema = multiDef.schema[entityName];
|
|
276
|
+
const metaEntry = Object.entries(metadata.entities).find(
|
|
277
|
+
([_, meta]) => meta.entity === entityInSchema,
|
|
278
|
+
);
|
|
266
279
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
}
|
|
280
|
+
if (metaEntry && relations) {
|
|
281
|
+
const [, meta] = metaEntry;
|
|
282
|
+
meta.relations = { ...meta.relations, ...relations };
|
|
283
|
+
}
|
|
273
284
|
}
|
|
285
|
+
}
|
|
274
286
|
}
|
|
287
|
+
}
|
|
275
288
|
|
|
276
|
-
|
|
289
|
+
return metadata;
|
|
277
290
|
}
|