@bairock/lenz 0.0.17 → 0.0.19

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 (2) hide show
  1. package/README.md +138 -1044
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -4,577 +4,155 @@ GraphQL SDL → MongoDB ORM — TypeScript-клиент и Apollo Server мод
4
4
 
5
5
  ## Features
6
6
 
7
- - ✅ **GraphQL SDL Schema** Define models using GraphQL syntax
8
- - **TypeScript First** Full type safety and autocompletion
9
- - **MongoDB Only** Native MongoDB, no SQL
10
- - **Prisma Style** Familiar configuration and client generation
11
- - **Auto-generated Client** Generate TypeScript client from GraphQL schema
12
- - ✅ **CRUD Modules** — Generate Apollo Server typeDefs + resolvers (`lenz generate crud`)
13
- - **Relations Support** — One-to-One, One-to-Many, Many-to-Many
14
- - **Smart Loading Strategies** — Automatic choice between populate (separate queries) and lookup (server-side joins)
15
- - **Automatic Indexing** Intelligent index creation for foreign key fields
16
- - **Transactions** — ACID transactions with MongoDB
17
-
18
- ## Сравнение с Prisma
19
-
20
- Lenz вдохновлён Prisma, но имеет важные отличия, связанные с MongoDB. Ниже — подробное сравнение.
21
-
22
- ### Таблица директив
23
-
24
- | Prisma атрибут | Lenz директива | Статус |
25
- |---------------|----------------|--------|
26
- | `@id` | `@id` | ✅ Полная поддержка (ObjectId) |
27
- | `@unique` | `@unique` | Полная поддержка |
28
- | `@index` | `@index` | Полная поддержка |
29
- | `@default(value)` | `@default(value: "...")` | Полная поддержка |
30
- | `@default(uuid())` | `@default(generator: "uuid")` | Эквивалент |
31
- | `@default(now())` | `@default(generator: "now")` | ✅ Эквивалент |
32
- | `@default(cuid())` | `@default(generator: "cuid")` | ✅ Базовый CUID |
33
- | `@default(cuid2())` | `@default(generator: "cuid2")` | CUID2-совместимый |
34
- | `@default(autoincrement())` | | Не применимо к MongoDB |
35
- | `@default(dbgenerated())` | | Не применимо |
36
- | `@relation(fields, references)` | `@relation(field)` | ⚠️ Упрощённый синтаксис (одно поле) |
37
- | `@updatedAt` | `@updatedAt` | Полная поддержка |
38
- | `@map` | `@map(name)` | ✅ Полная поддержка |
39
- | `@@map` | `@modelMap(name)` | ✅ Полная поддержка |
40
- | `@ignore` | `@ignore` | Полная поддержка |
41
- | `@hidden` | `@hide` | Аналог |
42
- | `@@unique([a, b])` | `@compoundUnique(fields: [...])` | Полная поддержка |
43
- | `@@index([a, b])` | `@compoundIndex(fields: [...])` | ✅ Полная поддержка |
44
- | `@@id([a, b])` | `@compoundId(fields: [...])` | ✅ Полная поддержка |
45
- | `@@index([...], type: "text")` | `@fulltext(fields: [...])` | ✅ Эквивалент (MongoDB text index) |
46
- | `@email` | `@email` | ✅ Полная поддержка |
47
- | `@url` | `@url` | ✅ Полная поддержка |
48
- | `@@index(map: "...")` | `@compoundIndex(fields: [...], name: "...")` | ✅ name аргумент |
49
- | `@id(map: "...")` | `@id(map: "...")` | ✅ Полная поддержка |
50
- | `@unique(map: "...")` | `@unique(map: "...")` | ✅ Полная поддержка |
51
- | `@@schema` | — | ❌ Не применимо (multi-schema) |
52
- | `@@view` | — | ❌ Не реализован |
53
- | `@relation(onDelete: Restrict)` | `@relation(onDelete: "Restrict")` | ✅ Полная поддержка |
54
- | `@embedded` (Prisma MongoDB) | `@embedded` | ✅ Полная поддержка |
55
- | — | `@regex(pattern)` | ✅ Только в Lenz |
56
- | — | `@@fulltext` | ✅ Только в Lenz (MongoDB text index) |
57
-
58
- ### Возможности
59
-
60
- | Возможность | Prisma | Lenz |
61
- |------------|--------|------|
62
- | **Поддерживаемые БД** | PostgreSQL, MySQL, SQLite, MongoDB, SQL Server, CockroachDB | **MongoDB** |
63
- | **CLI генерация** | `prisma generate` | `lenz generate` |
64
- | **Schema DSL** | Prisma Schema Language (.prisma) | GraphQL SDL (.graphql) |
65
- | **Типизация** | Полная (генерация типов) | Полная (генерация типов) |
66
- | **CRUD** | ✅ | ✅ |
67
- | **Relations (1:1, 1:m, m:n)** | ✅ | ✅ |
68
- | **Pagination (offset + cursor)** | ✅ | ✅ |
69
- | **Transactions** | ✅ | ✅ (требуется replica set) |
70
- | **Aggregation** | `groupBy`, `aggregate` | Пайплайн MongoDB + `aggregate` |
71
- | **Raw queries** | `$queryRaw`, `$executeRaw` | `$raw`, `aggregateRaw` |
72
- | **Client extensions** | `$extends` | `$extends` |
73
- | **Middleware** | `$use` (удалён в Prisma 6.14) | ❌ (используйте `$extends`) |
74
- | **Миграции** | Prisma Migrate | ❌ (ленивое создание коллекций) |
75
- | **Интроспекция** | `prisma db pull` | ❌ |
76
- | **Seed** | `prisma db seed` | ❌ |
77
- | **Studio** | Prisma Studio | ❌ |
78
- | **Драйверы** | Встроенные + driver adapters | MongoDB Native Driver |
79
- | **Views** | `@@view` (v5+) | ❌ |
80
- | **Multi-schema** | `@@schema` (PostgreSQL) | ❌ |
81
-
82
- ### Чего нет в Lenz (но есть в Prisma)
83
-
84
- 1. **Другие БД** — Lenz поддерживает только MongoDB. Prisma работает с PostgreSQL, MySQL, SQLite, SQL Server, CockroachDB и MongoDB.
85
- 2. **Миграции** — нет аналога Prisma Migrate. Коллекции и индексы создаются лениво при первом подключении.
86
- 3. **Интроспекция** — нет `prisma db pull` для импорта схемы из существующей БД.
87
- 4. **Типы данных** — `Decimal`, `Unsupported("...")` не реализованы. `Bytes` и `BigInt` ✅ реализованы.
88
- 5. **Views** — нет поддержки представлений (`@@view`).
89
- 6. **Seed** — нет встроенного фреймворка для наполнения тестовыми данными.
90
- 7. **Studio** — нет графического редактора данных (аналога Prisma Studio).
91
- 8. **Составные внешние ключи** — `@relation` поддерживает только одно поле, не `fields: [a, b], references: [c, d]`.
92
-
93
- ### Что есть в Lenz, чего нет в Prisma
94
-
95
- 1. **`@embedded`** — встроенные документы (MongoDB-специфичная возможность).
96
- 2. **`@hide`** — исключение полей из результатов по умолчанию.
97
- 3. **`@regex(pattern)`** — кастомная валидация через регулярные выражения.
98
- 4. **Стратегии загрузки** — автоматический выбор между `populate` (отдельные запросы) и `lookup` (`$lookup` aggregation) в зависимости от типа связи.
99
- 5. **Гео-пространственные фильтры** — `near`, `nearSphere`, `geoWithin`, `geoIntersects`.
100
- 6. **Атомарные операции с массивами** — `push`, `pull`, `addToSet`, `pop`, `pullAll`, `pushAll`.
101
- 7. **`@fulltext`** — декларативное создание MongoDB text index для полнотекстового поиска.
102
- 8. **Автоматическая инициализация массивов** — обязательные поля-массивы автоматически инициализируются пустым массивом.
103
- 9. **Bytes** — тип `Buffer` для бинарных данных (MongoDB BinData).
104
- 10. **BigInt** — тип `bigint` для 64-битных целых чисел (MongoDB Long).
105
-
106
- ## CRUD Operations
107
-
108
- Lenz provides a comprehensive set of CRUD operations similar to Prisma, with full TypeScript support and MongoDB-native performance.
109
-
110
- ### Create
111
-
112
- **Create a single record:**
113
- ```ts
114
- const user = await lenz.user.create({
115
- data: {
116
- email: "elsa@prisma.io",
117
- name: "Elsa Prisma",
118
- },
119
- });
120
- ```
121
-
122
- **Create multiple records:**
123
- ```ts
124
- const createMany = await lenz.user.createMany({
125
- data: [
126
- { name: "Bob", email: "bob@prisma.io" },
127
- { name: "Yewande", email: "yewande@prisma.io" },
128
- ],
129
- });
130
- // Returns: { count: 2 }
131
- ```
132
-
133
- ### Read
134
-
135
- **Get record by ID or unique field:**
136
- ```ts
137
- // By unique field
138
- const user = await lenz.user.findUnique({
139
- where: { email: "elsa@prisma.io" },
140
- });
141
-
142
- // By ID
143
- const user = await lenz.user.findUnique({
144
- where: { id: "99" },
145
- });
146
- ```
147
-
148
- **Get all records:**
149
- ```ts
150
- const users = await lenz.user.findMany();
151
- ```
152
-
153
- **Get first matching record:**
154
- ```ts
155
- const user = await lenz.user.findFirst({
156
- where: { posts: { some: { likes: { gt: 100 } } } },
157
- orderBy: { id: "desc" },
158
- });
159
- ```
160
-
161
- **Filter records:**
162
- ```ts
163
- // Single field filter
164
- const users = await lenz.user.findMany({
165
- where: { email: { endsWith: "prisma.io" } },
166
- });
167
-
168
- // Multiple conditions with OR/AND
169
- const users = await lenz.user.findMany({
170
- where: {
171
- OR: [{ name: { startsWith: "E" } }, { AND: { profileViews: { gt: 0 }, role: "ADMIN" } }],
172
- },
173
- });
174
-
175
- // Filter by related records
176
- const users = await lenz.user.findMany({
177
- where: {
178
- email: { endsWith: "prisma.io" },
179
- posts: { some: { published: false } },
180
- },
181
- });
182
- ```
183
-
184
- **Select fields:**
185
- ```ts
186
- const user = await lenz.user.findUnique({
187
- where: { email: "emma@prisma.io" },
188
- select: { email: true, name: true },
189
- });
190
- // Returns: { email: 'emma@prisma.io', name: "Emma" }
191
- ```
192
-
193
- **Include related records:**
194
- ```ts
195
- const users = await lenz.user.findMany({
196
- where: { role: "ADMIN" },
197
- include: { posts: true },
198
- });
199
- ```
200
-
201
- ### Update
202
-
203
- **Update a single record:**
204
- ```ts
205
- const updateUser = await lenz.user.update({
206
- where: { email: "viola@prisma.io" },
207
- data: { name: "Viola the Magnificent" },
208
- });
209
- ```
210
-
211
- **Update multiple records:**
212
- ```ts
213
- const updateUsers = await lenz.user.updateMany({
214
- where: { email: { contains: "prisma.io" } },
215
- data: { role: "ADMIN" },
216
- });
217
- // Returns: { count: 19 }
218
- ```
219
-
220
- **Upsert (update or create):**
221
- ```ts
222
- const upsertUser = await lenz.user.upsert({
223
- where: { email: "viola@prisma.io" },
224
- update: { name: "Viola the Magnificent" },
225
- create: { email: "viola@prisma.io", name: "Viola the Magnificent" },
226
- });
227
- ```
228
-
229
- **Atomic number operations:**
230
- ```ts
231
- await lenz.post.updateMany({
232
- data: {
233
- views: { increment: 1 },
234
- likes: { increment: 1 },
235
- },
236
- });
237
- ```
238
-
239
- ### Delete
240
-
241
- **Delete a single record:**
242
- ```ts
243
- const deleteUser = await lenz.user.delete({
244
- where: {
245
- email: "bert@prisma.io",
246
- },
247
- });
248
- ```
249
-
250
- **Delete multiple records:**
251
- ```ts
252
- const deleteUsers = await lenz.user.deleteMany({
253
- where: {
254
- email: {
255
- contains: "prisma.io",
256
- },
257
- },
258
- });
259
- ```
260
-
261
- **Delete all records:**
262
- ```ts
263
- const deleteUsers = await lenz.user.deleteMany({});
264
- ```
265
-
266
- **Cascading deletes:** Lenz does not automatically cascade deletes. You must manually delete related records or use transactions:
267
-
268
- ```ts
269
- const transaction = await lenz.$transaction([
270
- lenz.post.deleteMany({ where: { authorId: "7" } }),
271
- lenz.user.delete({ where: { id: "7" } }),
272
- ]);
273
- ```
274
-
275
- ### Pagination
276
-
277
- Lenz supports both offset-based and cursor-based pagination directly in the `findMany` method.
278
-
279
- **Offset-based pagination (skip/take):**
280
- ```ts
281
- // Get records 41-50 (page 5 with 10 per page)
282
- const users = await lenz.user.findMany({
283
- skip: 40,
284
- take: 10,
285
- where: { /* your filters */ },
286
- orderBy: { createdAt: 'desc' }
287
- });
288
- ```
289
-
290
- **Cursor-based pagination (more efficient for large datasets):**
291
- ```ts
292
- // Get first page (first 10 records)
293
- const firstPage = await lenz.post.findMany({
294
- take: 10,
295
- where: { published: true },
296
- orderBy: { id: 'asc' },
297
- });
298
-
299
- const lastPost = firstPage[9]; // Last item on page
300
- const cursor = lastPost?.id; // Use ID as cursor (string)
301
-
302
- // Get next page (next 10 records AFTER cursor)
303
- const nextPage = await lenz.post.findMany({
304
- take: 10,
305
- skip: 1, // Skip the cursor item itself to avoid duplication
306
- cursor: cursor, // Pass cursor as string
307
- where: { published: true },
308
- orderBy: { id: 'asc' },
309
- });
310
- ```
311
-
312
- **Note:** For cursor-based pagination, the cursor should be the value of the field you're ordering by (typically `id`). The cursor is passed as a string or ObjectId. Ensure the field is unique and sequential.
313
-
314
- ### Aggregation
315
-
316
- Lenz provides direct access to MongoDB aggregation pipeline for complex data analysis, as well as count operations.
317
-
318
- **Basic aggregation with MongoDB pipeline:**
319
- ```ts
320
- const aggregations = await lenz.user.aggregate([
321
- { $match: { role: "ADMIN" } },
322
- { $group: { _id: "$country", totalViews: { $sum: "$profileViews" } } },
323
- { $sort: { totalViews: -1 } }
324
- ]);
325
- ```
326
-
327
- **Count operations:**
328
- ```ts
329
- // Count all users
330
- const userCount = await lenz.user.count();
331
-
332
- // Count with filtering
333
- const activeUsers = await lenz.user.count({
334
- where: { profileViews: { gte: 100 } },
335
- });
336
-
337
- // Count relations (using _count in select)
338
- const usersWithPostCount = await lenz.user.findMany({
339
- select: {
340
- _count: {
341
- select: { posts: true },
342
- },
343
- },
344
- });
345
- ```
346
-
347
- **Aggregation with grouping and filtering:**
348
- ```ts
349
- // Group posts by category and calculate average likes
350
- const stats = await lenz.post.aggregate([
351
- { $match: { published: true } },
352
- { $group: {
353
- _id: "$categoryId",
354
- totalPosts: { $sum: 1 },
355
- avgLikes: { $avg: "$likes" },
356
- maxLikes: { $max: "$likes" }
357
- }},
358
- { $sort: { avgLikes: -1 } }
359
- ]);
360
- ```
361
-
362
- **Distinct values:**
363
- ```ts
364
- // Get distinct roles using aggregation
365
- const distinctRoles = await lenz.user.aggregate([
366
- { $group: { _id: "$role" } },
367
- { $project: { role: "$_id" } }
368
- ]);
369
- ```
370
-
371
- **Note:** The `aggregate()` method accepts raw MongoDB aggregation pipeline stages. For type-safe aggregations, you can define TypeScript interfaces for the aggregation result.
7
+ ### Schema & Models
8
+ - GraphQL SDLмодели через `type X @model`
9
+ - 20 директив: `@id`, `@unique`, `@index`, `@default`, `@relation`, `@embedded`, `@createdAt`, `@updatedAt`, `@hide`, `@map`, `@ignore`, `@email`, `@url`, `@regex`, `@modelMap`, `@compoundUnique`, `@compoundIndex`, `@compoundId`, `@fulltext`
10
+ - 11 типов: `String`, `Int`, `Float`, `Boolean`, `ID`, `DateTime`, `Date`, `Json`, `ObjectId`, `Bytes`, `BigInt`
11
+ - Enums, embedded documents, relations (1:1, 1:m, m:n, m:1)
12
+
13
+ ### Generated ORM Client
14
+ - Полная типизация + `.d.ts`
15
+ - CRUD делегаты: `findUnique`, `findFirst`, `findMany`, `create`, `update`, `upsert`, `delete`, `count`, `aggregate`, `groupBy`
16
+ - Nested operations (create/connect/connectOrCreate/disconnect/set/update/delete/upsert)
17
+ - Все Prisma-фильтры: `equals`, `not`, `in`, `lt`/`lte`/`gt`/`gte`, `contains`, `startsWith`, `endsWith`, `mode: insensitive`
18
+ - `AND`/`OR`/`NOT`, массивы (`has`/`hasEvery`/`hasSome`), full-text (`search`), geo-spatial
19
+ - Offset + cursor пагинация (Relay Connection)
20
+ - Атомарные обновления: `$push`/`$pull`/`$addToSet`/`$pop`/`$pullAll` с `$each`/`$position`, `increment`/`decrement`/`multiply`/`divide`
21
+ - Агрегации: `_count`, `_sum`, `_avg`, `_min`, `_max`, `groupBy`
22
+ - Транзакции (ACID), `$extends` (query interception, computed fields), default-генераторы (`uuid`/`now`/`cuid`/`cuid2`/`ulid`)
23
+ - Валидация (`@email`/`@url`/`@regex` с ReDoS), cascade (`Cascade`/`SetNull`/`Restrict`)
24
+ - Две стратегии загрузки: `populate` (eager) и `lookup` (`$lookup`)
25
+
26
+ ### Apollo Server CRUD (`lenz generate crud`)
27
+ - typeDefs + resolvers на каждую модель
28
+ - Резолверы через `{ lenz }` из контекста
29
+ - Баррель `index.ts` `import { typeDefs, resolvers } from './src'`
30
+ - SDL inputTypes (`*CreateInput`, `*UpdateInput`, все фильтры) в `inputTypes.ts`
31
+
32
+ ### Runtime
33
+ - QueryBuilder конвертация типизированных запросов в MongoDB
34
+ - Авто-ObjectId (`id` `_id` с 24-char hex)
35
+ - BSON: ObjectId string, Long bigint, Binary → Buffer
36
+ - Logger с уровнями `query`/`info`/`warn`/`error`
37
+ - Типизированные ошибки: `NotFoundError`, `UniqueConstraintError`, `ValidationError`, `ConnectionError`, `TransactionError`
38
+
39
+ ### CLI
40
+ - `lenz init` создать проект
41
+ - `lenz generate orm` сгенерировать ORM-клиент
42
+ - `lenz generate crud` сгенерировать Apollo Server модули
372
43
 
