@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.
Files changed (116) hide show
  1. package/.turbo/turbo-build.log +37 -0
  2. package/LICENSE +21 -0
  3. package/README.md +57 -0
  4. package/dist/chunk-AQVECMXP.js +1 -0
  5. package/dist/chunk-DU7UPWBW.js +1 -0
  6. package/dist/chunk-GPYZK4WY.js +1 -0
  7. package/dist/chunk-NPPZW6VT.js +1 -0
  8. package/dist/chunk-TOYV2M4M.js +1 -0
  9. package/dist/chunk-UM3YF5EC.js +1 -0
  10. package/dist/columns.d.ts +1 -0
  11. package/dist/columns.js +1 -0
  12. package/dist/db-zHIHBm1E.d.ts +815 -0
  13. package/dist/db.d.ts +3 -0
  14. package/dist/db.js +1 -0
  15. package/dist/diff.d.ts +18 -0
  16. package/dist/diff.js +1 -0
  17. package/dist/index.d.ts +42 -0
  18. package/dist/index.js +1 -0
  19. package/dist/introspection.d.ts +7 -0
  20. package/dist/introspection.js +1 -0
  21. package/dist/operators-BVreW0ky.d.ts +719 -0
  22. package/dist/snapshot.d.ts +24 -0
  23. package/dist/snapshot.js +1 -0
  24. package/dist/table.d.ts +1 -0
  25. package/dist/table.js +1 -0
  26. package/dist/transaction-RE7LXTGV.js +1 -0
  27. package/package.json +73 -24
  28. package/src/builders/base.ts +53 -56
  29. package/src/builders/batch-get.ts +63 -58
  30. package/src/builders/batch-write.ts +81 -78
  31. package/src/builders/delete.ts +46 -53
  32. package/src/builders/insert.ts +158 -150
  33. package/src/builders/query-promise.ts +26 -35
  34. package/src/builders/relational-builder.ts +214 -191
  35. package/src/builders/select.ts +250 -238
  36. package/src/builders/transaction.ts +170 -151
  37. package/src/builders/update.ts +198 -191
  38. package/src/columns/binary-set.ts +29 -38
  39. package/src/columns/binary.ts +25 -35
  40. package/src/columns/boolean.ts +25 -30
  41. package/src/columns/date.ts +57 -64
  42. package/src/columns/index.ts +15 -15
  43. package/src/columns/json.ts +39 -48
  44. package/src/columns/list.ts +26 -36
  45. package/src/columns/map.ts +26 -34
  46. package/src/columns/number-set.ts +29 -38
  47. package/src/columns/number.ts +33 -40
  48. package/src/columns/string-set.ts +38 -47
  49. package/src/columns/string.ts +37 -49
  50. package/src/columns/uuid.ts +26 -33
  51. package/src/core/client.ts +9 -9
  52. package/src/core/column-builder.ts +194 -220
  53. package/src/core/column.ts +127 -135
  54. package/src/core/diff.ts +40 -34
  55. package/src/core/errors.ts +20 -17
  56. package/src/core/introspection.ts +62 -58
  57. package/src/core/operations.ts +17 -23
  58. package/src/core/parser.ts +82 -88
  59. package/src/core/relations.ts +165 -152
  60. package/src/core/retry.ts +52 -52
  61. package/src/core/snapshot.ts +131 -130
  62. package/src/core/strategies.ts +222 -214
  63. package/src/core/table.ts +189 -202
  64. package/src/core/validation.ts +52 -52
  65. package/src/db.ts +216 -213
  66. package/src/expressions/actions.ts +26 -26
  67. package/src/expressions/builder.ts +62 -54
  68. package/src/expressions/operators.ts +48 -48
  69. package/src/expressions/update-builder.ts +79 -75
  70. package/src/index.ts +2 -1
  71. package/src/indexes.ts +8 -8
  72. package/test/batch-resilience.test.ts +138 -0
  73. package/test/builders/delete.test.ts +100 -0
  74. package/test/builders/insert.test.ts +216 -0
  75. package/test/builders/relational-types.test.ts +55 -0
  76. package/test/builders/relational.integration.test.ts +291 -0
  77. package/test/builders/relational.test.ts +66 -0
  78. package/test/builders/select.test.ts +411 -0
  79. package/test/builders/transaction-errors.test.ts +46 -0
  80. package/test/builders/transaction-execution.test.ts +99 -0
  81. package/test/builders/transaction-proxy.test.ts +41 -0
  82. package/test/builders/update-expression.test.ts +106 -0
  83. package/test/builders/update.test.ts +179 -0
  84. package/test/core/diff.test.ts +152 -0
  85. package/test/core/expressions.test.ts +64 -0
  86. package/test/core/introspection.test.ts +47 -0
  87. package/test/core/parser.test.ts +69 -0
  88. package/test/core/snapshot-gen.test.ts +155 -0
  89. package/test/core/snapshot.test.ts +52 -0
  90. package/test/date-column.test.ts +159 -0
  91. package/test/fluent-writes.integration.test.ts +148 -0
  92. package/test/integration-retry.test.ts +77 -0
  93. package/test/integration.test.ts +105 -0
  94. package/test/item-size-error.test.ts +16 -0
  95. package/test/item-size-validation.test.ts +82 -0
  96. package/test/item-size.test.ts +47 -0
  97. package/test/iterator-pagination.integration.test.ts +132 -0
  98. package/test/jsdoc-builders.test.ts +55 -0
  99. package/test/jsdoc-schema.test.ts +107 -0
  100. package/test/json-column.test.ts +51 -0
  101. package/test/metadata.test.ts +54 -0
  102. package/test/mizzle-package.test.ts +20 -0
  103. package/test/relational-centralized.test.ts +83 -0
  104. package/test/relational-definition.test.ts +75 -0
  105. package/test/relational-init.test.ts +30 -0
  106. package/test/relational-proxy.test.ts +52 -0
  107. package/test/relations.test.ts +45 -0
  108. package/test/resilience-config.test.ts +34 -0
  109. package/test/retry-handler.test.ts +63 -0
  110. package/test/transaction.integration.test.ts +153 -0
  111. package/test/unified-select.integration.test.ts +153 -0
  112. package/test/unified-update.integration.test.ts +139 -0
  113. package/test/update.integration.test.ts +132 -0
  114. package/tsconfig.json +12 -9
  115. package/tsup.config.ts +11 -11
  116. package/vitest.config.ts +8 -0
