@bairock/lenz 0.0.14 → 0.0.16

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 (120) hide show
  1. package/README.md +119 -8
  2. package/dist/cli/commands/generate.d.ts.map +1 -1
  3. package/dist/cli/commands/generate.js +66 -79
  4. package/dist/cli/commands/generate.js.map +1 -1
  5. package/dist/cli/commands/init.d.ts.map +1 -1
  6. package/dist/cli/commands/init.js +22 -30
  7. package/dist/cli/commands/init.js.map +1 -1
  8. package/dist/cli/commands/studio.js +15 -21
  9. package/dist/cli/commands/studio.js.map +1 -1
  10. package/dist/cli/index.js +11 -18
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/config/index.d.ts +4 -6
  13. package/dist/config/index.d.ts.map +1 -1
  14. package/dist/config/index.js +31 -15
  15. package/dist/config/index.js.map +1 -1
  16. package/dist/engine/CodeGenerator.d.ts +8 -28
  17. package/dist/engine/CodeGenerator.d.ts.map +1 -1
  18. package/dist/engine/CodeGenerator.js +26 -1935
  19. package/dist/engine/CodeGenerator.js.map +1 -1
  20. package/dist/engine/GraphQLParseHelpers.d.ts +25 -0
  21. package/dist/engine/GraphQLParseHelpers.d.ts.map +1 -0
  22. package/dist/engine/GraphQLParseHelpers.js +128 -0
  23. package/dist/engine/GraphQLParseHelpers.js.map +1 -0
  24. package/dist/engine/GraphQLParser.d.ts +23 -8
  25. package/dist/engine/GraphQLParser.d.ts.map +1 -1
  26. package/dist/engine/GraphQLParser.js +165 -248
  27. package/dist/engine/GraphQLParser.js.map +1 -1
  28. package/dist/engine/GraphQLRelationAnalyzer.d.ts +10 -0
  29. package/dist/engine/GraphQLRelationAnalyzer.d.ts.map +1 -0
  30. package/dist/engine/GraphQLRelationAnalyzer.js +117 -0
  31. package/dist/engine/GraphQLRelationAnalyzer.js.map +1 -0
  32. package/dist/engine/LenzEngine.d.ts +4 -4
  33. package/dist/engine/LenzEngine.d.ts.map +1 -1
  34. package/dist/engine/LenzEngine.js +59 -46
  35. package/dist/engine/LenzEngine.js.map +1 -1
  36. package/dist/engine/SchemaRelationValidator.d.ts +15 -0
  37. package/dist/engine/SchemaRelationValidator.d.ts.map +1 -0
  38. package/dist/engine/SchemaRelationValidator.js +133 -0
  39. package/dist/engine/SchemaRelationValidator.js.map +1 -0
  40. package/dist/engine/SchemaValidator.d.ts +12 -11
  41. package/dist/engine/SchemaValidator.d.ts.map +1 -1
  42. package/dist/engine/SchemaValidator.js +163 -169
  43. package/dist/engine/SchemaValidator.js.map +1 -1
  44. package/dist/engine/directives.d.ts +10 -0
  45. package/dist/engine/directives.d.ts.map +1 -1
  46. package/dist/engine/directives.js +192 -47
  47. package/dist/engine/directives.js.map +1 -1
  48. package/dist/engine/generators/ClientGenerator.d.ts +7 -0
  49. package/dist/engine/generators/ClientGenerator.d.ts.map +1 -0
  50. package/dist/engine/generators/ClientGenerator.js +386 -0
  51. package/dist/engine/generators/ClientGenerator.js.map +1 -0
  52. package/dist/engine/generators/DelegateGenerator.d.ts +9 -0
  53. package/dist/engine/generators/DelegateGenerator.d.ts.map +1 -0
  54. package/dist/engine/generators/DelegateGenerator.js +453 -0
  55. package/dist/engine/generators/DelegateGenerator.js.map +1 -0
  56. package/dist/engine/generators/DelegateHelpers.d.ts +7 -0
  57. package/dist/engine/generators/DelegateHelpers.d.ts.map +1 -0
  58. package/dist/engine/generators/DelegateHelpers.js +144 -0
  59. package/dist/engine/generators/DelegateHelpers.js.map +1 -0
  60. package/dist/engine/generators/DelegateRelations.d.ts +11 -0
  61. package/dist/engine/generators/DelegateRelations.d.ts.map +1 -0
  62. package/dist/engine/generators/DelegateRelations.js +794 -0
  63. package/dist/engine/generators/DelegateRelations.js.map +1 -0
  64. package/dist/engine/generators/DelegateTemplateBody.d.ts +8 -0
  65. package/dist/engine/generators/DelegateTemplateBody.d.ts.map +1 -0
  66. package/dist/engine/generators/DelegateTemplateBody.js +776 -0
  67. package/dist/engine/generators/DelegateTemplateBody.js.map +1 -0
  68. package/dist/engine/generators/GenerateRuntimeErrors.d.ts +2 -0
  69. package/dist/engine/generators/GenerateRuntimeErrors.d.ts.map +1 -0
  70. package/dist/engine/generators/GenerateRuntimeErrors.js +140 -0
  71. package/dist/engine/generators/GenerateRuntimeErrors.js.map +1 -0
  72. package/dist/engine/generators/GenerateRuntimeIndex.d.ts +2 -0
  73. package/dist/engine/generators/GenerateRuntimeIndex.d.ts.map +1 -0
  74. package/dist/engine/generators/GenerateRuntimeIndex.js +21 -0
  75. package/dist/engine/generators/GenerateRuntimeIndex.js.map +1 -0
  76. package/dist/engine/generators/GenerateRuntimeLogger.d.ts +2 -0
  77. package/dist/engine/generators/GenerateRuntimeLogger.d.ts.map +1 -0
  78. package/dist/engine/generators/GenerateRuntimeLogger.js +125 -0
  79. package/dist/engine/generators/GenerateRuntimeLogger.js.map +1 -0
  80. package/dist/engine/generators/GenerateRuntimePagination.d.ts +2 -0
  81. package/dist/engine/generators/GenerateRuntimePagination.d.ts.map +1 -0
  82. package/dist/engine/generators/GenerateRuntimePagination.js +159 -0
  83. package/dist/engine/generators/GenerateRuntimePagination.js.map +1 -0
  84. package/dist/engine/generators/GenerateRuntimeQuery.d.ts +2 -0
  85. package/dist/engine/generators/GenerateRuntimeQuery.d.ts.map +1 -0
  86. package/dist/engine/generators/GenerateRuntimeQuery.js +427 -0
  87. package/dist/engine/generators/GenerateRuntimeQuery.js.map +1 -0
  88. package/dist/engine/generators/GenerateRuntimeRelations.d.ts +2 -0
  89. package/dist/engine/generators/GenerateRuntimeRelations.d.ts.map +1 -0
  90. package/dist/engine/generators/GenerateRuntimeRelations.js +130 -0
  91. package/dist/engine/generators/GenerateRuntimeRelations.js.map +1 -0
  92. package/dist/engine/generators/RuntimeGenerator.d.ts +16 -0
  93. package/dist/engine/generators/RuntimeGenerator.d.ts.map +1 -0
  94. package/dist/engine/generators/RuntimeGenerator.js +16 -0
  95. package/dist/engine/generators/RuntimeGenerator.js.map +1 -0
  96. package/dist/engine/generators/TypeFilterTypes.d.ts +2 -0
  97. package/dist/engine/generators/TypeFilterTypes.d.ts.map +1 -0
  98. package/dist/engine/generators/TypeFilterTypes.js +220 -0
  99. package/dist/engine/generators/TypeFilterTypes.js.map +1 -0
  100. package/dist/engine/generators/TypeGenerator.d.ts +16 -0
  101. package/dist/engine/generators/TypeGenerator.d.ts.map +1 -0
  102. package/dist/engine/generators/TypeGenerator.js +493 -0
  103. package/dist/engine/generators/TypeGenerator.js.map +1 -0
  104. package/dist/engine/generators/helpers.d.ts +13 -0
  105. package/dist/engine/generators/helpers.d.ts.map +1 -0
  106. package/dist/engine/generators/helpers.js +316 -0
  107. package/dist/engine/generators/helpers.js.map +1 -0
  108. package/dist/engine/index.d.ts +3 -3
  109. package/dist/engine/index.d.ts.map +1 -1
  110. package/dist/engine/index.js +3 -9
  111. package/dist/engine/index.js.map +1 -1
  112. package/dist/errors/index.d.ts +3 -0
  113. package/dist/errors/index.d.ts.map +1 -1
  114. package/dist/errors/index.js +25 -32
  115. package/dist/errors/index.js.map +1 -1
  116. package/dist/index.d.ts +4 -5
  117. package/dist/index.d.ts.map +1 -1
  118. package/dist/index.js +10 -30
  119. package/dist/index.js.map +1 -1
  120. package/package.json +6 -7