373
44
  ## Quick Start
374
45
 
375
- ### 1. Install Lenz
376
-
377
46
  ```bash
378
- npm install lenz
379
- ```
380
-
381
- ### 2. Initialize your project
47
+ # Установка
48
+ npm install @bairock/lenz
382
49
 
383
- ```bash
50
+ # Инициализация проекта
384
51
  npx lenz init
385
- ```
386
52
 
387
- This creates:
388
-
389
- - `lenz/schema.graphql` - Your GraphQL schema
390
- - `lenz/lenz.config.ts` - Configuration file
391
- - `.env.example` - Environment variables template
53
+ # Настрой схему в lenz/schema.graphql
54
+ # Сгенерируй ORM-клиент
55
+ npx lenz generate orm
392
56
 
393
- ### 3. Edit your schema
57
+ # (опционально) Сгенерируй CRUD для Apollo Server
58
+ npx lenz generate crud
59
+ ```
394
60
 
395
- Edit `lenz/schema.graphql`:
61
+ Пример схемы (`lenz/schema.graphql`):
396
62
 
397
63
  ```graphql
398
64
  type User @model {
399
- id: ID! @id
65
+ id: String @id
400
66
  email: String! @unique
401
67
  name: String!
402
-
403
- # One-to-many relationship (auto-selects lookup strategy)
404
68
  posts: [Post!]! @relation(field: "postIds")
405
- postIds: [ID!]! # Array automatically indexed
406
-
407
- # One-to-one relationship (auto-selects populate strategy)
408
- profile: Profile @relation(field: "profileId")
409
- profileId: ID # Sparse index automatically created
410
-
69
+ postIds: [ID!]!
411
70
  createdAt: DateTime! @createdAt
412
71
  updatedAt: DateTime! @updatedAt
413
72
  }
