@bairock/lenz 0.0.18 → 0.0.20

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