@@ -0,0 +1,453 @@
1
+ import { DelegateHelpers } from './DelegateHelpers.js';
2
+ import { DelegateRelations } from './DelegateRelations.js';
3
+ import { genCreateMethods, genUpdateMethods, genUpsert, genDeleteMethods, genAggregate, genGroupBy } from './DelegateTemplateBody.js';
4
+ export class DelegateGenerator {
5
+ constructor() {
6
+ this.helpers = new DelegateHelpers();
7
+ this.relations = new DelegateRelations();
8
+ }
9
+ generateModelsIndex(models) {
10
+ return models.map(model => `export { ${model.name}Delegate } from './${model.name}'`).join('\n');
11
+ }
12
+ generateModelFiles(models) {
13
+ const files = {};
14
+ const collectionMap = Object.fromEntries(models.map(m => [m.name, m.collectionName]));
15
+ for (const model of models) {
16
+ files[`models/${model.name}.ts`] = this.generateModelDelegate(model, collectionMap);
17
+ }
18
+ return files;
19
+ }
20
+ generateModelDelegate(model, collectionNameMap = {}) {
21
+ const hasCascade = model.relations.some(r => r.onDelete !== 'NoAction');
22
+ const hasCascadeUpdate = model.relations.some(r => r.onUpdate && r.onUpdate !== 'NoAction');
23
+ const hasCryptoGenerator = model.fields.some(f => f.defaultGenerator && f.defaultGenerator !== 'now');
24
+ const hasBigInt = model.fields.some(f => f.type === 'BigInt');
25
+ const bigIntFields = model.fields.filter(f => f.type === 'BigInt');
26
+ const idField = model.fields.find(f => f.isId && f.defaultGenerator);
27
+ const customIdGenerator = idField?.defaultGenerator || null;
28
+ const hasValidation = model.fields.some(f => f.validationRules);
29
+ const validationMethod = hasValidation ? this.helpers.generateValidationMethod(model) : '';
30
+ const validateCall = hasValidation ? `\n this.validateData(data)` : '';
31
+ const cascadeMethod = this.helpers.generateCascadeMethod(model);
32
+ const cascadeUpdateMethod = hasCascadeUpdate ? this.helpers.generateCascadeUpdateMethod(model) : '';
33
+ const cascadeUpdateCall = hasCascadeUpdate ? `\n await this.handleCascadeUpdate(formatted, preRelationDoc)` : '';
34
+ const cascadeDeleteCall = hasCascade ? `\n await this.handleCascadeDelete(doc)` : '';
35
+ const cascadeDeleteManyBody = hasCascade
36
+ ? ` const whereMany = args.where ? await this.processRelationFilters(args.where) : {}
37
+ const query = QueryBuilder.buildWhere(whereMany)
38
+ const dbOptions = args.session ? { session: args.session } : {}
39
+ const docs = this.collection.find(query, dbOptions)
40
+ let docCount = 0
41
+ while (await docs.hasNext()) {
42
+ const doc = await docs.next()
43
+ if (doc) {
44
+ await this.handleCascadeDelete(doc)
45
+ docCount++
46
+ }
47
+ }
48
+ const result = await this.collection.deleteMany(query, dbOptions)
49
+ return { count: result.deletedCount }`
50
+ : ` const whereMany = args.where ? await this.processRelationFilters(args.where) : {}
51
+ const query = QueryBuilder.buildWhere(whereMany)
52
+ const dbOptions = args.session ? { session: args.session } : {}
53
+ const result = await this.collection.deleteMany(query, dbOptions)
54
+ return { count: result.deletedCount }`;
55
+ return `// This file was auto-generated by Lenz. Do not edit manually.
56
+ // @generated
57
+
58
+ import { Collection, ObjectId, Document${hasBigInt ? ', Long' : ''} } from 'mongodb'
59
+ import type {
60
+ ${model.name},
61
+ ${model.name}CreateInput,
62
+ ${model.name}UpdateInput,
63
+ ${model.name}WhereInput,
64
+ ${model.name}WhereUniqueInput,
65
+ ${model.name}QueryOptions,
66
+ ${model.name}CreateArgs,
67
+ ${model.name}UpdateArgs,
68
+ ${model.name}DeleteArgs,
69
+ ${model.name}UpsertArgs,
70
+ ${model.name}FindUniqueArgs,
71
+ ${model.name}FindUniqueOrThrowArgs,
72
+ ${model.name}FindFirstOrThrowArgs,
73
+ ${model.name}AggregateArgs,
74
+ ${model.name}AggregateResult
75
+ } from '../types'
76
+ import { QueryBuilder } from '../runtime/query'
77
+ import { PaginationHelper } from '../runtime/pagination'
78
+ import { RelationResolver } from '../runtime/relations'
79
+ import { wrapMongoError } from '../runtime/errors'
80
+ import { NotFoundError } from '../runtime/errors'
81
+ ${hasCryptoGenerator ? "import crypto from 'crypto'\n" : ''}import type { LenzClient } from '../client'
82
+
83
+ export class ${model.name}Delegate {
84
+ constructor(private client: LenzClient) {}
85
+
86
+ private modelName = '${model.name}';
87
+
88
+ private async execute<T>(name: string, fn: () => Promise<T>, context?: any): Promise<T> {
89
+ const queryExt = this.client.getQueryExtension(this.modelName, name)
90
+ if (queryExt) {
91
+ return queryExt({
92
+ args: context || {},
93
+ query: async () => {
94
+ return this.executeRaw(name, fn)
95
+ }
96
+ })
97
+ }
98
+ return this.executeRaw(name, fn)
99
+ }
100
+
101
+ private async executeRaw<T>(name: string, fn: () => Promise<T>): Promise<T> {
102
+ const start = Date.now()
103
+ try {
104
+ let result = await fn()
105
+ const duration = Date.now() - start
106
+ this.client.logger.query({ operation: name, collection: this.modelName, query: '', duration, timestamp: new Date() })
107
+
108
+ const resultExts = this.client.getResultExtensions(this.modelName)
109
+ if (resultExts && result) {
110
+ if (Array.isArray(result)) {
111
+ result = result.map(item => this.applyResultExtensions(item, resultExts)) as any
112
+ } else if (typeof result === 'object' && result !== null) {
113
+ result = this.applyResultExtensions(result, resultExts) as any
114
+ }
115
+ }
116
+
117
+ return result
118
+ } catch (error) {
119
+ const duration = Date.now() - start
120
+ this.client.logger.query({ operation: name, collection: this.modelName, query: '', duration, timestamp: new Date() })
121
+ throw wrapMongoError(error, \`\${this.constructor.name}.\${name}\`)
122
+ }
123
+ }
124
+
125
+ private applyResultExtensions(item: any, exts: Array<{ name: string; compute: (parent: any) => any }>): any {
126
+ if (!item || typeof item !== 'object') return item
127
+ const result = { ...item }
128
+ for (const ext of exts) {
129
+ result[ext.name] = ext.compute(result)
130
+ }
131
+ return result
132
+ }
133
+
134
+ private readonly hiddenFields: string[] = ${JSON.stringify(model.fields.filter(f => f.isHidden).map(f => f.name))};
135
+
136
+ private readonly fieldMapping: Record<string, string> = ${JSON.stringify(Object.fromEntries(model.fields.filter(f => f.dbName).map(f => [f.name, f.dbName])))};
137
+
138
+ private readonly relationFilters: Array<{ field: string; foreignKey: string; target: string; targetCollection: string; isArray: boolean; type: string }> = ${JSON.stringify(model.relations
139
+ .filter(r => r.foreignKey && !r.joinCollection)
140
+ .map(r => ({ field: r.field, foreignKey: r.foreignKey, target: r.target, targetCollection: collectionNameMap[r.target] || r.target.toLowerCase() + 's', isArray: r.isForeignKeyArray, type: r.type })))};
141
+
142
+ private async processRelationFilters(where: any): Promise<any> {
143
+ if (!where || this.relationFilters.length === 0) return where;
144
+ let result = { ...where };
145
+ for (const rel of this.relationFilters) {
146
+ const relFilter = result[rel.field];
147
+ if (!relFilter) continue;
148
+
149
+ const targetCol = this.client.$db.collection(rel.targetCollection);
150
+
151
+ if (relFilter.is || relFilter.isNot) {
152
+ const subWhere = relFilter.is || relFilter.isNot;
153
+ const useIn = !!relFilter.is;
154
+ const isFkInTarget = (rel.type === 'oneToMany' && !rel.isArray);
155
+ const projection = isFkInTarget ? { [rel.foreignKey]: 1 } : { _id: 1 };
156
+ const matchingDocs = await targetCol.find(QueryBuilder.buildWhere(subWhere), { projection }).toArray();
157
+ const matchingIds = matchingDocs.map((d: any) => isFkInTarget ? d[rel.foreignKey]?.toString() : d._id.toString()).filter(Boolean);
158
+ const filterField = isFkInTarget ? 'id' : rel.foreignKey;
159
+ const mappedField = isFkInTarget ? filterField : (this.fieldMapping[filterField] || filterField);
160
+ if (matchingIds.length > 0 || !useIn) {
161
+ if (rel.isArray) {
162
+ result[mappedField] = useIn ? { $in: matchingIds } : { $nin: matchingIds };
163
+ } else {
164
+ result[mappedField] = useIn && matchingIds.length === 1
165
+ ? matchingIds[0]
166
+ : useIn ? { $in: matchingIds } : { $nin: matchingIds };
167
+ }
168
+ } else if (useIn) {
169
+ result._id = { $in: [] };
170
+ }
171
+ delete result[rel.field];
172
+ }
173
+
174
+ if (relFilter.some || relFilter.every || relFilter.none) {
175
+ const andConditions: any[] = [];
176
+ const isFkInTarget = (rel.type === 'oneToMany' && !rel.isArray);
177
+ const filterField = isFkInTarget ? 'id' : rel.foreignKey;
178
+ const mappedField = isFkInTarget ? filterField : (this.fieldMapping[filterField] || filterField);
179
+ if (relFilter.some) {
180
+ const projection = isFkInTarget ? { [rel.foreignKey]: 1 } : { _id: 1 };
181
+ const matchingDocs = await targetCol.find(QueryBuilder.buildWhere(relFilter.some), { projection }).toArray();
182
+ const matchingIds = matchingDocs.map((d: any) => isFkInTarget ? d[rel.foreignKey]?.toString() : d._id.toString()).filter(Boolean);
183
+ if (matchingIds.length > 0) {
184
+ andConditions.push({ [mappedField]: rel.isArray ? { $in: matchingIds } : matchingIds.length === 1 ? matchingIds[0] : { $in: matchingIds } });
185
+ } else {
186
+ andConditions.push({ _id: { $in: [] } });
187
+ }
188
+ }
189
+ if (relFilter.every) {
190
+ const projection = isFkInTarget ? { [rel.foreignKey]: 1 } : { _id: 1 };
191
+ const nonMatchingDocs = await targetCol.find(
192
+ { $nor: [QueryBuilder.buildWhere(relFilter.every)] },
193
+ { projection }
194
+ ).toArray();
195
+ const nonMatchingIds = nonMatchingDocs.map((d: any) => isFkInTarget ? d[rel.foreignKey]?.toString() : d._id.toString()).filter(Boolean);
196
+ if (nonMatchingIds.length > 0) {
197
+ andConditions.push({ [mappedField]: { $nin: nonMatchingIds } });
198
+ }
199
+ }
200
+ if (relFilter.none) {
201
+ const projection = isFkInTarget ? { [rel.foreignKey]: 1 } : { _id: 1 };
202
+ const matchingDocs = await targetCol.find(QueryBuilder.buildWhere(relFilter.none), { projection }).toArray();
203
+ const matchingIds = matchingDocs.map((d: any) => isFkInTarget ? d[rel.foreignKey]?.toString() : d._id.toString()).filter(Boolean);
204
+ if (matchingIds.length > 0) {
205
+ andConditions.push({ [mappedField]: { $nin: matchingIds } });
206
+ }
207
+ }
208
+ if (andConditions.length === 1) {
209
+ Object.assign(result, andConditions[0]);
210
+ } else if (andConditions.length > 1) {
211
+ result.$and = [...(result.$and || []), ...andConditions];
212
+ }
213
+ delete result[rel.field];
214
+ }
215
+ }
216
+ return result;
217
+ }
218
+
219
+ private readonly defaultValues = {
220
+ ${model.fields.filter(f => !f.isId && (f.defaultValue !== undefined || f.defaultGenerator !== undefined || (f.isRequired && f.isArray))).map(f => {
221
+ if (f.defaultGenerator === 'now')
222
+ return `${f.name}: new Date()`;
223
+ if (f.defaultGenerator === 'uuid')
224
+ return `${f.name}: crypto.randomUUID()`;
225
+ if (f.defaultGenerator === 'cuid')
226
+ return `${f.name}: 'cuid_' + crypto.randomUUID().slice(0, 8)`;
227
+ if (f.defaultGenerator === 'cuid2')
228
+ return `${f.name}: 'c' + crypto.randomUUID().replace(/-/g, '').toLowerCase().slice(0, 23)`;
229
+ if (f.defaultGenerator === 'ulid')
230
+ return `${f.name}: (() => { const ENC = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; let ts = Date.now(); let r = ''; for (let i = 0; i < 10; i++) { r = ENC[ts % 32] + r; ts = Math.floor(ts / 32); } const rand = crypto.randomUUID().replace(/-/g, '').toUpperCase(); for (let i = 0; i < 16; i++) { r += ENC[parseInt(rand[i % rand.length], 16) % 32]; } return r; })()`;
231
+ if (f.defaultValue !== undefined)
232
+ return `${f.name}: ${JSON.stringify(f.defaultValue)}`;
233
+ if (f.isRequired && f.isArray && !f.isId)
234
+ return `${f.name}: []`;
235
+ return '';
236
+ }).filter(Boolean).join(',\n ')}
237
+ };
238
+
239
+
240
+ private get collection(): Collection<Document> {
241
+ return this.client.$db.collection('${model.collectionName}')
242
+ }
243
+
244
+ async findUnique(args: ${model.name}FindUniqueArgs): Promise<${model.name} | null> {
245
+ return this.execute('findUnique', async () => {
246
+ const processedWhere = await this.processRelationFilters(args.where)
247
+ const query = QueryBuilder.buildWhere(processedWhere)
248
+ const options = QueryBuilder.buildOptions(args, this.hiddenFields, this.fieldMapping)
249
+
250
+ const doc = await this.collection.findOne(query, options)
251
+ if (!doc) return null
252
+
253
+ const formatted = RelationResolver.formatDocument(doc)
254
+
255
+ if (args.include) {
256
+ return await this.includeRelations(formatted, args.include)
257
+ }
258
+
259
+ return formatted
260
+ })
261
+ }
262
+
263
+ async findUniqueOrThrow(args: ${model.name}FindUniqueOrThrowArgs): Promise<${model.name}> {
264
+ return this.execute('findUniqueOrThrow', async () => {
265
+ const result = await this.findUnique(args)
266
+ if (!result) throw new NotFoundError('${model.name}', { where: args.where })
267
+ return result
268
+ })
269
+ }
270
+
271
+ async findMany(args?: ${model.name}QueryOptions): Promise<${model.name}[]> {
272
+ return this.execute('findMany', async () => {
273
+ const { cursor, ...otherArgs } = args || {}
274
+ let where = await this.processRelationFilters(args?.where || {})
275
+
276
+ if (cursor) {
277
+ const cursorCondition = QueryBuilder.buildCursorCondition(cursor, args?.orderBy)
278
+ where = {
279
+ ...where,
280
+ ...cursorCondition
281
+ }
282
+ }
283
+
284
+ const query = QueryBuilder.buildWhere(where)
285
+ const options = QueryBuilder.buildOptions(otherArgs || {}, this.hiddenFields, this.fieldMapping)
286
+
287
+ let docs: any[];
288
+
289
+ if (args?.distinct) {
290
+ const distinctFields = Array.isArray(args.distinct) ? args.distinct : [args.distinct]
291
+ const pipeline: any[] = [{ $match: query }]
292
+
293
+ const groupId: any = {}
294
+ for (const field of distinctFields) {
295
+ groupId[field] = \`$\${field}\`
296
+ }
297
+ pipeline.push({ $group: { _id: groupId, doc: { $first: '$$ROOT' } } })
298
+ pipeline.push({ $replaceRoot: { newRoot: '$doc' } })
299
+
300
+ if (options.sort) { pipeline.push({ $sort: options.sort }) }
301
+ if (options.skip !== undefined) { pipeline.push({ $skip: options.skip }) }
302
+ if (options.limit !== undefined) { pipeline.push({ $limit: options.limit }) }
303
+
304
+ const aggCursor = this.collection.aggregate(pipeline, options.session ? { session: options.session } : {})
305
+ docs = await aggCursor.toArray()
306
+ } else {
307
+ const mongoCursor = this.collection.find(query, options)
308
+ docs = await mongoCursor.toArray()
309
+ }
310
+
311
+ const formatted = docs.map(RelationResolver.formatDocument)
312
+
313
+ if (args?.include) {
314
+ return await this.batchIncludeRelations(formatted, args.include!)
315
+ }
316
+
317
+ return formatted
318
+ })
319
+ }
320
+
321
+ async findFirst(args?: ${model.name}QueryOptions): Promise<${model.name} | null> {
322
+ return this.execute('findFirst', async () => {
323
+ const results = await this.findMany({ ...args, take: 1 })
324
+ return results[0] || null
325
+ })
326
+ }
327
+
328
+ async findFirstOrThrow(args: ${model.name}FindFirstOrThrowArgs): Promise<${model.name}> {
329
+ return this.execute('findFirstOrThrow', async () => {
330
+ const result = await this.findFirst(args)
331
+ if (!result) throw new NotFoundError('${model.name}', { where: args?.where })
332
+ return result
333
+ })
334
+ }
335
+
336
+ ${genCreateMethods(model, validateCall, hasBigInt, customIdGenerator)}
337
+
338
+ ${genUpdateMethods(model, hasBigInt, cascadeUpdateCall)}
339
+
340
+ ${genUpsert(model, hasBigInt, customIdGenerator)}
341
+
342
+ ${genDeleteMethods(model, cascadeDeleteCall, cascadeDeleteManyBody)}
343
+
344
+ async count(args?: { where?: ${model.name}WhereInput; session?: any }): Promise<number> {
345
+ return this.execute('count', async () => {
346
+ const whereCount = args?.where ? await this.processRelationFilters(args.where) : {}
347
+ const query = QueryBuilder.buildWhere(whereCount)
348
+ return await this.collection.countDocuments(query, args?.session ? { session: args.session } : {})
349
+ })
350
+ }
351
+
352
+ async findRaw(filter: any, options?: { session?: any }): Promise<any[]> {
353
+ return this.execute('findRaw', async () => {
354
+ return await this.collection.find(filter, options?.session ? { session: options.session } : {}).toArray()
355
+ })
356
+ }
357
+
358
+ async aggregateRaw(pipeline: any[], options?: { session?: any }): Promise<any[]> {
359
+ return this.execute('aggregateRaw', async () => {
360
+ return await this.collection.aggregate(pipeline, options?.session ? { session: options.session } : {}).toArray()
361
+ })
362
+ }
363
+
364
+ ${genAggregate(model)}
365
+
366
+ ${genGroupBy(model)}
367
+
368
+ private applySelect(document: any, select: any): any {
369
+ if (!select) {
370
+ if (this.hiddenFields.length === 0) return document;
371
+ const result = { ...document };
372
+ this.hiddenFields.forEach(field => {
373
+ delete result[field];
374
+ });
375
+ return result;
376
+ }
377
+
378
+ const projection = QueryBuilder.buildProjection(select, this.hiddenFields);
379
+ if (!projection) return document;
380
+
381
+ const result = { ...document };
382
+ for (const [field, value] of Object.entries(projection)) {
383
+ if (value === 0 && field in result) {
384
+ delete result[field];
385
+ }
386
+ }
387
+ return result;
388
+ }
389
+
390
+ private applyOmit(document: any, omit?: Record<string, boolean>): any {
391
+ if (!omit) return document;
392
+ const result = { ...document };
393
+ for (const [field, value] of Object.entries(omit)) {
394
+ if (value && field in result) {
395
+ delete result[field];
396
+ }
397
+ }
398
+ return result;
399
+ }
400
+
401
+ ${hasBigInt ? ` private convertBigIntFields(data: Record<string, any>): void {\n${bigIntFields.map(f => ` if (typeof data.${f.name} === 'bigint') data.${f.name} = Long.fromBigInt(data.${f.name})`).join('\n')}\n }\n\n` : ''} public async includeRelations(document: any, include: any): Promise<any> {
402
+ const result = { ...document }
403
+ if (!include || typeof include !== 'object') {
404
+ return result
405
+ }
406
+
407
+ if (include._count && include._count.select) {
408
+ const counts: Record<string, number> = {}
409
+ for (const [relField, shouldCount] of Object.entries(include._count.select)) {
410
+ if (shouldCount) {
411
+ ${this.relations.generateCountInIncludeCode(model)}
412
+ }
413
+ }
414
+ result._count = counts
415
+ }
416
+
417
+ ${this.relations.generateRelationInclusionCode(model)}
418
+
419
+ return result
420
+ }
421
+
422
+ private async batchIncludeRelations(documents: any[], include: any): Promise<any[]> {
423
+ if (!include || typeof include !== 'object' || documents.length === 0) {
424
+ return documents.map(d => ({ ...d }))
425
+ }
426
+ if (documents.length === 1) {
427
+ return [await this.includeRelations(documents[0], include)]
428
+ }
429
+
430
+ ${this.relations.generateBatchInclusionCode(model)}
431
+
432
+ return documents.map(d => ({ ...d }))
433
+ }
434
+
435
+ get $raw() {
436
+ return {
437
+ collection: this.collection,
438
+ find: async (filter: any) => await this.collection.find(filter).toArray(),
439
+ findOne: async (filter: any) => await this.collection.findOne(filter),
440
+ insertOne: async (doc: any) => await this.collection.insertOne(doc),
441
+ updateOne: async (filter: any, update: any) => await this.collection.updateOne(filter, update),
442
+ deleteOne: async (filter: any) => await this.collection.deleteOne(filter),
443
+ aggregate: async (pipeline: any[]) => await this.collection.aggregate(pipeline).toArray()
444
+ }
445
+ }
446
+ }
447
+ ${cascadeMethod}
448
+ ${cascadeUpdateMethod}
449
+ ${validationMethod}
450
+ `;
451
+ }
452
+ }
453
+ //# sourceMappingURL=DelegateGenerator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DelegateGenerator.js","sourceRoot":"","sources":["../../../src/engine/generators/DelegateGenerator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEtI,MAAM,OAAO,iBAAiB;IAA9B;QACU,YAAO,GAAG,IAAI,eAAe,EAAE,CAAC;QAChC,cAAS,GAAG,IAAI,iBAAiB,EAAE,CAAC;IAic9C,CAAC;IA/bC,mBAAmB,CAAC,MAAsB;QACxC,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,KAAK,CAAC,IAAI,sBAAsB,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnG,CAAC;IAED,kBAAkB,CAAC,MAAsB;QACvC,MAAM,KAAK,GAA2B,EAAE,CAAC;QACzC,MAAM,aAAa,GAAG,MAAM,CAAC,WAAW,CACtC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,CAC5C,CAAC;QACF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,KAAK,CAAC,UAAU,KAAK,CAAC,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,qBAAqB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QACtF,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,qBAAqB,CAAC,KAAmB,EAAE,oBAA4C,EAAE;QACvF,MAAM,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC;QACxE,MAAM,gBAAgB,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC;QAC5F,MAAM,kBAAkB,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,gBAAgB,KAAK,KAAK,CAAC,CAAC;QACtG,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;QAC9D,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;QACnE,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,gBAAgB,CAAC,CAAC;QACrE,MAAM,iBAAiB,GAAG,OAAO,EAAE,gBAAgB,IAAI,IAAI,CAAC;QAC5D,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;QAChE,MAAM,gBAAgB,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,wBAAwB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3F,MAAM,YAAY,GAAG,aAAa,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5E,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC;QAChE,MAAM,mBAAmB,GAAG,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,2BAA2B,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpG,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,CAAC,CAAC,mEAAmE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtH,MAAM,iBAAiB,GAAG,UAAU,CAAC,CAAC,CAAC,2CAA2C,CAAC,CAAC,CAAC,EAAE,CAAC;QACxF,MAAM,qBAAqB,GAAG,UAAU;YACtC,CAAC,CAAC;;;;;;;;;;;;;0CAakC;YACpC,CAAC,CAAC;;;;0CAIkC,CAAC;QAEvC,OAAO;;;yCAG8B,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;;IAE9D,KAAK,CAAC,IAAI;IACV,KAAK,CAAC,IAAI;IACV,KAAK,CAAC,IAAI;IACV,KAAK,CAAC,IAAI;IACV,KAAK,CAAC,IAAI;IACV,KAAK,CAAC,IAAI;IACV,KAAK,CAAC,IAAI;IACV,KAAK,CAAC,IAAI;IACV,KAAK,CAAC,IAAI;IACV,KAAK,CAAC,IAAI;IACV,KAAK,CAAC,IAAI;IACV,KAAK,CAAC,IAAI;IACV,KAAK,CAAC,IAAI;IACV,KAAK,CAAC,IAAI;IACV,KAAK,CAAC,IAAI;;;;;;;EAOZ,kBAAkB,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,EAAE;;eAE5C,KAAK,CAAC,IAAI;;;yBAGA,KAAK,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8CAgDW,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;;4DAEvD,IAAI,CAAC,SAAS,CACtE,MAAM,CAAC,WAAW,CAChB,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAO,CAAC,CAAC,CACjE,CACF;;+JAE4J,IAAI,CAAC,SAAS,CACzK,KAAK,CAAC,SAAS;aACZ,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC;aAC9C,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,UAAW,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,gBAAgB,EAAE,iBAAiB,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,GAAG,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAC1M;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAgFG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,YAAY,KAAK,SAAS,IAAI,CAAC,CAAC,gBAAgB,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;YAC/I,IAAI,CAAC,CAAC,gBAAgB,KAAK,KAAK;gBAAE,OAAO,GAAG,CAAC,CAAC,IAAI,cAAc,CAAC;YACjE,IAAI,CAAC,CAAC,gBAAgB,KAAK,MAAM;gBAAE,OAAO,GAAG,CAAC,CAAC,IAAI,uBAAuB,CAAC;YAC3E,IAAI,CAAC,CAAC,gBAAgB,KAAK,MAAM;gBAAE,OAAO,GAAG,CAAC,CAAC,IAAI,6CAA6C,CAAC;YACjG,IAAI,CAAC,CAAC,gBAAgB,KAAK,OAAO;gBAAE,OAAO,GAAG,CAAC,CAAC,IAAI,0EAA0E,CAAC;YAC/H,IAAI,CAAC,CAAC,gBAAgB,KAAK,MAAM;gBAAE,OAAO,GAAG,CAAC,CAAC,IAAI,oVAAoV,CAAC;YACxY,IAAI,CAAC,CAAC,YAAY,KAAK,SAAS;gBAAE,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC;YACxF,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI;gBAAE,OAAO,GAAG,CAAC,CAAC,IAAI,MAAM,CAAC;YACjE,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC;;;;;yCAKG,KAAK,CAAC,cAAc;;;2BAGlC,KAAK,CAAC,IAAI,4BAA4B,KAAK,CAAC,IAAI;;;;;;;;;;;;;;;;;;;kCAmBzC,KAAK,CAAC,IAAI,mCAAmC,KAAK,CAAC,IAAI;;;8CAG3C,KAAK,CAAC,IAAI;;;;;0BAK9B,KAAK,CAAC,IAAI,0BAA0B,KAAK,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAkD7C,KAAK,CAAC,IAAI,0BAA0B,KAAK,CAAC,IAAI;;;;;;;iCAOxC,KAAK,CAAC,IAAI,kCAAkC,KAAK,CAAC,IAAI;;;8CAGzC,KAAK,CAAC,IAAI;;;;;EAKtD,gBAAgB,CAAC,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,iBAAiB,CAAC;;EAEnE,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,iBAAiB,CAAC;;EAErD,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,iBAAiB,CAAC;;EAE9C,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,EAAE,qBAAqB,CAAC;;iCAElC,KAAK,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;EAoBzC,YAAY,CAAC,KAAK,CAAC;;EAEnB,UAAU,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmCjB,SAAS,CAAC,CAAC,CAAC,qEAAqE,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,uBAAuB,CAAC,CAAC,IAAI,uBAAuB,CAAC,CAAC,IAAI,2BAA2B,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;EAUjO,IAAI,CAAC,SAAS,CAAC,0BAA0B,CAAC,KAAK,CAAC;;;;;;EAMhD,IAAI,CAAC,SAAS,CAAC,6BAA6B,CAAC,KAAK,CAAC;;;;;;;;;;;;;EAanD,IAAI,CAAC,SAAS,CAAC,0BAA0B,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;EAiBhD,aAAa;EACb,mBAAmB;EACnB,gBAAgB;CACjB,CAAC;IACA,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ import { GraphQLModel } from '../GraphQLParser.js';
2
+ export declare class DelegateHelpers {
3
+ generateCascadeMethod(model: GraphQLModel): string;
4
+ generateCascadeUpdateMethod(model: GraphQLModel): string;
5
+ generateValidationMethod(model: GraphQLModel): string;
6
+ }
7
+ //# sourceMappingURL=DelegateHelpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DelegateHelpers.d.ts","sourceRoot":"","sources":["../../../src/engine/generators/DelegateHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,qBAAa,eAAe;IAC1B,qBAAqB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM;IAoElD,2BAA2B,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM;IA2BxD,wBAAwB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM;CAwCtD"}
@@ -0,0 +1,144 @@
1
+ import { toCamelCase } from './helpers.js';
2
+ export class DelegateHelpers {
3
+ generateCascadeMethod(model) {
4
+ const cascadeRelations = model.relations.filter(r => r.onDelete !== 'NoAction');
5
+ if (cascadeRelations.length === 0)
6
+ return '';
7
+ const lines = [];
8
+ lines.push(` private async handleCascadeDelete(doc: any): Promise<void> {`);
9
+ for (const rel of cascadeRelations) {
10
+ lines.push(` // Relation: ${rel.field} (${rel.type}) - onDelete: ${rel.onDelete}`);
11
+ if (rel.onDelete === 'Cascade') {
12
+ if (rel.joinCollection) {
13
+ lines.push(` await this.client.$db.collection('${rel.joinCollection}').deleteMany({ ${toCamelCase(model.name)}Id: doc.id });`);
14
+ }
15
+ else if (rel.type === 'oneToMany' && !rel.isForeignKeyArray) {
16
+ // FK in target (e.g., Post.userId) — cascade delete related docs
17
+ lines.push(` {
18
+ const related = await this.client.${toCamelCase(rel.target)}.findMany({ where: { ${rel.foreignKey}: doc.id } });
19
+ for (const r of related) {
20
+ await this.client.${toCamelCase(rel.target)}.delete({ where: { id: r.id } });
21
+ }
22
+ }`);
23
+ }
24
+ else if (rel.isForeignKeyArray) {
25
+ lines.push(` if (doc.${rel.foreignKey} && Array.isArray(doc.${rel.foreignKey}) && doc.${rel.foreignKey}.length > 0) {`);
26
+ lines.push(` await this.client.${toCamelCase(rel.target)}.deleteMany({ where: { id: { in: doc.${rel.foreignKey} } } });`);
27
+ lines.push(` }`);
28
+ }
29
+ else {
30
+ const isSingleResult = rel.type === 'manyToOne' || rel.type === 'oneToOne';
31
+ lines.push(` if (doc.${rel.foreignKey}) {`);
32
+ if (isSingleResult) {
33
+ lines.push(` await this.client.${toCamelCase(rel.target)}.delete({ where: { id: doc.${rel.foreignKey} } });`);
34
+ }
35
+ else {
36
+ lines.push(` await this.client.${toCamelCase(rel.target)}.deleteMany({ where: { id: doc.${rel.foreignKey} } });`);
37
+ }
38
+ lines.push(` }`);
39
+ }
40
+ }
41
+ else if (rel.onDelete === 'SetNull') {
42
+ if (rel.type === 'oneToMany' && !rel.isForeignKeyArray) {
43
+ // FK in target — nullify FK in all related target docs
44
+ lines.push(` await this.client.${toCamelCase(rel.target)}.updateMany({ where: { ${rel.foreignKey}: doc.id }, data: { ${rel.foreignKey}: null } });`);
45
+ }
46
+ else {
47
+ // FK is in the source document being deleted — SetNull does not apply to the deleted doc
48
+ lines.push(` // FK '${rel.foreignKey}' is in the source document — SetNull not applicable`);
49
+ }
50
+ }
51
+ else if (rel.onDelete === 'Restrict') {
52
+ if (rel.type === 'oneToMany' && !rel.isForeignKeyArray && rel.foreignKey) {
53
+ // FK in target — check if related docs exist
54
+ lines.push(` {
55
+ const count = await this.client.${toCamelCase(rel.target)}.count({ where: { ${rel.foreignKey}: doc.id } });
56
+ if (count > 0) {
57
+ throw new Error(\`Cannot delete ${model.name}: relation '${rel.field}' has \${count} related ${rel.target} records (Restrict constraint)\`);
58
+ }
59
+ }`);
60
+ }
61
+ else if (rel.isForeignKeyArray && rel.foreignKey) {
62
+ lines.push(` if (doc.${rel.foreignKey} && Array.isArray(doc.${rel.foreignKey}) && doc.${rel.foreignKey}.length > 0) {`);
63
+ lines.push(` throw new Error(\`Cannot delete ${model.name}: relation '${rel.field}' has \${doc.${rel.foreignKey}.length} related ${rel.target} records (Restrict constraint)\`);`);
64
+ lines.push(` }`);
65
+ }
66
+ else if (rel.foreignKey) {
67
+ lines.push(` if (doc.${rel.foreignKey}) {`);
68
+ lines.push(` throw new Error(\`Cannot delete ${model.name}: relation '${rel.field}' has related ${rel.target} records (Restrict constraint)\`);`);
69
+ lines.push(` }`);
70
+ }
71
+ }
72
+ }
73
+ lines.push(` }`);
74
+ return lines.join('\n');
75
+ }
76
+ generateCascadeUpdateMethod(model) {
77
+ const cascadeRelations = model.relations.filter(r => r.onUpdate && r.onUpdate !== 'NoAction');
78
+ if (cascadeRelations.length === 0)
79
+ return '';
80
+ const lines = [];
81
+ lines.push(` private async handleCascadeUpdate(doc: any, oldData: any): Promise<void> {`);
82
+ lines.push(` if (!oldData) return;`);
83
+ for (const rel of cascadeRelations) {
84
+ lines.push(` // Relation: ${rel.field} (${rel.type}) - onUpdate: ${rel.onUpdate}`);
85
+ if (rel.onUpdate === 'Cascade') {
86
+ lines.push(` // FK '${rel.foreignKey}' stored in source document — no target cascade needed`);
87
+ }
88
+ else if (rel.onUpdate === 'SetNull') {
89
+ lines.push(` // FK '${rel.foreignKey}' stored in source document — SetNull on update handled by FK write`);
90
+ }
91
+ else if (rel.onUpdate === 'Restrict') {
92
+ lines.push(` // FK in source — check if changed`);
93
+ lines.push(` if (oldData.${rel.foreignKey} && (!doc.${rel.foreignKey} || doc.${rel.foreignKey} !== oldData.${rel.foreignKey})) {`);
94
+ lines.push(` throw new Error(\`Cannot update ${model.name}: relation '${rel.field}' has related ${rel.target} records (Restrict constraint)\`);`);
95
+ lines.push(` }`);
96
+ }
97
+ }
98
+ lines.push(` }`);
99
+ return lines.join('\n');
100
+ }
101
+ generateValidationMethod(model) {
102
+ const rules = [];
103
+ for (const field of model.fields) {
104
+ if (!field.validationRules)
105
+ continue;
106
+ if (field.validationRules.email) {
107
+ rules.push(` if (data.${field.name} && !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(data.${field.name})) {
108
+ throw new Error('Validation failed: ${field.name} must be a valid email address')
109
+ }`);
110
+ }
111
+ if (field.validationRules.url) {
112
+ rules.push(` if (data.${field.name} && !/^https?:\\/\\//.test(data.${field.name})) {
113
+ throw new Error('Validation failed: ${field.name} must be a valid URL')
114
+ }`);
115
+ }
116
+ if (field.validationRules.regex) {
117
+ // Validate regex pattern for safety (reject ReDoS-prone patterns)
118
+ const rawPattern = field.validationRules.regex;
119
+ // Detect nested quantifiers: (a+)+, (a*)*, (a+)*, (a*)+, (.*)+, (.+)+
120
+ // Detect overlapping alternations: (a|aa), (a|a)
121
+ // Detect unbounded repetition after optional: .*.*, .+.+, (\w*)*
122
+ const unsafePattern = /\([^)]*[+*]\)\s*[+*]|\([^)]*\{[^}]*\}\)\s*[+*]|\.[*+]\s*[*+]|\([^)]+\|[^)]+\)\s*[+*]?|\[[^\]]*\]\{|\.\*.*\.\*|\(a+\)\+|\(a\|aa\)|\(\.\)\s*[*+]|\(\.\*\)\s*\+|\(\\w\*\)\s*[*+]|\(\\W\*\)\s*[*+]|\(\\d\*\)\s*[*+]/;
123
+ if (unsafePattern.test(rawPattern)) {
124
+ console.warn(`⚠️ Pattern '${rawPattern}' on ${field.name} may be vulnerable to ReDoS attacks`);
125
+ }
126
+ // Warn on very long patterns
127
+ if (rawPattern.length > 100) {
128
+ console.warn(`⚠️ Pattern on ${field.name} is ${rawPattern.length} characters long — may impact query performance`);
129
+ }
130
+ const escapedPattern = rawPattern.replace(/\\/g, '\\\\').replace(/\`/g, '\\`').replace(/\${/g, '\\${');
131
+ rules.push(` if (data.${field.name} && !new RegExp(\`${escapedPattern}\`).test(data.${field.name})) {
132
+ throw new Error('Validation failed: ${field.name} does not match required pattern')
133
+ }`);
134
+ }
135
+ }
136
+ if (rules.length === 0)
137
+ return '';
138
+ return `
139
+ private validateData(data: any): void {
140
+ ${rules.join('\n')}
141
+ }`;
142
+ }
143
+ }
144
+ //# sourceMappingURL=DelegateHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DelegateHelpers.js","sourceRoot":"","sources":["../../../src/engine/generators/DelegateHelpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { GraphQLModel } from '../GraphQLParser.js';
2
+ export declare class DelegateRelations {
3
+ generateCountInIncludeCode(model: GraphQLModel): string;
4
+ generateRelationInclusionCode(model: GraphQLModel): string;
5
+ /**
6
+ * Generate batch relation inclusion code — replaces N+1 queries with batched queries.
7
+ * Collects all FK values across documents, queries once with $in, and maps results back.
8
+ */
9
+ generateBatchInclusionCode(model: GraphQLModel): string;
10
+ }
11
+ //# sourceMappingURL=DelegateRelations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DelegateRelations.d.ts","sourceRoot":"","sources":["../../../src/engine/generators/DelegateRelations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,qBAAa,iBAAiB;IAC5B,0BAA0B,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM;IAqCvD,6BAA6B,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM;IAqX1D;;;OAGG;IACH,0BAA0B,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM;CAqXxD"}