414
73
 
415
74
  type Post @model {
416
- id: ID! @id
75
+ id: String @id
417
76
  title: String!
418
77
  content: String
419
-
420
- # Many-to-one relationship (auto-selects populate strategy)
78
+ published: Boolean! @default(value: "false")
421
79
  author: User! @relation(field: "authorId")
422
- authorId: ID! # Foreign key index automatically created
423
-
424
- # Many-to-many relationship with ID arrays (auto-selects lookup strategy)
425
- categories: [Category!]! @relation(field: "categoryIds")
426
- categoryIds: [ID!]! # Multikey index automatically created
80
+ authorId: ID!
81
+ tags: [String!]
427
82
  }
428
-
429
- type Profile @model {
430
- id: ID! @id
431
- bio: String
432
- user: User @relation(field: "userId", strategy: "populate")
433
- userId: ID
434
- }
435
-
436
- type Category @model {
437
- id: ID! @id
438
- name: String! @unique
439
- posts: [Post!]! @relation(field: "postIds", strategy: "lookup", index: false)
440
- postIds: [ID!]! # No auto-index (index: false)
441
- }
442
- ```
443
-
444
- ### 4. Generate the client
445
-
446
- ```bash
447
- npx lenz generate
448
83
  ```
449
84
 
450
- This generates the client in `generated/lenz/client`.
451
-
452
- ### 5. Use in your code
85
+ Использование:
453
86
 
454
87
  ```typescript
