@bairock/lenz 0.0.1

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 (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +224 -0
  3. package/dist/cli/commands/generate.d.ts +3 -0
  4. package/dist/cli/commands/generate.d.ts.map +1 -0
  5. package/dist/cli/commands/generate.js +110 -0
  6. package/dist/cli/commands/generate.js.map +1 -0
  7. package/dist/cli/commands/init.d.ts +3 -0
  8. package/dist/cli/commands/init.d.ts.map +1 -0
  9. package/dist/cli/commands/init.js +145 -0
  10. package/dist/cli/commands/init.js.map +1 -0
  11. package/dist/cli/commands/studio.d.ts +3 -0
  12. package/dist/cli/commands/studio.d.ts.map +1 -0
  13. package/dist/cli/commands/studio.js +32 -0
  14. package/dist/cli/commands/studio.js.map +1 -0
  15. package/dist/cli/index.d.ts +3 -0
  16. package/dist/cli/index.d.ts.map +1 -0
  17. package/dist/cli/index.js +32 -0
  18. package/dist/cli/index.js.map +1 -0
  19. package/dist/config/index.d.ts +47 -0
  20. package/dist/config/index.d.ts.map +1 -0
  21. package/dist/config/index.js +42 -0
  22. package/dist/config/index.js.map +1 -0
  23. package/dist/engine/CodeGenerator.d.ts +37 -0
  24. package/dist/engine/CodeGenerator.d.ts.map +1 -0
  25. package/dist/engine/CodeGenerator.js +1604 -0
  26. package/dist/engine/CodeGenerator.js.map +1 -0
  27. package/dist/engine/GraphQLParser.d.ts +74 -0
  28. package/dist/engine/GraphQLParser.d.ts.map +1 -0
  29. package/dist/engine/GraphQLParser.js +269 -0
  30. package/dist/engine/GraphQLParser.js.map +1 -0
  31. package/dist/engine/LenzEngine.d.ts +23 -0
  32. package/dist/engine/LenzEngine.d.ts.map +1 -0
  33. package/dist/engine/LenzEngine.js +112 -0
  34. package/dist/engine/LenzEngine.js.map +1 -0
  35. package/dist/engine/SchemaValidator.d.ts +63 -0
  36. package/dist/engine/SchemaValidator.d.ts.map +1 -0
  37. package/dist/engine/SchemaValidator.js +268 -0
  38. package/dist/engine/SchemaValidator.js.map +1 -0
  39. package/dist/engine/directives.d.ts +15 -0
  40. package/dist/engine/directives.d.ts.map +1 -0
  41. package/dist/engine/directives.js +91 -0
  42. package/dist/engine/directives.js.map +1 -0
  43. package/dist/engine/index.d.ts +4 -0
  44. package/dist/engine/index.d.ts.map +1 -0
  45. package/dist/engine/index.js +10 -0
  46. package/dist/engine/index.js.map +1 -0
  47. package/dist/errors/index.d.ts +46 -0
  48. package/dist/errors/index.d.ts.map +1 -0
  49. package/dist/errors/index.js +133 -0
  50. package/dist/errors/index.js.map +1 -0
  51. package/dist/index.d.ts +12 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +36 -0
  54. package/dist/index.js.map +1 -0
  55. package/package.json +70 -0
@@ -0,0 +1,1604 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CodeGenerator = void 0;
37
+ const ts = __importStar(require("typescript"));
38
+ class CodeGenerator {
39
+ constructor() {
40
+ this.typeMap = {
41
+ 'String': 'string',
42
+ 'Int': 'number',
43
+ 'Float': 'number',
44
+ 'Boolean': 'boolean',
45
+ 'ID': 'string',
46
+ 'DateTime': 'Date',
47
+ 'Date': 'Date',
48
+ 'Json': 'any',
49
+ 'ObjectId': 'string'
50
+ };
51
+ }
52
+ compileTypeScriptToJavaScript(content) {
53
+ try {
54
+ // Use TypeScript compiler
55
+ const result = ts.transpileModule(content, {
56
+ compilerOptions: {
57
+ target: ts.ScriptTarget.ES2020,
58
+ module: ts.ModuleKind.ESNext,
59
+ removeComments: false,
60
+ preserveConstEnums: true,
61
+ sourceMap: false,
62
+ declaration: false,
63
+ strict: false,
64
+ esModuleInterop: true,
65
+ skipLibCheck: true
66
+ }
67
+ });
68
+ return this.addJsExtensionsToImports(result.outputText);
69
+ }
70
+ catch (error) {
71
+ console.error('TypeScript compilation failed:', error);
72
+ // Fallback to enhanced conversion method
73
+ return this.convertToJavaScript(content);
74
+ }
75
+ }
76
+ addJsExtensionsToImports(content) {
77
+ let result = content;
78
+ // 1. Remove import type (already present)
79
+ result = result.replace(/import type/g, 'import');
80
+ // 2. Add .js extensions to relative imports (already present)
81
+ // Handle both single and double quotes, with optional whitespace
82
+ // Match from './path' or from '../path' or from '../../path' etc.
83
+ result = result.replace(/from\s+['"](\.\.?\/[^'"]*)['"]/g, (match, p1) => {
84
+ if (p1.endsWith('.js') || p1.endsWith('.ts'))
85
+ return match;
86
+ // Keep the original quote type
87
+ const quoteChar = match.includes('"') ? '"' : "'";
88
+ return `from ${quoteChar}${p1}.js${quoteChar}`;
89
+ });
90
+ // Also handle import './path' (without from)
91
+ result = result.replace(/import\s+['"](\.\.?\/[^'"]*)['"]/g, (match, p1) => {
92
+ if (p1.endsWith('.js') || p1.endsWith('.ts'))
93
+ return match;
94
+ const quoteChar = match.includes('"') ? '"' : "'";
95
+ return `import ${quoteChar}${p1}.js${quoteChar}`;
96
+ });
97
+ return result;
98
+ }
99
+ removeTypeScriptSyntax(content) {
100
+ let result = content;
101
+ // 1. Remove variable type annotations
102
+ result = result.replace(/(\w+)\s*:\s*[^=;,\n]+(?=\s*(=|;|,|\n))/g, '$1');
103
+ // 2. Remove generic parameters from functions and classes
104
+ result = result.replace(/(\w+)<[^>]+>(?=\s*[\s\(])/g, '$1');
105
+ // 3. Remove return type annotations
106
+ result = result.replace(/\s*:\s*[^{]+(?=\s*{)/g, '');
107
+ // 4. Remove type assertions
108
+ result = result.replace(/\s+as\s+[^,\n;]+/g, '');
109
+ // 5. Remove export type/interface lines
110
+ result = result.replace(/export\s+(type|interface)\s+\w+.*\n/g, '');
111
+ // 6. Remove 'as const'
112
+ result = result.replace(/ as const/g, '');
113
+ // 7. Remove private/protected/public modifiers
114
+ result = result.replace(/\b(private|protected|public)\s+/g, '');
115
+ // 8. Clean up empty lines
116
+ result = result.replace(/\n\s*\n\s*\n/g, '\n\n');
117
+ return result;
118
+ }
119
+ convertToJavaScript(content) {
120
+ let result = this.addJsExtensionsToImports(content);
121
+ result = this.removeTypeScriptSyntax(result);
122
+ return result;
123
+ }
124
+ convertTypesToJavaScript(content) {
125
+ // For types.ts and enums.ts files: create JavaScript-compatible exports
126
+ // For enums: keep the actual enum objects, remove 'as const' and type exports
127
+ // For types: export undefined stubs
128
+ // First, extract all constant exports (export const ... = ...)
129
+ const constExports = [];
130
+ const lines = content.split('\n');
131
+ for (let i = 0; i < lines.length; i++) {
132
+ const line = lines[i];
133
+ // Match export const Name = { ... } or export const Name = ...
134
+ const constMatch = line.match(/export const (\w+)\s*=\s*(.+)/);
135
+ if (constMatch) {
136
+ const name = constMatch[1];
137
+ let value = constMatch[2];
138
+ // Check if value continues on next lines (for multi-line objects)
139
+ let braceCount = (value.match(/{/g) || []).length - (value.match(/}/g) || []).length;
140
+ let j = i;
141
+ while (braceCount > 0 && j + 1 < lines.length) {
142
+ j++;
143
+ const nextLine = lines[j];
144
+ value += '\n' + nextLine;
145
+ braceCount += (nextLine.match(/{/g) || []).length - (nextLine.match(/}/g) || []).length;
146
+ }
147
+ // Remove 'as const' if present
148
+ value = value.replace(/ as const/g, '');
149
+ constExports.push({ name, value });
150
+ }
151
+ }
152
+ // Extract other export names (interfaces, types) for stub exports
153
+ const stubExportNames = new Set();
154
+ // Find export interface Name (but skip if we already have a const export with same name)
155
+ const interfaceMatches = content.match(/export interface (\w+)/g);
156
+ if (interfaceMatches) {
157
+ interfaceMatches.forEach(match => {
158
+ const name = match.replace('export interface ', '').trim();
159
+ if (!constExports.some(exp => exp.name === name)) {
160
+ stubExportNames.add(name);
161
+ }
162
+ });
163
+ }
164
+ // Find export type Name
165
+ const typeMatches = content.match(/export type (\w+)/g);
166
+ if (typeMatches) {
167
+ typeMatches.forEach(match => {
168
+ const name = match.replace('export type ', '').split('<')[0].trim();
169
+ if (!constExports.some(exp => exp.name === name)) {
170
+ stubExportNames.add(name);
171
+ }
172
+ });
173
+ }
174
+ // Find named exports: export { Name1, Name2 }
175
+ const namedExportMatches = content.match(/export \{([^}]+)\}/g);
176
+ if (namedExportMatches) {
177
+ namedExportMatches.forEach(match => {
178
+ const namesStr = match.replace('export {', '').replace('}', '').trim();
179
+ const names = namesStr.split(',').map(n => n.trim()).filter(n => n.length > 0);
180
+ names.forEach(name => {
181
+ if (!constExports.some(exp => exp.name === name)) {
182
+ stubExportNames.add(name);
183
+ }
184
+ });
185
+ });
186
+ }
187
+ // Generate JavaScript file
188
+ let result = `// This file was auto-generated by Lenz. Do not edit manually.
189
+ // @generated
190
+ // This file provides JavaScript-compatible exports for TypeScript types.
191
+ // TypeScript projects should use the .d.ts files for full type information.
192
+
193
+ `;
194
+ // Add imports (convert import type to import and add .js extensions)
195
+ const importLines = lines.filter(line => line.includes('import '));
196
+ const processedImports = new Set();
197
+ for (const line of importLines) {
198
+ let processed = line.replace(/import type/g, 'import');
199
+ processed = processed.replace(/from '\.\/([^']+)'/g, (match, p1) => {
200
+ if (p1.endsWith('.js') || p1.endsWith('.ts')) {
201
+ return match;
202
+ }
203
+ return `from './${p1}.js'`;
204
+ });
205
+ if (!processedImports.has(processed)) {
206
+ processedImports.add(processed);
207
+ result += processed + '\n';
208
+ }
209
+ }
210
+ if (processedImports.size > 0) {
211
+ result += '\n';
212
+ }
213
+ // Add constant exports first (enums)
214
+ constExports.forEach(exp => {
215
+ result += `export const ${exp.name} = ${exp.value};\n`;
216
+ });
217
+ if (constExports.length > 0) {
218
+ result += '\n';
219
+ }
220
+ // Add stub exports for types and interfaces
221
+ const stubExportArray = Array.from(stubExportNames);
222
+ if (stubExportArray.length > 0) {
223
+ stubExportArray.forEach(name => {
224
+ result += `export const ${name} = undefined;\n`;
225
+ });
226
+ // Also export all as a default object for convenience
227
+ result += `\nexport default {\n`;
228
+ [...constExports.map(exp => exp.name), ...stubExportArray].forEach((name, index, arr) => {
229
+ result += ` ${name}: ${name}${index < arr.length - 1 ? ',' : ''}\n`;
230
+ });
231
+ result += `};\n`;
232
+ }
233
+ else if (constExports.length > 0) {
234
+ // Only constant exports, add default export
235
+ result += `\nexport default {\n`;
236
+ constExports.forEach((exp, index) => {
237
+ result += ` ${exp.name}: ${exp.name}${index < constExports.length - 1 ? ',' : ''}\n`;
238
+ });
239
+ result += `};\n`;
240
+ }
241
+ else {
242
+ // No exports found, export empty object
243
+ result += `export {};\n`;
244
+ }
245
+ return result;
246
+ }
247
+ convertToDeclaration(content) {
248
+ // For now, return the TypeScript content as-is for declarations
249
+ // This will be refined later to remove implementations
250
+ return content;
251
+ }
252
+ generate(options) {
253
+ const { models, enums, clientName = 'LenzClient' } = options;
254
+ const files = {
255
+ 'index.ts': this.generateIndex(clientName),
256
+ 'client.ts': this.generateClient(clientName, models),
257
+ 'types.ts': this.generateTypes(models, enums),
258
+ 'enums.ts': this.generateEnums(enums),
259
+ 'runtime/index.ts': this.generateRuntimeIndex(),
260
+ 'runtime/query.ts': this.generateRuntimeQuery(),
261
+ 'runtime/pagination.ts': this.generateRuntimePagination(),
262
+ 'runtime/relations.ts': this.generateRuntimeRelations(),
263
+ 'models/index.ts': this.generateModelsIndex(models),
264
+ ...this.generateModelFiles(models)
265
+ };
266
+ const result = {};
267
+ for (const [filePath, content] of Object.entries(files)) {
268
+ // Generate JavaScript file (.js)
269
+ const jsPath = filePath.replace(/\.ts$/, '.js');
270
+ // Special handling for types and enums files
271
+ if (filePath === 'types.ts' || filePath === 'enums.ts') {
272
+ result[jsPath] = this.convertTypesToJavaScript(content);
273
+ }
274
+ else {
275
+ result[jsPath] = this.compileTypeScriptToJavaScript(content);
276
+ }
277
+ // Generate TypeScript declaration file (.d.ts)
278
+ const dtsPath = filePath.replace(/\.ts$/, '.d.ts');
279
+ result[dtsPath] = this.convertToDeclaration(content);
280
+ }
281
+ return result;
282
+ }
283
+ generateIndex(clientName) {
284
+ return `// This file was auto-generated by Lenz. Do not edit manually.
285
+ // @generated
286
+
287
+ export { ${clientName} } from './client'
288
+ export * from './types'
289
+ export * from './enums'
290
+
291
+ import { ${clientName} } from './client'
292
+
293
+ /**
294
+ * Default export for the Lenz client
295
+ */
296
+ const lenz = new ${clientName}()
297
+ export default lenz
298
+ `;
299
+ }
300
+ generateClient(clientName, models) {
301
+ return `// This file was auto-generated by Lenz. Do not edit manually.
302
+ // @generated
303
+
304
+ import { MongoClient, Db, ObjectId } from 'mongodb'
305
+ import type { LenzConfig } from './types'
306
+ import { QueryBuilder } from './runtime/query'
307
+ import { RelationResolver } from './runtime/relations'
308
+
309
+ ${models.map(model => `
310
+ import { ${model.name}Delegate } from './models/${model.name}'`).join('\n')}
311
+
312
+ export class ${clientName} {
313
+ private mongoClient: MongoClient | null = null
314
+ private db: Db | null = null
315
+ private config: LenzConfig
316
+ private supportsTransactions: boolean = false
317
+
318
+ // Model delegates
319
+ ${models.map(model => ` public ${this.toCamelCase(model.name)}: ${model.name}Delegate`).join('\n')}
320
+
321
+ constructor(config: LenzConfig = {}) {
322
+ this.config = {
323
+ url: config.url || process.env.MONGODB_URI || 'mongodb://localhost:27017',
324
+ database: config.database || 'myapp',
325
+ autoCreateCollections: config.autoCreateCollections ?? true,
326
+ log: config.log || [],
327
+ ...config
328
+ }
329
+
330
+ // Initialize model delegates
331
+ ${models.map(model => ` this.${this.toCamelCase(model.name)} = new ${model.name}Delegate(this)`).join('\n')}
332
+ }
333
+
334
+ async $connect(): Promise<void> {
335
+ if (this.mongoClient) {
336
+ return
337
+ }
338
+
339
+ this.mongoClient = new MongoClient(this.config.url, {
340
+ maxPoolSize: this.config.maxPoolSize || 10,
341
+ connectTimeoutMS: this.config.connectTimeoutMS || 10000,
342
+ socketTimeoutMS: this.config.socketTimeoutMS || 45000
343
+ })
344
+
345
+ await this.mongoClient.connect()
346
+ this.db = this.mongoClient.db(this.config.database)
347
+
348
+ // Test connection
349
+ await this.db.command({ ping: 1 })
350
+
351
+ // Check if MongoDB supports transactions (requires replica set)
352
+ try {
353
+ const serverInfo = await this.db.admin().serverInfo()
354
+ this.supportsTransactions = serverInfo.repl?.replSetName !== undefined
355
+
356
+ if (!this.supportsTransactions) {
357
+ console.warn('⚠️ MongoDB is running in standalone mode. Transactions will not work.')
358
+ console.warn(' Consider setting up a replica set for transaction support.')
359
+ console.warn(' Example: mongod --replSet rs0 --port 27017')
360
+ }
361
+ } catch (error) {
362
+ console.warn('⚠️ Could not determine MongoDB deployment type:', error.message)
363
+ this.supportsTransactions = false
364
+ }
365
+
366
+ // Initialize collections and indexes
367
+ await this.initializeCollections()
368
+
369
+ if (this.config.log?.includes('info')) {
370
+ console.log('✅ Connected to MongoDB')
371
+ }
372
+ }
373
+
374
+ async $disconnect(): Promise<void> {
375
+ if (this.mongoClient) {
376
+ await this.mongoClient.close()
377
+ this.mongoClient = null
378
+ this.db = null
379
+
380
+ if (this.config.log?.includes('info')) {
381
+ console.log('👋 Disconnected from MongoDB')
382
+ }
383
+ }
384
+ }
385
+
386
+ async $transaction<T>(callback: (tx: any) => Promise<T>): Promise<T> {
387
+ if (!this.mongoClient) {
388
+ throw new Error('Not connected to database')
389
+ }
390
+
391
+ // Check if transactions are supported
392
+ if (!this.supportsTransactions) {
393
+ throw new Error(
394
+ 'Transactions are not supported in standalone MongoDB. ' +
395
+ 'Set up a replica set or use alternative consistency patterns. ' +
396
+ 'During development: mongod --replSet rs0 --port 27017'
397
+ )
398
+ }
399
+
400
+ const session = this.mongoClient.startSession()
401
+
402
+ try {
403
+ session.startTransaction()
404
+ const result = await callback(session)
405
+ await session.commitTransaction()
406
+ return result
407
+ } catch (error) {
408
+ await session.abortTransaction()
409
+ throw error
410
+ } finally {
411
+ session.endSession()
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Check if the connected MongoDB deployment supports transactions
417
+ * Returns false if not connected or if running in standalone mode
418
+ */
419
+ $supportsTransactions(): boolean {
420
+ return this.supportsTransactions
421
+ }
422
+
423
+ private async initializeCollections(): Promise<void> {
424
+ if (!this.db || !this.config.autoCreateCollections) return
425
+
426
+ const models = ${JSON.stringify(this.getModelsForRuntime(models), null, 2)}
427
+
428
+ for (const model of models) {
429
+ const collections = await this.db.listCollections({ name: model.collectionName }).toArray()
430
+
431
+ if (collections.length === 0) {
432
+ await this.db.createCollection(model.collectionName)
433
+
434
+ // Create indexes
435
+ if (model.indexes.length > 0) {
436
+ const indexes = model.indexes.map(index => ({
437
+ key: index.fields.reduce((acc, field) => {
438
+ acc[field] = 1
439
+ return acc
440
+ }, {}),
441
+ unique: index.unique,
442
+ sparse: index.sparse || false
443
+ }))
444
+
445
+ try {
446
+ await this.db.collection(model.collectionName).createIndexes(indexes)
447
+ } catch (error) {
448
+ console.warn(\`Failed to create indexes for \${model.name}:\`, error)
449
+ }
450
+ }
451
+ }
452
+ }
453
+ }
454
+
455
+ $isConnected(): boolean {
456
+ return this.mongoClient !== null
457
+ }
458
+
459
+ get $db(): Db {
460
+ if (!this.db) {
461
+ throw new Error('Database not connected. Call $connect() first.')
462
+ }
463
+ return this.db
464
+ }
465
+
466
+ get $mongo(): { client: MongoClient; ObjectId: any } {
467
+ if (!this.mongoClient) {
468
+ throw new Error('Database not connected. Call $connect() first.')
469
+ }
470
+ return {
471
+ client: this.mongoClient,
472
+ ObjectId: ObjectId
473
+ }
474
+ }
475
+ }
476
+ `;
477
+ }
478
+ getModelsForRuntime(models) {
479
+ return models.map(model => ({
480
+ name: model.name,
481
+ collectionName: model.collectionName,
482
+ indexes: model.indexes.map(index => ({
483
+ fields: index.fields,
484
+ unique: index.unique,
485
+ sparse: index.sparse ?? false
486
+ }))
487
+ }));
488
+ }
489
+ generateTypes(models, enums) {
490
+ return `// This file was auto-generated by Lenz. Do not edit manually.
491
+ // @generated
492
+
493
+ import { ObjectId } from 'mongodb'
494
+
495
+ // Base types
496
+ export type ScalarType = 'String' | 'Int' | 'Float' | 'Boolean' | 'ID' | 'DateTime' | 'Date' | 'Json' | 'ObjectId'
497
+ export type RelationType = 'oneToOne' | 'oneToMany' | 'manyToOne' | 'manyToMany'
498
+
499
+ // Enum types
500
+ ${enums.map(e => `
501
+ export enum ${e.name} {
502
+ ${e.values.map(v => `${v} = '${v}'`).join(',\n ')}
503
+ }`).join('\n\n')}
504
+
505
+ // Model types
506
+ ${models.map(model => this.generateModelType(model)).join('\n\n')}
507
+
508
+ // Pagination types
509
+ export interface PageInfo {
510
+ hasNextPage: boolean
511
+ hasPreviousPage: boolean
512
+ startCursor?: string
513
+ endCursor?: string
514
+ }
515
+
516
+ export interface Connection<T> {
517
+ edges: Array<Edge<T>>
518
+ pageInfo: PageInfo
519
+ totalCount: number
520
+ }
521
+
522
+ export interface Edge<T> {
523
+ node: T
524
+ cursor: string
525
+ }
526
+
527
+ // Operation types
528
+ export interface WhereInput<T = any> {
529
+ id?: string | ObjectId
530
+ AND?: WhereInput<T>[]
531
+ OR?: WhereInput<T>[]
532
+ NOT?: WhereInput<T>[]
533
+ [key: string]: any
534
+ }
535
+
536
+ export interface OrderByInput {
537
+ [key: string]: 'asc' | 'desc' | 1 | -1
538
+ }
539
+
540
+ export interface SelectInput {
541
+ [key: string]: boolean | SelectInput
542
+ }
543
+
544
+ export interface IncludeInput {
545
+ [key: string]: boolean | IncludeInput
546
+ }
547
+
548
+ export interface QueryOptions<T = any> {
549
+ where?: WhereInput<T>
550
+ select?: SelectInput
551
+ include?: IncludeInput
552
+ skip?: number
553
+ take?: number
554
+ orderBy?: OrderByInput | OrderByInput[]
555
+ distinct?: string | string[]
556
+ /** Cursor for pagination */
557
+ cursor?: string | ObjectId
558
+ }
559
+
560
+ export interface CreateInput<T = any> {
561
+ data: Partial<T>
562
+ select?: SelectInput
563
+ include?: IncludeInput
564
+ }
565
+
566
+ export interface UpdateInput<T = any> {
567
+ where: WhereInput<T>
568
+ data: Partial<T>
569
+ select?: SelectInput
570
+ include?: IncludeInput
571
+ }
572
+
573
+ export interface DeleteInput<T = any> {
574
+ where: WhereInput<T>
575
+ select?: SelectInput
576
+ include?: IncludeInput
577
+ }
578
+
579
+ export interface UpsertInput<T = any> {
580
+ where: WhereInput<T>
581
+ create: Partial<T>
582
+ update: Partial<T>
583
+ select?: SelectInput
584
+ include?: IncludeInput
585
+ }
586
+
587
+ // Pagination specific interfaces
588
+ export interface OffsetPaginationArgs<T = any> extends QueryOptions<T> {
589
+ page?: number
590
+ perPage?: number
591
+ }
592
+
593
+ export interface CursorPaginationArgs<T = any> extends QueryOptions<T> {
594
+ cursor?: string | ObjectId
595
+ take?: number
596
+ }
597
+
598
+ export interface PaginatedResult<T> {
599
+ data: T[]
600
+ meta: {
601
+ total: number
602
+ page: number
603
+ perPage: number
604
+ totalPages: number
605
+ hasNextPage: boolean
606
+ hasPreviousPage: boolean
607
+ }
608
+ }
609
+
610
+ export interface CursorPaginatedResult<T> {
611
+ edges: Array<{
612
+ node: T
613
+ cursor: string
614
+ }>
615
+ pageInfo: {
616
+ hasNextPage: boolean
617
+ hasPreviousPage: boolean
618
+ startCursor?: string
619
+ endCursor?: string
620
+ }
621
+ totalCount: number
622
+ }
623
+
624
+ // Config types
625
+ export interface LenzConfig {
626
+ url?: string
627
+ database?: string
628
+ schemaPath?: string
629
+ log?: ('query' | 'error' | 'warn' | 'info')[]
630
+ autoCreateCollections?: boolean
631
+ maxPoolSize?: number
632
+ connectTimeoutMS?: number
633
+ socketTimeoutMS?: number
634
+ }
635
+
636
+ // Utility types
637
+ export type DeepPartial<T> = {
638
+ [P in keyof T]?: T[P] extends Array<infer U>
639
+ ? Array<DeepPartial<U>>
640
+ : T[P] extends ReadonlyArray<infer U>
641
+ ? ReadonlyArray<DeepPartial<U>>
642
+ : DeepPartial<T[P]> | T[P]
643
+ }
644
+
645
+ export type WithId<T> = T & { id: string }
646
+ export type OptionalId<T> = Omit<T, 'id'> & { id?: string }
647
+ `;
648
+ }
649
+ generateModelType(model) {
650
+ return `export interface ${model.name} {
651
+ id: string
652
+ ${model.fields.filter(f => !f.isId).map(field => {
653
+ const tsType = this.mapToTSType(field.type, field.isArray);
654
+ return ` ${field.name}${field.isRequired ? '' : '?'}: ${tsType}`;
655
+ }).join('\n')}
656
+ }
657
+
658
+ export interface ${model.name}CreateInput {
659
+ ${model.fields.filter(f => !f.isId && !f.directives.includes('@generated')).map(field => {
660
+ const tsType = this.mapToTSType(field.type, field.isArray);
661
+ return ` ${field.name}${field.isRequired ? '' : '?'}: ${tsType}`;
662
+ }).join('\n')}
663
+ }
664
+
665
+ export interface ${model.name}UpdateInput {
666
+ ${model.fields.filter(f => !f.isId).map(field => {
667
+ const tsType = this.mapToTSType(field.type, field.isArray);
668
+ return ` ${field.name}?: ${tsType}`;
669
+ }).join('\n')}
670
+ }
671
+
672
+ export type ${model.name}WhereInput = WhereInput<${model.name}>
673
+ export type ${model.name}QueryOptions = QueryOptions<${model.name}>
674
+ export type ${model.name}CreateArgs = CreateInput<${model.name}>
675
+ export type ${model.name}UpdateArgs = UpdateInput<${model.name}>
676
+ export type ${model.name}DeleteArgs = DeleteInput<${model.name}>
677
+ export type ${model.name}UpsertArgs = UpsertInput<${model.name}>`;
678
+ }
679
+ mapToTSType(type, isArray) {
680
+ let baseType = this.typeMap[type] || type;
681
+ if (isArray) {
682
+ return `${baseType}[]`;
683
+ }
684
+ return baseType;
685
+ }
686
+ generateEnums(enums) {
687
+ if (enums.length === 0) {
688
+ return '// No enums defined in schema\n\nexport {}';
689
+ }
690
+ return `// This file was auto-generated by Lenz. Do not edit manually.
691
+ // @generated
692
+
693
+ ${enums.map(e => `
694
+ export const ${e.name} = {
695
+ ${e.values.map(v => ` ${v}: '${v}',`).join('\n')}
696
+ } as const
697
+
698
+ export type ${e.name} = typeof ${e.name}[keyof typeof ${e.name}]
699
+ `).join('\n')}`;
700
+ }
701
+ generateRuntimePagination() {
702
+ return `// This file was auto-generated by Lenz. Do not edit manually.
703
+ // @generated
704
+
705
+ import { ObjectId } from 'mongodb'
706
+
707
+ /**
708
+ * Pagination helper for Lenz ORM
709
+ * Implements both offset-based and cursor-based pagination
710
+ * Similar to Prisma's pagination patterns
711
+ */
712
+ export class PaginationHelper {
713
+ /**
714
+ * Create a cursor from a document
715
+ * Uses the document's _id by default
716
+ */
717
+ static createCursor(doc: any): string {
718
+ if (!doc) throw new Error('Cannot create cursor from null document')
719
+
720
+ // Use _id if available, otherwise id
721
+ const id = doc._id || doc.id
722
+ if (!id) throw new Error('Document must have an id to create cursor')
723
+
724
+ // Base64 encode for cursor
725
+ return Buffer.from(id.toString()).toString('base64')
726
+ }
727
+
728
+ /**
729
+ * Parse cursor to get the id
730
+ */
731
+ static parseCursor(cursor: string): string {
732
+ try {
733
+ return Buffer.from(cursor, 'base64').toString('utf8')
734
+ } catch (error) {
735
+ throw new Error('Invalid cursor format')
736
+ }
737
+ }
738
+
739
+ /**
740
+ * Build MongoDB filter for cursor-based pagination
741
+ * Assumes ordering by _id unless specified otherwise
742
+ */
743
+ static buildCursorFilter(
744
+ cursor: string,
745
+ orderBy: any = { _id: 'asc' },
746
+ direction: 'forward' | 'backward' = 'forward'
747
+ ): any {
748
+ const cursorId = this.parseCursor(cursor)
749
+
750
+ // For simplicity, we'll handle single field ordering
751
+ const orderField = Object.keys(orderBy)[0] || '_id'
752
+ const orderDirection = orderBy[orderField] || 'asc'
753
+
754
+ const isAscending = orderDirection === 'asc' || orderDirection === 1
755
+ const isForward = direction === 'forward'
756
+
757
+ // Build comparison operator based on direction and order
758
+ let operator: string
759
+ if (isForward) {
760
+ operator = isAscending ? '$gt' : '$lt'
761
+ } else {
762
+ operator = isAscending ? '$lt' : '$gt'
763
+ }
764
+
765
+ return {
766
+ [orderField]: { [operator]: new ObjectId(cursorId) }
767
+ }
768
+ }
769
+
770
+ /**
771
+ * Calculate skip for offset pagination
772
+ */
773
+ static calculateSkip(page: number, perPage: number): number {
774
+ if (page < 1) throw new Error('Page must be greater than 0')
775
+ return (page - 1) * perPage
776
+ }
777
+
778
+ /**
779
+ * Calculate total pages
780
+ */
781
+ static calculateTotalPages(total: number, perPage: number): number {
782
+ return Math.ceil(total / perPage)
783
+ }
784
+
785
+ /**
786
+ * Get pagination metadata
787
+ */
788
+ static getPaginationMeta(
789
+ total: number,
790
+ page: number,
791
+ perPage: number,
792
+ dataLength: number
793
+ ): {
794
+ total: number
795
+ page: number
796
+ perPage: number
797
+ totalPages: number
798
+ hasNextPage: boolean
799
+ hasPreviousPage: boolean
800
+ } {
801
+ const totalPages = this.calculateTotalPages(total, perPage)
802
+
803
+ return {
804
+ total,
805
+ page,
806
+ perPage,
807
+ totalPages,
808
+ hasNextPage: page < totalPages,
809
+ hasPreviousPage: page > 1
810
+ }
811
+ }
812
+
813
+ /**
814
+ * Format results for GraphQL-style connection
815
+ */
816
+ static toConnection<T>(
817
+ data: T[],
818
+ totalCount: number,
819
+ hasNextPage: boolean,
820
+ hasPreviousPage: boolean,
821
+ createCursor: (item: T) => string = (item: any) => this.createCursor(item)
822
+ ): {
823
+ edges: Array<{ node: T; cursor: string }>
824
+ pageInfo: {
825
+ hasNextPage: boolean
826
+ hasPreviousPage: boolean
827
+ startCursor?: string
828
+ endCursor?: string
829
+ }
830
+ totalCount: number
831
+ } {
832
+ const edges = data.map(item => ({
833
+ node: item,
834
+ cursor: createCursor(item)
835
+ }))
836
+
837
+ return {
838
+ edges,
839
+ pageInfo: {
840
+ hasNextPage,
841
+ hasPreviousPage,
842
+ startCursor: edges[0]?.cursor,
843
+ endCursor: edges[edges.length - 1]?.cursor
844
+ },
845
+ totalCount
846
+ }
847
+ }
848
+
849
+ /**
850
+ * Simple offset pagination
851
+ */
852
+ static paginate<T>(
853
+ data: T[],
854
+ page: number = 1,
855
+ perPage: number = 10
856
+ ): {
857
+ data: T[]
858
+ meta: {
859
+ page: number
860
+ perPage: number
861
+ total: number
862
+ totalPages: number
863
+ hasNextPage: boolean
864
+ hasPreviousPage: boolean
865
+ }
866
+ } {
867
+ const startIndex = (page - 1) * perPage
868
+ const endIndex = startIndex + perPage
869
+ const paginatedData = data.slice(startIndex, endIndex)
870
+ const total = data.length
871
+ const totalPages = Math.ceil(total / perPage)
872
+
873
+ return {
874
+ data: paginatedData,
875
+ meta: {
876
+ page,
877
+ perPage,
878
+ total,
879
+ totalPages,
880
+ hasNextPage: page < totalPages,
881
+ hasPreviousPage: page > 1
882
+ }
883
+ }
884
+ }
885
+ }
886
+ `;
887
+ }
888
+ generateRuntimeIndex() {
889
+ return `// This file was auto-generated by Lenz. Do not edit manually.
890
+ // @generated
891
+
892
+ export { QueryBuilder } from './query'
893
+ export { PaginationHelper } from './pagination'
894
+ export { RelationResolver } from './relations'
895
+ `;
896
+ }
897
+ generateRuntimeQuery() {
898
+ return `// This file was auto-generated by Lenz. Do not edit manually.
899
+ // @generated
900
+
901
+ import { ObjectId, Filter, UpdateFilter } from 'mongodb'
902
+ import type { WhereInput, QueryOptions, SelectInput } from '../types'
903
+ import { PaginationHelper } from './pagination'
904
+
905
+ export class QueryBuilder {
906
+ static buildWhere<T>(where: WhereInput<T>): Filter<any> {
907
+ const filter: Filter<any> = {}
908
+
909
+ for (const [key, value] of Object.entries(where || {})) {
910
+ if (key === 'id') {
911
+ filter._id = this.normalizeId(value)
912
+ } else if (typeof value === 'object' && value !== null) {
913
+ this.applyOperators(filter, key, value)
914
+ } else {
915
+ filter[key] = value
916
+ }
917
+ }
918
+
919
+ return filter
920
+ }
921
+
922
+ /**
923
+ * Build cursor condition for pagination
924
+ */
925
+ static buildCursorCondition(
926
+ cursor: string | ObjectId,
927
+ orderBy: any = { _id: 'asc' }
928
+ ): Filter<any> {
929
+ const cursorId = typeof cursor === 'string'
930
+ ? PaginationHelper.parseCursor(cursor)
931
+ : cursor.toString()
932
+
933
+ // For simple _id ordering
934
+ if (orderBy._id || (!Object.keys(orderBy).length)) {
935
+ return {
936
+ _id: { $gt: new ObjectId(cursorId) }
937
+ }
938
+ }
939
+
940
+ // For other field ordering (simplified implementation)
941
+ // In real implementation, you'd need to know the value at the cursor
942
+ const orderField = Object.keys(orderBy)[0]
943
+ const orderDirection = orderBy[orderField]
944
+
945
+ // Note: This is a simplified version
946
+ // Full implementation requires fetching the cursor document
947
+ return {
948
+ [orderField]: orderDirection === 'desc'
949
+ ? { $lt: cursorId }
950
+ : { $gt: cursorId }
951
+ }
952
+ }
953
+
954
+ /**
955
+ * Build MongoDB projection object from select input and hidden fields
956
+ */
957
+ static buildProjection<T>(
958
+ select: SelectInput | undefined,
959
+ hiddenFields: string[] = []
960
+ ): any {
961
+ if (!select) {
962
+ // По умолчанию: исключаем скрытые поля
963
+ if (hiddenFields.length === 0) return undefined;
964
+ const projection: any = {};
965
+ hiddenFields.forEach(field => {
966
+ projection[field] = 0;
967
+ });
968
+ return projection;
969
+ }
970
+
971
+ const projection: any = {};
972
+ const processSelect = (sel: SelectInput, prefix = '') => {
973
+ for (const [key, value] of Object.entries(sel)) {
974
+ const fullPath = prefix ? \`\${prefix}.\${key}\` : key;
975
+
976
+ if (typeof value === 'boolean') {
977
+ // Базовое поле: true - включать, false - исключать
978
+ projection[fullPath] = value ? 1 : 0;
979
+ } else {
980
+ // Вложенный объект (отношение)
981
+ processSelect(value, fullPath);
982
+ }
983
+ }
984
+ };
985
+
986
+ processSelect(select);
987
+
988
+ // Убедимся, что скрытые поля исключены, если не указано явно
989
+ hiddenFields.forEach(field => {
990
+ if (projection[field] === undefined) {
991
+ projection[field] = 0;
992
+ }
993
+ });
994
+
995
+ return projection;
996
+ }
997
+
998
+ static buildOptions<T>(options: QueryOptions<T>, hiddenFields: string[] = []): any {
999
+ const result: any = {}
1000
+
1001
+ if (options.skip !== undefined) result.skip = options.skip
1002
+ if (options.take !== undefined) result.limit = options.take
1003
+
1004
+ if (options.orderBy) {
1005
+ result.sort = this.buildSort(options.orderBy)
1006
+ }
1007
+
1008
+ // Добавить проекцию, если есть select или скрытые поля
1009
+ const projection = this.buildProjection(options.select, hiddenFields)
1010
+ if (projection) {
1011
+ result.projection = projection
1012
+ }
1013
+
1014
+ return result
1015
+ }
1016
+
1017
+ private static buildSort(orderBy: any): any {
1018
+ if (Array.isArray(orderBy)) {
1019
+ return orderBy.reduce((acc, curr) => ({ ...acc, ...this.buildSort(curr) }), {})
1020
+ }
1021
+
1022
+ const sort: any = {}
1023
+ for (const [field, direction] of Object.entries(orderBy)) {
1024
+ if (direction === 'asc' || direction === 1) {
1025
+ sort[field] = 1
1026
+ } else if (direction === 'desc' || direction === -1) {
1027
+ sort[field] = -1
1028
+ }
1029
+ }
1030
+ return sort
1031
+ }
1032
+
1033
+ private static applyOperators(filter: any, field: string, operators: any): void {
1034
+ const mongoOperators: Record<string, string> = {
1035
+ equals: '$eq',
1036
+ not: '$ne',
1037
+ in: '$in',
1038
+ notIn: '$nin',
1039
+ lt: '$lt',
1040
+ lte: '$lte',
1041
+ gt: '$gt',
1042
+ gte: '$gte',
1043
+ contains: '$regex',
1044
+ startsWith: '$regex',
1045
+ endsWith: '$regex'
1046
+ }
1047
+
1048
+ for (const [op, value] of Object.entries(operators)) {
1049
+ const mongoOp = mongoOperators[op]
1050
+
1051
+ if (mongoOp) {
1052
+ if (op === 'contains') {
1053
+ filter[field] = { $regex: value, $options: 'i' }
1054
+ } else if (op === 'startsWith') {
1055
+ filter[field] = { $regex: \`^\${value}\`, $options: 'i' }
1056
+ } else if (op === 'endsWith') {
1057
+ filter[field] = { $regex: \`\${value}\$\`, $options: 'i' }
1058
+ } else {
1059
+ if (!filter[field]) filter[field] = {}
1060
+ filter[field][mongoOp] = value
1061
+ }
1062
+ }
1063
+ }
1064
+ }
1065
+
1066
+ static normalizeId(id: string | ObjectId): ObjectId | string {
1067
+ try {
1068
+ if (typeof id === 'string' && /^[0-9a-fA-F]{24}\$/.test(id)) {
1069
+ return new ObjectId(id)
1070
+ }
1071
+ return id
1072
+ } catch {
1073
+ return id
1074
+ }
1075
+ }
1076
+
1077
+ static buildUpdate(data: any): UpdateFilter<any> {
1078
+ const update: UpdateFilter<any> = {}
1079
+
1080
+ const setOperations: any = {}
1081
+ const otherOperations: any = {}
1082
+
1083
+ for (const [key, value] of Object.entries(data)) {
1084
+ if (key.startsWith('$')) {
1085
+ otherOperations[key] = value
1086
+ } else {
1087
+ setOperations[key] = value
1088
+ }
1089
+ }
1090
+
1091
+ if (Object.keys(setOperations).length > 0) {
1092
+ update.$set = setOperations
1093
+ }
1094
+
1095
+ Object.assign(update, otherOperations)
1096
+
1097
+ return update
1098
+ }
1099
+ }
1100
+ `;
1101
+ }
1102
+ generateRuntimeRelations() {
1103
+ return `// This file was auto-generated by Lenz. Do not edit manually.
1104
+ // @generated
1105
+
1106
+ import { Db, ObjectId } from 'mongodb'
1107
+
1108
+ export class RelationResolver {
1109
+ static async resolveOneToOne(
1110
+ db: Db,
1111
+ sourceCollection: string,
1112
+ targetCollection: string,
1113
+ sourceId: string,
1114
+ foreignKey: string
1115
+ ): Promise<any> {
1116
+ const collection = db.collection(targetCollection)
1117
+ return await collection.findOne({ [foreignKey]: sourceId })
1118
+ }
1119
+
1120
+ static async resolveOneToMany(
1121
+ db: Db,
1122
+ sourceCollection: string,
1123
+ targetCollection: string,
1124
+ sourceId: string,
1125
+ foreignKey: string
1126
+ ): Promise<any[]> {
1127
+ const collection = db.collection(targetCollection)
1128
+ return await collection.find({ [foreignKey]: sourceId }).toArray()
1129
+ }
1130
+
1131
+ static async resolveManyToMany(
1132
+ db: Db,
1133
+ sourceCollection: string,
1134
+ targetCollection: string,
1135
+ joinCollection: string,
1136
+ sourceId: string
1137
+ ): Promise<any[]> {
1138
+ const joinCol = db.collection(joinCollection)
1139
+ const targetCol = db.collection(targetCollection)
1140
+
1141
+ const connections = await joinCol.find({
1142
+ [\`\${sourceCollection.toLowerCase()}Id\`]: sourceId
1143
+ }).toArray()
1144
+
1145
+ const targetIds = connections.map(c => c[\`\${targetCollection.toLowerCase()}Id\`])
1146
+
1147
+ return await targetCol.find({
1148
+ _id: { $in: targetIds.map(id => new ObjectId(id)) }
1149
+ }).toArray()
1150
+ }
1151
+
1152
+ static formatDocument(doc: any): any {
1153
+ if (!doc) return doc
1154
+
1155
+ const formatted = { ...doc }
1156
+ if (formatted._id) {
1157
+ formatted.id = formatted._id.toString()
1158
+ delete formatted._id
1159
+ }
1160
+
1161
+ return formatted
1162
+ }
1163
+ }
1164
+ `;
1165
+ }
1166
+ generateModelsIndex(models) {
1167
+ return models.map(model => `export { ${model.name}Delegate } from './${model.name}'`).join('\n');
1168
+ }
1169
+ generateModelFiles(models) {
1170
+ const files = {};
1171
+ for (const model of models) {
1172
+ files[`models/${model.name}.ts`] = this.generateModelDelegate(model);
1173
+ }
1174
+ return files;
1175
+ }
1176
+ generateModelDelegate(model) {
1177
+ return `// This file was auto-generated by Lenz. Do not edit manually.
1178
+ // @generated
1179
+
1180
+ import { Collection, ObjectId, Document } from 'mongodb'
1181
+ import type {
1182
+ ${model.name},
1183
+ ${model.name}CreateInput,
1184
+ ${model.name}UpdateInput,
1185
+ ${model.name}WhereInput,
1186
+ ${model.name}QueryOptions,
1187
+ ${model.name}CreateArgs,
1188
+ ${model.name}UpdateArgs,
1189
+ ${model.name}DeleteArgs,
1190
+ ${model.name}UpsertArgs,
1191
+ PaginatedResult,
1192
+ CursorPaginatedResult,
1193
+ OffsetPaginationArgs,
1194
+ CursorPaginationArgs
1195
+ } from '../types'
1196
+ import { QueryBuilder } from '../runtime/query'
1197
+ import { PaginationHelper } from '../runtime/pagination'
1198
+ import { RelationResolver } from '../runtime/relations'
1199
+ import type { LenzClient } from '../client'
1200
+
1201
+ export class ${model.name}Delegate {
1202
+ constructor(private client: LenzClient) {}
1203
+
1204
+ private readonly hiddenFields: string[] = ${JSON.stringify(model.fields.filter(f => f.isHidden).map(f => f.name))};
1205
+
1206
+ private get collection(): Collection<Document> {
1207
+ return this.client.$db.collection('${model.collectionName}')
1208
+ }
1209
+
1210
+ async findUnique(args: { where: ${model.name}WhereInput } & ${model.name}QueryOptions): Promise<${model.name} | null> {
1211
+ const query = QueryBuilder.buildWhere(args.where)
1212
+ const options = QueryBuilder.buildOptions(args, this.hiddenFields)
1213
+
1214
+ const doc = await this.collection.findOne(query, options)
1215
+ if (!doc) return null
1216
+
1217
+ const formatted = RelationResolver.formatDocument(doc)
1218
+
1219
+ if (args.include) {
1220
+ return await this.includeRelations(formatted, args.include)
1221
+ }
1222
+
1223
+ return formatted
1224
+ }
1225
+
1226
+ async findMany(args?: ${model.name}QueryOptions): Promise<${model.name}[]> {
1227
+ const { cursor, ...otherArgs } = args || {}
1228
+ let where = args?.where || {}
1229
+
1230
+ // Handle cursor-based pagination
1231
+ if (cursor) {
1232
+ const cursorCondition = QueryBuilder.buildCursorCondition(cursor, args?.orderBy)
1233
+ where = {
1234
+ ...where,
1235
+ ...cursorCondition
1236
+ }
1237
+ }
1238
+
1239
+ const query = QueryBuilder.buildWhere(where)
1240
+ const options = QueryBuilder.buildOptions(otherArgs || {}, this.hiddenFields)
1241
+
1242
+ const mongoCursor = this.collection.find(query, options)
1243
+ const docs = await mongoCursor.toArray()
1244
+ const formatted = docs.map(RelationResolver.formatDocument)
1245
+
1246
+ if (args?.include) {
1247
+ return await Promise.all(
1248
+ formatted.map(doc => this.includeRelations(doc, args.include!))
1249
+ )
1250
+ }
1251
+
1252
+ return formatted
1253
+ }
1254
+
1255
+ async findFirst(args?: ${model.name}QueryOptions): Promise<${model.name} | null> {
1256
+ const results = await this.findMany({ ...args, take: 1 })
1257
+ return results[0] || null
1258
+ }
1259
+
1260
+ async create(args: ${model.name}CreateArgs): Promise<${model.name}> {
1261
+ const now = new Date()
1262
+ const document = {
1263
+ ...args.data,
1264
+ _id: new ObjectId(),
1265
+ createdAt: now,
1266
+ updatedAt: now
1267
+ }
1268
+
1269
+ const result = await this.collection.insertOne(document)
1270
+ const createdDoc = await this.collection.findOne({ _id: result.insertedId })
1271
+
1272
+ if (!createdDoc) {
1273
+ throw new Error('Failed to create document')
1274
+ }
1275
+
1276
+ const formatted = RelationResolver.formatDocument(createdDoc)
1277
+
1278
+ if (args.include) {
1279
+ return await this.includeRelations(formatted, args.include)
1280
+ }
1281
+
1282
+ return formatted
1283
+ }
1284
+
1285
+ async createMany(args: { data: ${model.name}CreateInput[] }): Promise<{ count: number }> {
1286
+ const now = new Date()
1287
+ const documents = args.data.map(data => ({
1288
+ ...data,
1289
+ _id: new ObjectId(),
1290
+ createdAt: now,
1291
+ updatedAt: now
1292
+ }))
1293
+
1294
+ const result = await this.collection.insertMany(documents)
1295
+ return { count: result.insertedCount }
1296
+ }
1297
+
1298
+ async update(args: ${model.name}UpdateArgs): Promise<${model.name}> {
1299
+ const query = QueryBuilder.buildWhere(args.where)
1300
+ const updateData = {
1301
+ ...args.data,
1302
+ updatedAt: new Date()
1303
+ }
1304
+
1305
+ const update = QueryBuilder.buildUpdate(updateData)
1306
+ const result = await this.collection.findOneAndUpdate(
1307
+ query,
1308
+ update,
1309
+ { returnDocument: 'after' }
1310
+ )
1311
+
1312
+ if (!result.value) {
1313
+ throw new Error('Document not found')
1314
+ }
1315
+
1316
+ const formatted = RelationResolver.formatDocument(result.value)
1317
+
1318
+ if (args.include) {
1319
+ return await this.includeRelations(formatted, args.include)
1320
+ }
1321
+
1322
+ return formatted
1323
+ }
1324
+
1325
+ async updateMany(args: { where?: ${model.name}WhereInput; data: ${model.name}UpdateInput }): Promise<{ count: number }> {
1326
+ const query = args.where ? QueryBuilder.buildWhere(args.where) : {}
1327
+ const updateData = {
1328
+ ...args.data,
1329
+ updatedAt: new Date()
1330
+ }
1331
+
1332
+ const update = QueryBuilder.buildUpdate(updateData)
1333
+ const result = await this.collection.updateMany(query, update)
1334
+ return { count: result.modifiedCount }
1335
+ }
1336
+
1337
+ async upsert(args: ${model.name}UpsertArgs): Promise<${model.name}> {
1338
+ const query = QueryBuilder.buildWhere(args.where)
1339
+ const existing = await this.collection.findOne(query)
1340
+
1341
+ if (existing) {
1342
+ return this.update({
1343
+ where: args.where,
1344
+ data: args.update,
1345
+ select: args.select,
1346
+ include: args.include
1347
+ })
1348
+ } else {
1349
+ return this.create({
1350
+ data: args.create,
1351
+ select: args.select,
1352
+ include: args.include
1353
+ })
1354
+ }
1355
+ }
1356
+
1357
+ async delete(args: ${model.name}DeleteArgs): Promise<${model.name} | null> {
1358
+ const query = QueryBuilder.buildWhere(args.where)
1359
+ const doc = await this.collection.findOne(query)
1360
+
1361
+ if (!doc) return null
1362
+
1363
+ await this.collection.deleteOne(query)
1364
+ const formatted = RelationResolver.formatDocument(doc)
1365
+
1366
+ if (args.include) {
1367
+ return await this.includeRelations(formatted, args.include)
1368
+ }
1369
+
1370
+ return formatted
1371
+ }
1372
+
1373
+ async deleteMany(args: { where?: ${model.name}WhereInput }): Promise<{ count: number }> {
1374
+ const query = args.where ? QueryBuilder.buildWhere(args.where) : {}
1375
+ const result = await this.collection.deleteMany(query)
1376
+ return { count: result.deletedCount }
1377
+ }
1378
+
1379
+ async count(args?: { where?: ${model.name}WhereInput }): Promise<number> {
1380
+ const query = args?.where ? QueryBuilder.buildWhere(args.where) : {}
1381
+ return await this.collection.countDocuments(query)
1382
+ }
1383
+
1384
+ async aggregate<T = any>(pipeline: any[]): Promise<T[]> {
1385
+ return await this.collection.aggregate(pipeline).toArray() as T[]
1386
+ }
1387
+
1388
+ /**
1389
+ * Offset-based pagination (page-based)
1390
+ * Similar to Prisma's skip/take pagination
1391
+ */
1392
+ async findManyPaginated(args: OffsetPaginationArgs<${model.name}>): Promise<PaginatedResult<${model.name}>> {
1393
+ const page = args.page || 1
1394
+ const perPage = args.take || args.perPage || 10
1395
+ const skip = (page - 1) * perPage
1396
+
1397
+ // Get total count
1398
+ const where = args.where ? QueryBuilder.buildWhere(args.where) : {}
1399
+ const total = await this.collection.countDocuments(where)
1400
+
1401
+ // Get paginated data
1402
+ const query = QueryBuilder.buildWhere(args.where || {})
1403
+ const options = {
1404
+ skip,
1405
+ limit: perPage,
1406
+ ...QueryBuilder.buildOptions(args, this.hiddenFields)
1407
+ }
1408
+
1409
+ const mongoCursor = this.collection.find(query, options)
1410
+ const docs = await mongoCursor.toArray()
1411
+ const data = docs.map(RelationResolver.formatDocument)
1412
+
1413
+ // Handle includes
1414
+ let resultData = data
1415
+ if (args.include) {
1416
+ resultData = await Promise.all(
1417
+ data.map(doc => this.includeRelations(doc, args.include!))
1418
+ )
1419
+ }
1420
+
1421
+ const totalPages = Math.ceil(total / perPage)
1422
+
1423
+ return {
1424
+ data: resultData,
1425
+ meta: {
1426
+ total,
1427
+ page,
1428
+ perPage,
1429
+ totalPages,
1430
+ hasNextPage: page < totalPages,
1431
+ hasPreviousPage: page > 1
1432
+ }
1433
+ }
1434
+ }
1435
+
1436
+ /**
1437
+ * Cursor-based pagination
1438
+ * More efficient for large datasets, similar to Relay/GraphQL cursor pagination
1439
+ */
1440
+ async findManyWithCursor(args: CursorPaginationArgs<${model.name}>): Promise<CursorPaginatedResult<${model.name}>> {
1441
+ const take = args.take || 20
1442
+ let where = args.where || {}
1443
+
1444
+ // Apply cursor if provided
1445
+ if (args.cursor) {
1446
+ const cursorCondition = QueryBuilder.buildCursorCondition(args.cursor, args.orderBy)
1447
+ where = {
1448
+ ...where,
1449
+ ...cursorCondition
1450
+ }
1451
+ }
1452
+
1453
+ // Get total count (optional, for pageInfo)
1454
+ const query = QueryBuilder.buildWhere(args.where || {})
1455
+ const totalCount = await this.collection.countDocuments(query)
1456
+
1457
+ // Get data with one extra to check if there's more
1458
+ const options = {
1459
+ limit: take + 1,
1460
+ ...QueryBuilder.buildOptions(args, this.hiddenFields)
1461
+ }
1462
+
1463
+ const mongoCursor = this.collection.find(where, options)
1464
+ const docs = await mongoCursor.toArray()
1465
+ const hasNextPage = docs.length > take
1466
+
1467
+ // Remove extra element if exists
1468
+ const resultDocs = hasNextPage ? docs.slice(0, take) : docs
1469
+ const data = resultDocs.map(RelationResolver.formatDocument)
1470
+
1471
+ // Handle includes
1472
+ let resultData = data
1473
+ if (args.include) {
1474
+ resultData = await Promise.all(
1475
+ data.map(doc => this.includeRelations(doc, args.include!))
1476
+ )
1477
+ }
1478
+
1479
+ // Create edges with cursors
1480
+ const edges = resultData.map(doc => ({
1481
+ node: doc,
1482
+ cursor: PaginationHelper.createCursor(doc)
1483
+ }))
1484
+
1485
+ return {
1486
+ edges,
1487
+ pageInfo: {
1488
+ hasNextPage,
1489
+ hasPreviousPage: !!args.cursor,
1490
+ startCursor: edges[0]?.cursor,
1491
+ endCursor: edges[edges.length - 1]?.cursor
1492
+ },
1493
+ totalCount
1494
+ }
1495
+ }
1496
+
1497
+ /**
1498
+ * Find with advanced pagination options
1499
+ * Supports both offset and cursor pagination
1500
+ */
1501
+ async findWithPagination(
1502
+ args: ${model.name}QueryOptions & {
1503
+ paginationType?: 'offset' | 'cursor'
1504
+ page?: number
1505
+ perPage?: number
1506
+ cursor?: string | ObjectId
1507
+ }
1508
+ ): Promise<any> {
1509
+ const paginationType = args.paginationType || 'offset'
1510
+
1511
+ if (paginationType === 'cursor') {
1512
+ return this.findManyWithCursor({
1513
+ where: args.where,
1514
+ select: args.select,
1515
+ include: args.include,
1516
+ orderBy: args.orderBy,
1517
+ cursor: args.cursor,
1518
+ take: args.take || args.perPage
1519
+ })
1520
+ } else {
1521
+ return this.findManyPaginated({
1522
+ where: args.where,
1523
+ select: args.select,
1524
+ include: args.include,
1525
+ orderBy: args.orderBy,
1526
+ skip: args.skip,
1527
+ take: args.take,
1528
+ page: args.page,
1529
+ perPage: args.perPage
1530
+ })
1531
+ }
1532
+ }
1533
+
1534
+ /**
1535
+ * Count with pagination info
1536
+ */
1537
+ async countWithPagination(args?: { where?: ${model.name}WhereInput }): Promise<{
1538
+ total: number
1539
+ filtered?: number
1540
+ }> {
1541
+ const where = args?.where ? QueryBuilder.buildWhere(args.where) : {}
1542
+ const total = await this.collection.estimatedDocumentCount()
1543
+ const filtered = await this.collection.countDocuments(where)
1544
+
1545
+ return {
1546
+ total,
1547
+ filtered: total !== filtered ? filtered : undefined
1548
+ }
1549
+ }
1550
+
1551
+ private applySelect(document: any, select: any): any {
1552
+ if (!select) {
1553
+ // If no select, exclude hidden fields by default
1554
+ if (this.hiddenFields.length === 0) return document;
1555
+ const result = { ...document };
1556
+ this.hiddenFields.forEach(field => {
1557
+ delete result[field];
1558
+ });
1559
+ return result;
1560
+ }
1561
+
1562
+ // Build projection using QueryBuilder
1563
+ const projection = QueryBuilder.buildProjection(select, this.hiddenFields);
1564
+ if (!projection) return document;
1565
+
1566
+ const result = { ...document };
1567
+ // Apply projection (simplified - only top-level fields)
1568
+ for (const [field, value] of Object.entries(projection)) {
1569
+ if (value === 0 && field in result) {
1570
+ delete result[field];
1571
+ }
1572
+ // If value === 1, keep the field (already present)
1573
+ }
1574
+ return result;
1575
+ }
1576
+
1577
+ private async includeRelations(document: any, include: any): Promise<any> {
1578
+ const result = { ...document }
1579
+
1580
+ // TODO: Implement relation inclusion
1581
+ return result
1582
+ }
1583
+
1584
+ // Raw access
1585
+ get $raw() {
1586
+ return {
1587
+ collection: this.collection,
1588
+ find: async (filter: any) => await this.collection.find(filter).toArray(),
1589
+ findOne: async (filter: any) => await this.collection.findOne(filter),
1590
+ insertOne: async (doc: any) => await this.collection.insertOne(doc),
1591
+ updateOne: async (filter: any, update: any) => await this.collection.updateOne(filter, update),
1592
+ deleteOne: async (filter: any) => await this.collection.deleteOne(filter),
1593
+ aggregate: async (pipeline: any[]) => await this.collection.aggregate(pipeline).toArray()
1594
+ }
1595
+ }
1596
+ }
1597
+ `;
1598
+ }
1599
+ toCamelCase(str) {
1600
+ return str.charAt(0).toLowerCase() + str.slice(1);
1601
+ }
1602
+ }
1603
+ exports.CodeGenerator = CodeGenerator;
1604
+ //# sourceMappingURL=CodeGenerator.js.map