@@ -1,113 +1,107 @@
1
- import { ENTITY_SYMBOLS, TABLE_SYMBOLS } from "@mizzle/shared";
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
- [TABLE_SYMBOLS.PARTITION_KEY]: Column;
9
- [TABLE_SYMBOLS.SORT_KEY]?: Column;
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
- 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
- }
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
- const primaryItems: Record<string, unknown>[] = [];
32
- const otherItems: Record<string, unknown>[] = [];
31
+ const primaryItems: Record<string, unknown>[] = [];
32
+ const otherItems: Record<string, unknown>[] = [];
33
33
 
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
- }
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
- // 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;
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
- return primaryItems;
70
- }
48
+ const relConfig = rootEntityMeta.relations[relName];
49
+ if (!relConfig) continue;
50
+
51
+ const targetEntity = relConfig.config.to;
71
52
 
72
- /**
73
- * Check if an item matches an entity definition based on its key strategies.
74
- */
75
- private isEntity(item: Record<string, unknown>, entity: Entity): boolean {
76
- const strategies = entity[ENTITY_SYMBOLS.ENTITY_STRATEGY] as Record<
77
- string,
78
- KeyStrategy
79
- >;
80
- const physicalTable = entity[ENTITY_SYMBOLS.PHYSICAL_TABLE] as unknown as MinimalPhysicalTable;
81
-
82
- const pkName = physicalTable[TABLE_SYMBOLS.PARTITION_KEY].name;
83
- const skName = physicalTable[TABLE_SYMBOLS.SORT_KEY]?.name;
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
- * Check if a value matches a key strategy.
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
- const strValue = String(value);
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
- if (strategy.type === "static") {
103
- return strValue === strategy.segments[0];
104
- }
78
+ const pkName = physicalTable[TABLE_SYMBOLS.PARTITION_KEY]!.name;
79
+ const skName = physicalTable[TABLE_SYMBOLS.SORT_KEY]?.name;
105
80
 
106
- if (strategy.type === "prefix" || strategy.type === "composite") {
107
- const prefix = strategy.segments[0];
108
- return typeof prefix === "string" && strValue.startsWith(prefix);
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
- return false;
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
  }
@@ -1,4 +1,4 @@
1
- import { RELATION_SYMBOLS } from "@mizzle/shared";
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
- * 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;
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
- constructor(
37
- public type: TType,
38
- public config: RelationConfig
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
- * The source entity.
48
- */
49
- entity: TEntity;
50
- /**
51
- * Map of relation names to their configurations.
52
- */
53
- config: Record<string, Relation>;
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<TSchema extends Record<string, Entity> = Record<string, Entity>> {
60
- /**
61
- * The schema containing all entities.
62
- */
63
- schema: TSchema;
64
- /**
65
- * Map of entity names to their relation configurations.
66
- */
67
- definitions: {
68
- [K in keyof TSchema]?: Record<string, Relation>;
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
- * Define a one-to-one relationship.
78
- */
79
- one: {
80
- [K in keyof TSchema]: (config?: Omit<RelationConfig, "to">) => Relation<"one">;
81
- };
82
- /**
83
- * Define a one-to-many relationship.
84
- */
85
- many: {
86
- [K in keyof TSchema]: (config?: Omit<RelationConfig, "to">) => Relation<"many">;
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
- * Access an entity in the schema to get its columns.
91
- */
92
- [K in keyof TSchema]: TSchema[K];
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
- * Define a one-to-one relationship.
101
- */
102
- one: (to: Entity, config?: Omit<RelationConfig, "to">) => Relation<"one">;
103
- /**
104
- * Define a one-to-many relationship.
105
- */
106
- many: (to: Entity, config?: Omit<RelationConfig, "to">) => Relation<"many">;
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
- helpers: RelationsHelpers<TSchema>
115
+ helpers: RelationsHelpers<TSchema>,
114
116
  ) => {
115
- [K in keyof TSchema]?: Record<string, Relation>;
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
- entity: TEntity,
134
- relations: RelationsCallback
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
- schema: TSchema,
162
- relations: MultiRelationsCallback<TSchema>
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
- first: Entity | Record<string, Entity>,
170
- callback: Function
171
- ): any {
172
- if (first instanceof Entity) {
173
- // Single entity mode
174
- const config = callback({
175
- one: (to: Entity, config: any) => new Relation("one", { to, ...config }),
176
- many: (to: Entity, config: any) => new Relation("many", { to, ...config }),
177
- });
178
-
179
- return {
180
- entity: first,
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
- for (const [key, entity] of Object.entries(schema)) {
195
- helpers.one[key] = (config: any) => new Relation("one", { to: entity, ...config });
196
- helpers.many[key] = (config: any) => new Relation("many", { to: entity, ...config });
197
- helpers[key] = entity;
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
- const definitions = callback(helpers);
194
+ // Build helpers
195
+ const helpers: Record<string, unknown> = {
196
+ one: {},
197
+ many: {},
198
+ };
201
199
 
202
- return {
203
- schema,
204
- definitions,
205
- [RELATION_SYMBOLS.RELATION_CONFIG]: true
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
- entity: Entity;
215
- relations: Record<string, Relation>;
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
- entities: Record<string, EntityMetadata>;
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
- const metadata: InternalRelationalSchema = {
230
- entities: {},
231
- };
237
+ const metadata: InternalRelationalSchema = {
238
+ entities: {},
239
+ };
232
240
 
233
- // First pass: identify entities
234
- for (const [key, value] of Object.entries(schema)) {
235
- if (value instanceof Entity) {
236
- metadata.entities[key] = {
237
- entity: value,
238
- relations: {},
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
- // Second pass: identify relations
244
- for (const [, value] of Object.entries(schema)) {
245
- if (value && (value as any)[RELATION_SYMBOLS.RELATION_CONFIG]) {
246
- if ((value as any).entity) {
247
- // Single entity definition
248
- const definition = value as RelationsDefinition;
249
- const entityEntry = Object.entries(metadata.entities).find(
250
- ([_, meta]) => meta.entity === definition.entity
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
- if (entityEntry) {
254
- const [, meta] = entityEntry;
255
- meta.relations = { ...meta.relations, ...definition.config };
256
- }
257
- } else if ((value as any).definitions) {
258
- // Multi-entity definition
259
- const multiDef = value as MultiRelationsDefinition;
260
- for (const [entityName, relations] of Object.entries(multiDef.definitions)) {
261
- // Try to find the entity in our metadata by matching the entity from the schema
262
- const entityInSchema = multiDef.schema[entityName];
263
- const metaEntry = Object.entries(metadata.entities).find(
264
- ([_, meta]) => meta.entity === entityInSchema
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
- if (metaEntry && relations) {
268
- const [, meta] = metaEntry;
269
- meta.relations = { ...meta.relations, ...relations };
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
- return metadata;
289
+ return metadata;
277
290
  }