455
- import { LenzClient } from '../generated/lenz/client';
88
+ import { LenzClient } from '../generated/lenz/client/index.js'
456
89
 
457
- async function main() {
458
- const lenz = new LenzClient({
459
- url: process.env.MONGODB_URI,
460
- database: 'myapp',
461
- log: ['query', 'info'] // Enable query logging
462
- });
90
+ const lenz = new LenzClient({ url: 'mongodb://localhost:27017/myapp' })
91
+ await lenz.$connect()
463
92
 
464
- await lenz.$connect();
93
+ // Create
94
+ const user = await lenz.user.create({
95
+ data: { email: 'test@test.com', name: 'Test' },
96
+ include: { posts: true }
97
+ })
465
98
 
466
- // Create a user with posts (populate strategy for posts)
467
- const user = await lenz.user.create({
468
- data: {
469
- email: 'alice@example.com',
470
- name: 'Alice',
471
- posts: {
472
- create: [
473
- { title: 'Hello World', content: 'My first post' }
474
- ]
475
- }
476
- },
477
- include: {
478
- posts: true, // Uses populate strategy (separate queries)
479
- profile: true // Uses populate strategy (one-to-one)
480
- }
481
- });
99
+ // Read
100
+ const users = await lenz.user.findMany({
101
+ where: { email: { contains: 'test' } },
102
+ orderBy: { createdAt: 'desc' },
103
+ take: 10
104
+ })
482
105
 
483
- // Query with include - automatic strategy selection
484
- const authorWithBooks = await lenz.author.findUnique({
485
- where: { id: 'some-author-id' },
486
- include: {
487
- books: true // Uses lookup strategy (one-to-many, single query)
488
- }
489
- });
106
+ // Update
107
+ await lenz.user.update({
108
+ where: { id: user.id },
109
+ data: { name: 'Updated' }
110
+ })
490
111
 
491
- // Manual array synchronization for lookup strategy
492
- // When creating a book, add its ID to author.bookIds
493
- const newBook = await lenz.book.create({
494
- data: {
495
- title: 'New Book',
496
- authorId: 'author-id'
497
- }
498
- });
112
+ // Delete
113
+ await lenz.user.delete({ where: { id: user.id } })
499
114
 
500
- // Manually update the author's bookIds array
501
- await lenz.author.update({
502
- where: { id: 'author-id' },
503
- data: {
504
- bookIds: {
505
- push: newBook.id // Add new book ID to array
506
- }
507
- }
508
- });
115
+ // Transaction
116
+ await lenz.$transaction(async (tx) => {
117
+ await lenz.user.update({ where: { id: '1' }, data: { name: 'x' } })
118
+ await lenz.post.create({ data: { title: 'New', authorId: '1' } })
119
+ })
120
+ ```
509
121
 
510
- // Complex query with filtering and sorting
511
- const posts = await lenz.post.findMany({
512
- where: {
513
- title: { contains: 'tutorial' },
514
- categories: {
515
- some: { name: { equals: 'Programming' } }
516
- }
517
- },
518
- orderBy: { createdAt: 'desc' },
519
- take: 10,
520
- include: {
521
- author: true, // populate strategy
522
- categories: true // many-to-many lookup strategy (server-side join with $lookup)
523
- }
524
- });
122
+ ## Apollo Server Integration
525
123
 
526
- // Transaction example (requires replica set)
527
- await lenz.$transaction(async (tx) => {
528
- const updatedUser = await lenz.user.update({
529
- where: { id: user.id },
530
- data: { name: 'Alice Updated' }
531
- });
124
+ ```typescript
125
+ import { ApolloServer } from '@apollo/server'
126
+ import { startStandaloneServer } from '@apollo/server/standalone'
127
+ import { LenzClient } from '../generated/lenz/client/index.js'
128
+ import { typeDefs, resolvers } from './src/index.js'
532
129
 
533
- await lenz.post.create({
534
- data: {
535
- title: 'Transaction Post',
536
- authorId: user.id
537
- }
538
- });
539
- });
130
+ const lenz = new LenzClient({ url: process.env.MONGO_URL })
131
+ await lenz.$connect()
540
132
 
541
- // Advanced many-to-many lookup with filtering
542
- // Uses MongoDB aggregation pipeline for server-side joins
543
- const postsWithTechCategories = await lenz.post.findMany({
544
- where: {
545
- categories: {
546
- some: {
547
- name: { equals: 'Technology' }
548
- }
549
- }
550
- },
551
- include: {
552
- categories: {
553
- where: {
554
- name: { equals: 'Technology' }
555
- }
556
- }
557
- },
558
- orderBy: { createdAt: 'desc' },
559
- take: 5
560
- });
133
+ const server = new ApolloServer({ typeDefs, resolvers })
134
+ const { url } = await startStandaloneServer(server, {
135
+ context: async () => ({ lenz }),
136
+ })
137
+ ```
561
138
 
562
- // Note: For lookup strategy, array synchronization is manual
563
- // When creating relationships, update both arrays:
564
- // await lenz.post.update({ where: { id: postId }, data: { categoryIds: { push: categoryId } } });
565
- // await lenz.category.update({ where: { id: categoryId }, data: { postIds: { push: postId } } });
139
+ ## CLI
566
140
 
567
- await lenz.$disconnect();
568
- }
141
+ ```bash
142
+ npx lenz init # Создать проект
143
+ npx lenz generate orm # Сгенерировать ORM-клиент
144
+ npx lenz generate orm --config lenz/lenz.config.ts
145
+ npx lenz generate crud # Сгенерировать CRUD для Apollo
146
+ npx lenz generate crud --schema ./schema.graphql --output ./src
147
+ npx lenz generate # Справка по генерации
569
148
  ```
570
149
 
571
- ## Configuration
572
-
573
- ### `lenz/lenz.config.ts`
150
+ ## Config
574
151
 
575
152
  ```typescript
153
+ // lenz/lenz.config.ts
576
154
  import 'dotenv/config'
577
- import { defineConfig } from 'lenz/config'
155
+ import { defineConfig } from '@bairock/lenz/config'
578
156
 
579
157
  export default defineConfig({
580
158
  schema: 'schema.graphql',
@@ -586,544 +164,60 @@ export default defineConfig({
586
164
  client: {
587
165
  output: '../generated/lenz/client',
588
166
  },
589
- },
590
- log: ['query', 'info', 'warn', 'error'] as const,
591
- })
592
- ```
593
-
594
- ### `lenz/lenz.config.js` (for JavaScript projects)
595
-
596
- For JavaScript projects, use `lenz.config.js` as an ESM module:
597
-
598
- ```javascript
599
- // ESM module - lenz.config.js
600
- import 'dotenv/config';
601
-
602
- export default {
603
- schema: 'schema.graphql',
604
- datasource: {
605
- url: process.env.MONGODB_URI || 'mongodb://localhost:27017',
606
- database: process.env.MONGODB_DATABASE || 'myapp',
607
- },
608
- generate: {
609
- client: {
610
- output: '../generated/lenz/client',
167
+ crud: {
168
+ output: './src',
611
169
  },
612
170
  },
613
- log: ['query', 'info', 'warn', 'error'],
614
- };
615
- ```
616
-
617
- > **Note**: Lenz supports only ESM modules for JavaScript projects. The `lenz init` command automatically detects your project type (TypeScript or JavaScript) and creates the appropriate config file (`lenz.config.ts` for TypeScript, `lenz.config.js` for JavaScript). JavaScript config files must use ESM syntax (`export default`).
618
-
619
- ## GraphQL Directives
620
-
621
- Lenz extends GraphQL with custom directives:
622
-
623
- - `@model` - Marks a type as a database model
624
- - `@id` - Marks a field as primary key (auto-generated ObjectId)
625
- - `@unique` - Creates a unique index
626
- - `@index` - Creates a regular index
627
- - `@default(value: "...")` - Sets default value
628
- - `@relation(field: "...", strategy: "populate|lookup", index: true|false, onDelete: "NoAction|Cascade|SetNull")` - Defines relation with loading strategy, index control, and cascade delete behavior (default: `NoAction`)
629
- - `@createdAt` - Auto-sets creation timestamp
630
- - `@updatedAt` - Auto-updates timestamp
631
- - `@embedded` - Marks a type as embedded document
632
- - `@hide` - Excludes field from query results by default
633
-
634
- ## Relations
635
-
636
- Lenz implements MongoDB-native relations with explicit foreign key fields and intelligent loading strategies. Each relation type has an optimal default strategy for loading related data (see [Relation Loading Strategies](#relation-loading-strategies) for details).
637
-
638
- **Key principles:**
639
- - Foreign keys must be in the **source model** (the model containing `@relation`)
640
- - Arrays are automatically initialized for required array fields
641
- - Indexes are automatically created for foreign key fields (configurable)
642
- - Loading strategy is automatically selected based on relation type
643
-
644
- ### Automatic Array Initialization
645
-
646
- Required array fields (like `bookIds: [ID!]!`) are automatically initialized with empty arrays `[]` when creating documents. This prevents MongoDB `$in needs an array` errors when querying relations.
647
-
648
- ```typescript
649
- // When creating a document, required arrays are automatically set to []
650
- const author = await lenz.author.create({
651
- data: {
652
- name: 'John Doe',
653
- // bookIds is automatically set to [] even if not provided
654
- }
655
- });
656
- ```
657
-
658
- ### One-to-Many / Many-to-One
659
- A bidirectional relationship where one side has an array and the other has a single reference:
660
-
661
- ```graphql
662
- type Author @model {
663
- id: ID! @id
664
- books: [Book!]! @relation(field: "bookIds") # one-to-many side, auto: lookup strategy
665
- bookIds: [ID!]! # array automatically indexed (multikey index)
666
- }
667
-
668
- type Book @model {
669
- id: ID! @id
670
- author: Author! @relation(field: "authorId") # many-to-one side, auto: populate strategy
671
- authorId: ID! # single ID automatically indexed (sparse index)
672
- }
673
- ```
674
-
675
- - **Author → Book (one-to-many):** Uses `lookup` strategy by default (server-side join). Requires manual synchronization of `bookIds` array.
676
- - **Book → Author (many-to-one):** Uses `populate` strategy by default (separate query). Foreign key `authorId` has automatic sparse index.
677
- - **Indexes:** Multikey index on `bookIds`, sparse index on `authorId` (created automatically).
678
-
679
- ### One-to-One (foreign key single ID in source model)
680
-
681
- ```graphql
682
- type User @model {
683
- id: ID! @id
684
- profile: Profile @relation(field: "profileId") # auto: populate strategy
685
- profileId: ID # optional, sparse index
686
- }
687
-
688
- type Profile @model {
689
- id: ID! @id
690
- user: User @relation(field: "userId", strategy: "populate") # explicit populate
691
- userId: ID # optional, sparse index
692
- }
693
- ```
694
-
695
- - **Strategy:** Uses `populate` by default (separate queries for each side)
696
- - **Indexes:** Sparse indexes on `profileId` and `userId` (optional fields)
697
- - **Optional:** Both sides can be optional (nullable IDs)
698
- - **Bidirectional:** Each side maintains its own foreign key
699
-
700
- ### Many-to-Many (ID arrays on both sides)
701
-
702
- ```graphql
703
- type Post @model {
704
- id: ID! @id
705
- categories: [Category!]! @relation(field: "categoryIds") # auto: lookup strategy (default for arrays)
706
- categoryIds: [ID!]! # multikey index
707
- }
708
-
709
- type Category @model {
710
- id: ID! @id
711
- posts: [Post!]! @relation(field: "postIds", strategy: "lookup", index: false)
712
- postIds: [ID!]! # no auto-index (index: false)
713
- }
714
- ```
715
-
716
- - **Strategy:** Uses `lookup` by default when foreign key arrays are specified (server-side joins with MongoDB's `$lookup` aggregation), `populate` for join collections
717
- - **Indexes:** Multikey indexes on both array fields (configurable with `index: false`)
718
- - **Bidirectional:** Both sides maintain arrays of IDs
719
- - **Manual sync:** You must manually synchronize both arrays when creating/updating relations (for `lookup` strategy)
720
-
721
- **Example - Manual synchronization for many-to-many lookup strategy:**
722
-
723
- ```typescript
724
- // Create a post and category
725
- const post = await lenz.post.create({
726
- data: {
727
- title: 'My Post',
728
- categoryIds: [] // Initialize empty array
729
- }
730
- });
731
-
732
- const category = await lenz.category.create({
733
- data: {
734
- name: 'Technology',
735
- postIds: [] // Initialize empty array
736
- }
737
- });
738
-
739
- // Add category to post
740
- await lenz.post.update({
741
- where: { id: post.id },
742
- data: {
743
- categoryIds: {
744
- push: category.id // Add category ID to post's array
745
- }
746
- }
747
- });
748
-
749
- // Add post to category (bidirectional sync)
750
- await lenz.category.update({
751
- where: { id: category.id },
752
- data: {
753
- postIds: {
754
- push: post.id // Add post ID to category's array
755
- }
756
- }
757
- });
758
- ```
759
-
760
- **Important:** Foreign keys must always be in the **source model** (the model containing the `@relation` directive). Classic one-to-many patterns with foreign keys in target models will cause validation errors.
761
-
762
- ## Relation Loading Strategies
763
-
764
- Lenz automatically chooses the optimal strategy for loading related data, balancing performance and simplicity. You can also explicitly override the strategy.
765
-
766
- ### Populate Strategy (Default for oneToOne, manyToOne)
767
-
768
- Uses separate queries - first fetches the main document, then queries related collections. Best for simple relationships and sharded environments.
769
-
770
- ```graphql
771
- type User @model {
772
- profile: Profile @relation(field: "profileId", strategy: "populate")
773
- profileId: ID
774
- }
775
- ```
776
-
777
- ### Lookup Strategy (Default for oneToMany)
778
-
779
- Uses MongoDB's `$lookup` aggregation operator for server-side joins. Best for high-read scenarios with bidirectional relationships.
780
-
781
- ```graphql
782
- type Author @model {
783
- books: [Book!]! @relation(field: "bookIds", strategy: "lookup")
784
- bookIds: [ID!]!
785
- }
786
- ```
787
-
788
- **Note:** When using `lookup` strategy, you must manually synchronize ID arrays (e.g., `bookIds`) when creating/updating/deleting related documents.
789
-
790
- **Include options support:** Lookup strategy now supports `where`, `orderBy`, `take`, and `skip` options for filtering, sorting, and paginating related documents.
791
-
792
- Example:
793
- ```typescript
794
- const author = await lenz.author.findUnique({
795
- where: { id: 'author-id' },
796
- include: {
797
- books: {
798
- where: { published: true },
799
- orderBy: { createdAt: 'desc' },
800
- take: 5
801
- }
802
- }
803
- });
804
- ```
805
-
806
- ### Automatic Strategy Selection
807
-
808
- | Relation Type | Default Strategy | Reason |
809
- |--------------|------------------|--------|
810
- | `oneToOne` | `populate` | Simple relationships, no arrays |
811
- | `manyToOne` | `populate` | Single reference, no arrays |
812
- | `oneToMany` | `lookup` | Array of IDs in source document (e.g., Author.bookIds) |
813
- | `manyToMany` | `lookup` (if foreign key array specified) or `populate` (if join collection) | Foreign key arrays use server-side joins, join collections use separate queries |
814
-
815
- **Note:** The lookup strategy now supports include options (where, orderBy, take, skip) for both array foreign keys and single foreign key relations. However, nested includes are not yet supported for lookup strategy but are fully supported for populate strategy.
816
-
817
- **Many-to-Many Lookup Strategy:**
818
-
819
- When a many-to-many relationship uses foreign key arrays (either single-sided or both sides), Lenz automatically uses the `lookup` strategy with MongoDB's `$lookup` aggregation operator. This performs server-side joins for optimal read performance. For example, `post.categoryIds` array referencing categories.
820
-
821
- ```graphql
822
- type Post @model {
823
- categories: [Category!]! @relation(field: "categoryIds", strategy: "lookup")
824
- categoryIds: [ID!]! # multikey index automatically created
825
- }
826
-
827
- type Category @model {
828
- posts: [Post!]! @relation(field: "postIds", strategy: "lookup", index: false)
829
- postIds: [ID!]! # no auto-index (index: false)
830
- }
831
- ```
832
-
833
- **Many-to-Many Populate Strategy:**
834
-
835
- When a many-to-many relationship uses a join collection (no foreign key arrays), Lenz uses the `populate` strategy with separate queries.
836
-
837
- ```graphql
838
- type Post @model {
839
- categories: [Category!]! @relation # no field parameter → join collection
840
- }
841
-
842
- type Category @model {
843
- posts: [Post!]! @relation
844
- }
845
- ```
846
-
847
- ### Cascade Delete Behavior (`onDelete`)
848
-
849
- Lenz supports cascade delete operations on relations. By default, **no cascade is performed** (`onDelete: "NoAction"`) for performance and safety. You must explicitly opt in.
850
-
851
- | Value | Behavior |
852
- |-------|----------|
853
- | `NoAction` (default) | No cascade. Deleted document's related references become orphaned. |
854
- | `Cascade` | When a document is deleted, all related documents are also deleted. |
855
- | `SetNull` | When a document is deleted, foreign key fields in related documents are set to `null` (only for nullable FK fields). |
856
-
857
- **Important:** Cascade operations negatively impact write performance because each delete triggers additional queries. Use only when necessary.
858
-
859
- Examples:
860
-
861
- ```graphql
862
- type Author @model {
863
- id: ID! @id
864
- posts: [Post!]! @relation(field: "postIds", onDelete: "Cascade") # Deletes all posts when author is deleted
865
- postIds: [ID!]!
866
- }
867
-
868
- type Profile @model {
869
- id: ID! @id
870
- user: User @relation(field: "userId", onDelete: "SetNull") # Sets userId to null when user is deleted
871
- userId: ID
872
- }
873
- ```
874
-
875
- **Note:** `onDelete: "SetNull"` is only supported on nullable foreign key fields (optional `ID` fields, not `ID!`). Using it on required fields will cause a validation error.
876
-
877
- ### Automatic Indexing
878
-
879
- Lenz automatically creates indexes for foreign key fields when `index: true` (default). You can disable this:
880
-
881
- ```graphql
882
- type Author @model {
883
- books: [Book!]! @relation(field: "bookIds", index: false)
884
- bookIds: [ID!]!
885
- }
886
- ```
887
-
888
- - For arrays (oneToMany): Creates multikey indexes
889
- - For single IDs (oneToOne, manyToOne): Creates sparse indexes
890
-
891
- ## Supported Field Types
892
-
893
- - `String`
894
- - `Int`
895
- - `Float`
896
- - `Boolean`
897
- - `ID` (stored as String)
898
- - `DateTime` (JavaScript Date)
899
- - `Date` (JavaScript Date)
900
- - `Json` (any JSON value)
901
- - `ObjectId` (MongoDB ObjectId as string)
902
-
903
- ## Best Practices with MongoDB
904
-
905
- ### 1. Schema Design
906
- - **Embed documents** when data is accessed together frequently (use `@embedded` directive)
907
- - **Reference documents** when data is large or accessed independently
908
- - **Use arrays judiciously** - large arrays can impact performance
909
- - **Denormalize carefully** - duplicate data for read performance, but maintain consistency
910
-
911
- ### 2. Index Strategy
912
- - **Auto-index foreign keys** - Lenz does this by default with `index: true`
913
- - **Add compound indexes** for frequently queried field combinations
914
- - **Use sparse indexes** for optional fields (auto-created for optional relations)
915
- - **Monitor index usage** with `$indexStats`
916
-
917
- ### 3. Performance Optimization
918
- - **Use `lookup` strategy** for high-read bidirectional relationships
919
- - **Use `populate` strategy** for simple relationships and sharded clusters
920
- - **Batch operations** with `createMany`, `updateMany` instead of individual calls
921
- - **Project only needed fields** with `select` option
922
-
923
- ### 4. Query Patterns
924
- - **Filter early** - push filters to database with `where` clauses
925
- - **Avoid large skip/limit** - use cursor-based pagination (`cursor` option)
926
- - **Use transactions** for multi-document consistency (requires replica set)
927
- - **Monitor slow queries** with MongoDB profiler
928
-
929
- ### 5. Data Consistency
930
- - **Manual array sync** - when using `lookup` strategy, maintain ID arrays
931
- - **Use default values** for required fields to avoid validation errors
932
- - **Handle race conditions** with optimistic concurrency or transactions
933
- - **Implement soft deletes** with `deletedAt` field instead of hard deletes
934
-
935
- ### 6. Many-to-Many Performance Optimization
936
- - **Array size limits:** Keep array sizes reasonable (<1000 elements). Large arrays increase `$lookup` complexity and memory usage.
937
- - **Batch synchronization:** When updating multiple relationships, batch array operations to reduce database round trips.
938
- - **Index management:** Regularly monitor and optimize multikey indexes for array fields.
939
- - **Alternative approaches:** For extremely large many-to-many relationships, consider:
940
- - **Denormalization:** Embed frequently accessed data
941
- - **Hybrid approach:** Use `lookup` for recent/active relationships, `populate` for historical
942
- - **Materialized views:** Pre-compute relationships for read-heavy scenarios
943
-
944
- ### 7. When to Use Each Strategy
945
-
946
- #### Use **Populate** when:
947
- - Simple one-to-one or many-to-one relationships
948
- - Working with sharded clusters (`$lookup` doesn't work across shards)
949
- - Relationships are rarely accessed
950
- - You prefer automatic data consistency
951
- - Many-to-many relationships with join collections (no foreign key arrays)
952
- - Small datasets where separate queries are acceptable
953
-
954
- #### Use **Lookup** when:
955
- - High-read scenarios with one-to-many or many-to-many relationships
956
- - Need server-side joins for complex filtering/sorting
957
- - Willing to manually maintain ID arrays
958
- - Maximum read performance is critical
959
- - Many-to-many relationships with foreign key arrays (both sides)
960
- - Medium to large datasets where join performance matters
961
-
962
- #### Special Considerations for Many-to-Many Lookup:
963
- - **Array size:** Large arrays (>1000 IDs) can impact `$lookup` performance. Consider denormalization or hybrid approaches.
964
- - **Indexing:** Multikey indexes are essential for array fields. Monitor index size and performance.
965
- - **Consistency:** Manual array synchronization requires careful application logic to maintain data integrity.
966
- - **Sharding:** `$lookup` doesn't work across shards. For sharded clusters, use `populate` strategy or ensure related documents are on the same shard.
967
-
968
- ### 8. Production Readiness
969
- - **Enable replica set** for transaction support
970
- - **Set up proper connection pooling** in `lenz.config.ts`
971
- - **Implement retry logic** for transient failures
972
- - **Use environment-specific configurations**
973
- - **Monitor connection health** with regular `ping` commands
974
-
975
- ## CLI Commands
976
-
977
- ```bash
978
- # Initialize project
979
- npx lenz init
980
-
981
- # Generate ORM client
982
- npx lenz generate orm
983
-
984
- # Generate ORM client with custom config
985
- npx lenz generate orm --config lenz/lenz.config.ts
986
-
987
- # Generate Apollo Server CRUD modules
988
- npx lenz generate crud
989
-
990
- # Generate CRUD modules with custom paths
991
- npx lenz generate crud --schema lenz/schema.graphql --output ./src
992
-
993
- # Show help
994
- npx lenz --help
995
-
996
- # Show generate subcommands
997
- npx lenz generate
171
+ })
998
172
  ```
999
173
 
1000
- ## Structure After Generation
174
+ ## Generated Structure
1001
175
 
176
+ ### ORM-клиент (`lenz generate orm`)
1002
177
  ```
1003
- my-app/
1004
- ├── lenz/
1005
- ├── schema.graphql # Your GraphQL schema
1006
- │ └── lenz.config.ts|js # Configuration (TypeScript or JavaScript ESM)
1007
- ├── generated/
1008
- │ └── lenz/
1009
- │ └── client/ # Generated client
1010
- ├── index.ts # Main export
1011
- ├── client.ts # LenzClient class
1012
- ├── types.ts # TypeScript types
1013
- │ ├── enums.ts # Enum definitions
1014
- ├── inputTypes.ts # GraphQL input types (filters, CreateInput, UpdateInput)
1015
- ├── runtime/ # Runtime utilities
1016
- │ └── models/ # Model delegates
1017
- └── .env # Environment variables
178
+ generated/lenz/client/
179
+ ├── index.ts # Экспорт LenzClient
180
+ ├── client.ts # Класс LenzClient
181
+ ├── types.ts # TypeScript типы
182
+ ├── enums.ts # Enum-константы
183
+ ├── inputTypes.ts # SDL input-типы (gql)
184
+ ├── models/
185
+ ├── index.ts
186
+ ├── User.ts # UserDelegate (CRUD)
187
+ └── Post.ts
188
+ └── runtime/
189
+ ├── query.ts # QueryBuilder
190
+ ├── pagination.ts # PaginationHelper
191
+ ├── relations.ts # RelationResolver
192
+ ├── errors.ts # Runtime ошибки
193
+ └── logger.ts # Logger
1018
194
  ```
1019
195
 
1020
- ### `lenz generate crud` — Apollo Server CRUD Modules
1021
-
1022
- Generates per-model Apollo Server modules (typeDefs + resolvers) from `lenz/schema.graphql`.
1023
-
1024
- **Опции:**
1025
- - `-c, --config <path>` — путь к конфиг-файлу (по умолчанию: `lenz/lenz.config.ts`, с автоопределением `lenz/lenz.config.js`)
1026
- - `-s, --schema <path>` — путь к схеме (по умолчанию: из конфига или `schema.graphql`)
1027
- - `-o, --output <path>` — выходная директория (по умолчанию: из конфига `generate.crud.output` или `./src`)
1028
-
1029
- **Определение языка:** генератор определяет TypeScript или JavaScript по расширению конфиг-файла:
1030
- - `lenz/lenz.config.ts` → генерирует `.ts` файлы
1031
- - `lenz/lenz.config.js` → генерирует `.js` файлы
1032
-
1033
- Если конфиг не найден, по умолчанию используются `.ts` файлы.
1034
-
1035
- **Структура выхода:**
196
+ ### CRUD модули (`lenz generate crud`)
1036
197
  ```
1037
198
  src/
1038
- ├── index.ts # Баррель-файл: typeDefs + resolvers всех модулей
1039
- ├── Category/
199
+ ├── index.ts # Баррель (typeDefs + resolvers)
200
+ ├── User/
1040
201
  │ ├── typeDefs/
1041
- │ │ ├── mutations.ts # createCategory, updateCategory, deleteCategory
1042
- │ │ └── queries.ts # findUniqueCategory, findFirstCategory, findManyCategory, findManyCategoryCount
202
+ │ │ ├── mutations.ts # createUser, updateUser, deleteUser
203
+ │ │ └── queries.ts # findUnique, findFirst, findMany, findManyCount
1043
204
  │ ├── resolvers/
1044
- │ │ ├── mutations.ts # Resolvers using { lenz } from context
205
+ │ │ ├── mutations.ts # { lenz } из контекста
1045
206
  │ │ └── queries.ts
1046
- │ └── index.ts # Aggregated typeDefs and resolvers
1047
- ├── User/
1048
- │ ├── ...
1049
207
  │ └── index.ts
1050
- └── ... (все модели)
208
+ └── Post/...
1051
209
  ```
1052
210
 
1053
- Баррель `src/index.ts` автоматически импортирует `inputTypes` из ORM-клиента и собирает все модули:
1054
-
1055
- ```typescript
1056
- // src/index.ts (сгенерировано)
1057
- import { mergeTypeDefs, mergeResolvers } from '@graphql-tools/merge';
1058
- import { inputTypes } from '../generated/lenz/client/inputTypes.js';
1059
- import { categoryTypeDefs, categoryResolvers } from './Category/index.js';
1060
- import { userTypeDefs, userResolvers } from './User/index.js';
1061
-
1062
- export const typeDefs = mergeTypeDefs([inputTypes, ...categoryTypeDefs, ...userTypeDefs]);
1063
- export const resolvers = mergeResolvers([categoryResolvers, userResolvers]);
1064
- ```
1065
-
1066
- **Пример конфига (опционально):**
1067
- ```js
1068
- // lenz/lenz.config.js
1069
- export default {
1070
- schema: 'lenz/schema.graphql',
1071
- generate: {
1072
- crud: {
1073
- output: './src',
1074
- },
1075
- },
1076
- };
1077
- ```
1078
-
1079
- **Использование с Apollo Server:**
1080
-
1081
- Резолверы получают экземпляр `LenzClient` через поле `lenz` GraphQL контекста — без импорта сервисов. Баррель `src/index.ts` уже собирает все typeDefs и resolvers:
1082
-
1083
- ```typescript
1084
- import { ApolloServer } from '@apollo/server';
1085
- import { startStandaloneServer } from '@apollo/server/standalone';
1086
- import { LenzClient } from '../generated/lenz/client/index.js';
1087
- import { typeDefs, resolvers } from './src/index.js';
1088
-
1089
- const lenz = new LenzClient({ url: process.env.MONGO_URL });
1090
- await lenz.$connect();
1091
-
1092
- const server = new ApolloServer({ typeDefs, resolvers });
1093
-
1094
- const { url } = await startStandaloneServer(server, {
1095
- context: async () => ({ lenz }),
1096
- });
1097
- ```
1098
-
1099
- > **Важно:** `src/index.ts` автоматически импортирует `inputTypes` из ORM-клиента и объединяет их со всеми CRUD-модулями через `mergeTypeDefs` / `mergeResolvers`. Путь к ORM-клиенту берётся из конфига (`generate.client.output`) или используется `../generated/lenz/client` по умолчанию.
1100
-
1101
211
  ## Development
1102
212
 
1103
- ### Build
1104
-
1105
- ```bash
1106
- npm run build
1107
- ```
1108
-
1109
- ### Watch mode
1110
-
1111
- ```bash
1112
- npm run dev
1113
- ```
1114
-
1115
- ### Lint
1116
-
1117
- ```bash
1118
- npm run lint
1119
- ```
1120
-
1121
- ### Format
1122
-
1123
213
  ```bash
1124
- npm run format
214
+ npm run build # tsc
215
+ npm run dev # tsc --watch
216
+ npm test # vitest
217
+ npm run lint # eslint
218
+ npm run format # prettier
1125
219
  ```
1126
220
 
1127
221
  ## License
1128
222
 
1129
- MIT
223
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bairock/lenz",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "Lenz ORM for MongoDB with GraphQL SDL - Prisma 7.0 style",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",