create-questpie 2.0.3 → 2.1.0

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 (153) hide show
  1. package/dist/index.mjs +544 -87
  2. package/package.json +2 -3
  3. package/templates/elysia/AGENTS.md +56 -0
  4. package/templates/elysia/CLAUDE.md +39 -0
  5. package/templates/elysia/Dockerfile +24 -0
  6. package/templates/elysia/README.md +148 -0
  7. package/templates/elysia/docker/init-extensions.sql +11 -0
  8. package/templates/elysia/docker-compose.yml +21 -0
  9. package/templates/elysia/env.example +16 -0
  10. package/templates/elysia/gitignore +6 -0
  11. package/templates/elysia/package.json +47 -0
  12. package/templates/elysia/questpie.config.ts +12 -0
  13. package/templates/elysia/src/index.ts +21 -0
  14. package/templates/elysia/src/lib/auth-client.ts +32 -0
  15. package/templates/elysia/src/lib/client.ts +13 -0
  16. package/templates/elysia/src/lib/env.ts +24 -0
  17. package/templates/elysia/src/lib/query-client.ts +18 -0
  18. package/templates/elysia/src/lib/query.ts +18 -0
  19. package/templates/elysia/src/questpie/server/.generated/context.gen.ts +200 -0
  20. package/templates/elysia/src/questpie/server/.generated/entities.gen.ts +84 -0
  21. package/templates/elysia/src/questpie/server/.generated/factories.ts +65 -0
  22. package/templates/elysia/src/questpie/server/.generated/index.ts +131 -0
  23. package/templates/elysia/src/questpie/server/.generated/names.gen.ts +25 -0
  24. package/templates/elysia/src/questpie/server/app.ts +10 -0
  25. package/templates/elysia/src/questpie/server/collections/index.ts +1 -0
  26. package/templates/elysia/src/questpie/server/collections/posts.collection.ts +10 -0
  27. package/templates/elysia/src/questpie/server/config/auth.ts +8 -0
  28. package/templates/elysia/src/questpie/server/config/openapi.ts +10 -0
  29. package/templates/elysia/src/questpie/server/globals/index.ts +1 -0
  30. package/templates/elysia/src/questpie/server/globals/site-settings.global.ts +10 -0
  31. package/templates/elysia/src/questpie/server/modules.ts +8 -0
  32. package/templates/elysia/src/questpie/server/questpie.config.ts +21 -0
  33. package/templates/elysia/tsconfig.json +28 -0
  34. package/templates/hono/AGENTS.md +56 -0
  35. package/templates/hono/CLAUDE.md +39 -0
  36. package/templates/hono/Dockerfile +24 -0
  37. package/templates/hono/README.md +148 -0
  38. package/templates/hono/docker/init-extensions.sql +11 -0
  39. package/templates/hono/docker-compose.yml +21 -0
  40. package/templates/hono/env.example +16 -0
  41. package/templates/hono/gitignore +6 -0
  42. package/templates/hono/package.json +47 -0
  43. package/templates/hono/questpie.config.ts +12 -0
  44. package/templates/hono/src/index.ts +30 -0
  45. package/templates/hono/src/lib/auth-client.ts +32 -0
  46. package/templates/hono/src/lib/client.ts +13 -0
  47. package/templates/hono/src/lib/env.ts +24 -0
  48. package/templates/hono/src/lib/query-client.ts +18 -0
  49. package/templates/hono/src/lib/query.ts +18 -0
  50. package/templates/hono/src/questpie/server/.generated/context.gen.ts +200 -0
  51. package/templates/hono/src/questpie/server/.generated/entities.gen.ts +84 -0
  52. package/templates/hono/src/questpie/server/.generated/factories.ts +65 -0
  53. package/templates/hono/src/questpie/server/.generated/index.ts +131 -0
  54. package/templates/hono/src/questpie/server/.generated/names.gen.ts +25 -0
  55. package/templates/hono/src/questpie/server/app.ts +10 -0
  56. package/templates/hono/src/questpie/server/collections/index.ts +1 -0
  57. package/templates/hono/src/questpie/server/collections/posts.collection.ts +10 -0
  58. package/templates/hono/src/questpie/server/config/auth.ts +8 -0
  59. package/templates/hono/src/questpie/server/config/openapi.ts +10 -0
  60. package/templates/hono/src/questpie/server/globals/index.ts +1 -0
  61. package/templates/hono/src/questpie/server/globals/site-settings.global.ts +10 -0
  62. package/templates/hono/src/questpie/server/modules.ts +8 -0
  63. package/templates/hono/src/questpie/server/questpie.config.ts +21 -0
  64. package/templates/hono/tsconfig.json +28 -0
  65. package/templates/next/AGENTS.md +55 -0
  66. package/templates/next/CLAUDE.md +39 -0
  67. package/templates/next/Dockerfile +25 -0
  68. package/templates/next/README.md +148 -0
  69. package/templates/next/components.json +22 -0
  70. package/templates/next/docker/init-extensions.sql +11 -0
  71. package/templates/next/docker-compose.yml +21 -0
  72. package/templates/next/env.example +16 -0
  73. package/templates/next/gitignore +10 -0
  74. package/templates/next/next-env.d.ts +5 -0
  75. package/templates/next/next.config.ts +20 -0
  76. package/templates/next/package.json +54 -0
  77. package/templates/next/postcss.config.mjs +8 -0
  78. package/templates/next/public/.gitkeep +0 -0
  79. package/templates/next/questpie.config.ts +12 -0
  80. package/templates/next/src/app/admin/[[...all]]/page.tsx +34 -0
  81. package/templates/next/src/app/admin/admin.css +4 -0
  82. package/templates/next/src/app/admin/layout.tsx +63 -0
  83. package/templates/next/src/app/api/[...all]/route.ts +24 -0
  84. package/templates/next/src/app/layout.tsx +24 -0
  85. package/templates/next/src/app/not-found.tsx +18 -0
  86. package/templates/next/src/app/page.tsx +74 -0
  87. package/templates/next/src/app/providers.tsx +11 -0
  88. package/templates/next/src/lib/auth-client.ts +12 -0
  89. package/templates/next/src/lib/client.ts +13 -0
  90. package/templates/next/src/lib/env.ts +24 -0
  91. package/templates/next/src/lib/query-client.ts +18 -0
  92. package/templates/next/src/lib/query.ts +18 -0
  93. package/templates/next/src/questpie/admin/.generated/client.ts +13 -0
  94. package/templates/next/src/questpie/admin/admin.ts +9 -0
  95. package/templates/next/src/questpie/admin/modules.ts +3 -0
  96. package/templates/next/src/questpie/server/.generated/context.gen.ts +204 -0
  97. package/templates/next/src/questpie/server/.generated/entities.gen.ts +100 -0
  98. package/templates/next/src/questpie/server/.generated/factories.ts +204 -0
  99. package/templates/next/src/questpie/server/.generated/index.ts +139 -0
  100. package/templates/next/src/questpie/server/.generated/names.gen.ts +31 -0
  101. package/templates/next/src/questpie/server/app.ts +10 -0
  102. package/templates/next/src/questpie/server/collections/index.ts +1 -0
  103. package/templates/next/src/questpie/server/collections/posts.collection.ts +58 -0
  104. package/templates/next/src/questpie/server/config/admin.ts +80 -0
  105. package/templates/next/src/questpie/server/config/auth.ts +8 -0
  106. package/templates/next/src/questpie/server/config/openapi.ts +10 -0
  107. package/templates/next/src/questpie/server/globals/index.ts +1 -0
  108. package/templates/next/src/questpie/server/globals/site-settings.global.ts +19 -0
  109. package/templates/next/src/questpie/server/modules.ts +9 -0
  110. package/templates/next/src/questpie/server/questpie.config.ts +21 -0
  111. package/templates/next/src/styles.css +125 -0
  112. package/templates/next/tsconfig.json +37 -0
  113. package/templates/tanstack-start/AGENTS.md +35 -600
  114. package/templates/tanstack-start/CLAUDE.md +26 -127
  115. package/templates/tanstack-start/README.md +20 -7
  116. package/templates/tanstack-start/docker/init-extensions.sql +11 -0
  117. package/templates/tanstack-start/docker-compose.yml +1 -0
  118. package/templates/tanstack-start/package.json +1 -0
  119. package/templates/tanstack-start/src/lib/auth-client.ts +1 -1
  120. package/templates/tanstack-start/src/lib/client.ts +1 -1
  121. package/templates/tanstack-start/src/lib/query.ts +18 -0
  122. package/templates/tanstack-start/src/questpie/admin/modules.ts +3 -1
  123. package/templates/tanstack-start/src/questpie/server/.generated/factories.ts +10 -9
  124. package/templates/tanstack-start/src/questpie/server/collections/index.ts +1 -1
  125. package/templates/tanstack-start/src/questpie/server/config/auth.ts +1 -1
  126. package/templates/tanstack-start/src/questpie/server/globals/index.ts +1 -1
  127. package/templates/tanstack-start/src/questpie/server/modules.ts +4 -5
  128. package/templates/tanstack-start/src/questpie/server/questpie.config.ts +3 -2
  129. package/templates/tanstack-start/src/routes/__root.tsx +31 -1
  130. package/templates/tanstack-start/src/routes/api/$.ts +2 -3
  131. package/templates/tanstack-start/src/routes/index.tsx +97 -0
  132. package/templates/tanstack-start/vite.config.ts +2 -2
  133. package/skills/questpie/AGENTS.md +0 -2670
  134. package/skills/questpie/SKILL.md +0 -260
  135. package/skills/questpie/references/auth.md +0 -121
  136. package/skills/questpie/references/business-logic.md +0 -550
  137. package/skills/questpie/references/codegen-plugin-api.md +0 -382
  138. package/skills/questpie/references/crud-api.md +0 -378
  139. package/skills/questpie/references/data-modeling.md +0 -493
  140. package/skills/questpie/references/extend.md +0 -557
  141. package/skills/questpie/references/field-types.md +0 -386
  142. package/skills/questpie/references/infrastructure-adapters.md +0 -545
  143. package/skills/questpie/references/multi-tenancy.md +0 -364
  144. package/skills/questpie/references/production.md +0 -475
  145. package/skills/questpie/references/query-operators.md +0 -125
  146. package/skills/questpie/references/quickstart.md +0 -564
  147. package/skills/questpie/references/rules.md +0 -389
  148. package/skills/questpie/references/tanstack-query.md +0 -520
  149. package/skills/questpie-admin/AGENTS.md +0 -1508
  150. package/skills/questpie-admin/SKILL.md +0 -436
  151. package/skills/questpie-admin/references/blocks.md +0 -331
  152. package/skills/questpie-admin/references/custom-ui.md +0 -305
  153. package/skills/questpie-admin/references/views.md +0 -449
@@ -1,493 +0,0 @@
1
- ---
2
- name: questpie-core/data-modeling
3
- ---
4
-
5
- This skill builds on questpie-core. It covers collections, globals, fields, relations, and localization -- the data modeling layer of QUESTPIE.
6
-
7
- ## Imports
8
-
9
- Data model files import generated factories from the `#questpie/factories` alias:
10
-
11
- ```ts
12
- import { collection, global } from "#questpie/factories";
13
- ```
14
-
15
- Drizzle index helpers come from `drizzle-orm/pg-core`:
16
-
17
- ```ts
18
- import { uniqueIndex, index } from "drizzle-orm/pg-core";
19
- ```
20
-
21
- ## Collections
22
-
23
- A collection is a database-backed data model. Each collection file exports a builder chain:
24
-
25
- ```ts title="collections/posts.ts"
26
- import { collection } from "#questpie/factories";
27
-
28
- export default collection("posts")
29
- .fields(({ f }) => ({
30
- title: f.text(255).required(),
31
- body: f.richText().localized(),
32
- cover: f.upload({ to: "assets", mimeTypes: ["image/*"] }),
33
- status: f
34
- .select([
35
- { value: "draft", label: "Draft" },
36
- { value: "published", label: "Published" },
37
- ])
38
- .default("draft"),
39
- publishedAt: f.date(),
40
- }))
41
- .title(({ f }) => f.title)
42
- .admin(({ c }) => ({
43
- label: { en: "Posts" },
44
- icon: c.icon("ph:article"),
45
- }))
46
- .options({ timestamps: true, versioning: true });
47
- ```
48
-
49
- ### Builder Chain Methods
50
-
51
- | Method | Purpose |
52
- | ----------------------------------------------- | --------------------------------------- |
53
- | `.fields(({ f }) => ({...}))` | Define data fields |
54
- | `.title(({ f }) => f.name)` | Record display title |
55
- | `.admin(({ c }) => ({...}))` | Admin UI metadata (label, icon, hidden) |
56
- | `.indexes(({ table }) => [...])` | Database indexes |
57
- | `.list(({ v, f }) => v.collectionTable({...}))` | List view config |
58
- | `.form(({ v, f }) => v.collectionForm({...}))` | Form view config |
59
- | `.hooks({...})` | Lifecycle hooks |
60
- | `.access({...})` | Access control rules |
61
- | `.preview({...})` | Live preview config |
62
- | `.options({...})` | Timestamps, versioning, soft delete |
63
- | `.search({...})` | Search indexing |
64
- | `.searchable(string[])` | Searchable fields |
65
-
66
- ### Collection Options
67
-
68
- ```ts
69
- .options({
70
- timestamps: true, // adds createdAt, updatedAt
71
- versioning: true, // track content versions
72
- softDelete: true, // mark as deleted instead of removing
73
- })
74
- ```
75
-
76
- ### Indexes
77
-
78
- ```ts
79
- import { uniqueIndex } from "drizzle-orm/pg-core";
80
-
81
- .indexes(({ table }) => [
82
- uniqueIndex("posts_slug_unique").on(table.slug),
83
- ])
84
- ```
85
-
86
- ### Live Preview
87
-
88
- ```ts
89
- .preview({
90
- enabled: true,
91
- position: "right", // "right" | "bottom"
92
- defaultWidth: 50,
93
- url: ({ record }) => `/posts/${record.slug}?preview=true`,
94
- })
95
- ```
96
-
97
- Live Preview uses the existing admin `FormView`, Preview button, `LivePreviewMode`, and iframe. Do not introduce a separate visual-edit form API, a second default form view, or parallel preview API names.
98
-
99
- When workflow is the publication source for pages, public reads use `stage: "published"` and preview/draft-mode reads can load the working stage for authorized editors. Do not add duplicate publication booleans for the same concern.
100
-
101
- ### Access Control
102
-
103
- ```ts
104
- .access({
105
- read: true,
106
- create: ({ session }) => session?.user?.role === "admin",
107
- update: ({ session }) => session?.user?.role === "admin",
108
- delete: ({ session }) => session?.user?.role === "admin",
109
- })
110
- ```
111
-
112
- ### CRUD Operations (Server-Side)
113
-
114
- ```ts
115
- const { collections } = context;
116
-
117
- // Find many
118
- const results = await collections.posts.find({
119
- where: { status: "published" },
120
- orderBy: { publishedAt: "desc" },
121
- limit: 10,
122
- offset: 0,
123
- });
124
- // results.docs: Post[], results.totalDocs: number
125
-
126
- // Find one
127
- const post = await collections.posts.findOne({ where: { id: "abc" } });
128
-
129
- // Create
130
- const newPost = await collections.posts.create({
131
- title: "Hello",
132
- body: "<p>World</p>",
133
- status: "draft",
134
- });
135
-
136
- // Update
137
- await collections.posts.update({
138
- where: { id: "abc" },
139
- data: { status: "published" },
140
- });
141
-
142
- // Delete
143
- await collections.posts.delete({ where: { id: "abc" } });
144
-
145
- // Count
146
- const count = await collections.posts.count({ where: { status: "published" } });
147
- ```
148
-
149
- ## Globals
150
-
151
- A global is a singleton -- one record, no list view. Use for site-wide settings:
152
-
153
- ```ts title="globals/site-settings.ts"
154
- import { global } from "#questpie/factories";
155
-
156
- export const siteSettings = global("siteSettings")
157
- .fields(({ f }) => ({
158
- shopName: f.text().required().default("My App"),
159
- tagline: f.text().localized(),
160
- logo: f.upload({ to: "assets" }),
161
- contactEmail: f.email().required(),
162
- }))
163
- .admin(({ c }) => ({
164
- label: { en: "Site Settings" },
165
- icon: c.icon("ph:gear"),
166
- }))
167
- .options({ timestamps: true, versioning: true })
168
- .access({
169
- read: true,
170
- update: ({ session }) => session?.user?.role === "admin",
171
- });
172
- ```
173
-
174
- ### Global Builder Methods
175
-
176
- Globals share most methods with collections but do NOT support `.list()`, `.indexes()`, `.title()`, or `.preview()`.
177
-
178
- | Method | Purpose |
179
- | ------------------------------------------ | -------------------------- |
180
- | `.fields(({ f }) => ({...}))` | Define data fields |
181
- | `.admin(({ c }) => ({...}))` | Admin label and icon |
182
- | `.form(({ v, f }) => v.globalForm({...}))` | Form layout |
183
- | `.hooks({...})` | Lifecycle hooks |
184
- | `.access({...})` | Read/update access control |
185
- | `.options({...})` | Timestamps, versioning |
186
-
187
- ### Global API
188
-
189
- ```ts
190
- // Server-side
191
- const settings = await globals.siteSettings.get();
192
- await globals.siteSettings.update({ shopName: "New Name" });
193
-
194
- // Client-side
195
- const settings = await client.globals.siteSettings.get();
196
- await client.globals.siteSettings.update({ shopName: "New Name" });
197
- ```
198
-
199
- ## Fields
200
-
201
- Fields are defined inside `.fields()` using the `f` builder. Each field drives the database column, API validation, query operators, client types, and admin UI.
202
-
203
- ### Field Types Overview
204
-
205
- | Field | DB Type | Use Case |
206
- | -------------- | --------------------- | ------------------------------- |
207
- | `f.text()` | `varchar` / `text` | Short strings, titles, slugs |
208
- | `f.textarea()` | `text` | Long text, descriptions |
209
- | `f.richText()` | `text` (HTML) | Rich formatted content |
210
- | `f.email()` | `varchar` | Email addresses (validated) |
211
- | `f.url()` | `varchar` | URLs (validated) |
212
- | `f.number()` | `integer` / `numeric` | Counts, prices, quantities |
213
- | `f.boolean()` | `boolean` | Flags, toggles |
214
- | `f.date()` | `date` | Calendar dates |
215
- | `f.time()` | `time` | Time of day |
216
- | `f.datetime()` | `timestamp` | Date + time |
217
- | `f.select()` | `varchar` | Single choice from list |
218
- | `f.relation()` | FK column | Reference to another collection |
219
- | `f.upload()` | FK column | File upload linked to storage |
220
- | `f.object()` | `jsonb` | Nested structured data |
221
- | `.array()` | `jsonb` | Repeatable items |
222
- | `f.blocks()` | `jsonb` | Page builder content blocks |
223
- | `f.json()` | `jsonb` | Raw JSON |
224
-
225
- See `references/field-types.md` for complete config options per field type.
226
-
227
- ### Common Field Options
228
-
229
- Every field accepts:
230
-
231
- | Option | Type | Description |
232
- | ------------- | ---------------------------------- | ---------------------------------------- |
233
- | `required` | `boolean` | Field must have a value |
234
- | `default` | `T` | Default value |
235
- | `label` | `string \| Record<string, string>` | Display label (supports i18n) |
236
- | `description` | `string \| Record<string, string>` | Help text |
237
- | `localized` | `boolean` | Enable per-locale values |
238
- | `input` | `"optional"` | Optional in API input but required in DB |
239
- | `meta` | `object` | Admin UI rendering hints |
240
- | `virtual` | `SQL` | SQL expression for computed fields |
241
-
242
- ### Virtual (Computed) Fields
243
-
244
- ```ts
245
- import { sql } from "questpie";
246
-
247
- displayTitle: f.text().virtual(sql<string>`(
248
- SELECT COALESCE(name, 'Unknown') || ' - ' ||
249
- TO_CHAR("scheduledAt", 'YYYY-MM-DD HH24:MI')
250
- FROM appointments WHERE id = appointments.id
251
- )`),
252
- ```
253
-
254
- Virtual fields are read-only -- they appear in queries but cannot be written to.
255
-
256
- ## Relations
257
-
258
- All relations are defined via `f.relation()` inside `.fields()`.
259
-
260
- ### Belongs-To (Single)
261
-
262
- ```ts
263
- author: f.relation("users").required(),
264
- barber: f.relation("barbers").required().onDelete("cascade"),
265
- ```
266
-
267
- Creates a foreign key column pointing to the target collection's `id`.
268
-
269
- ### Many-to-Many (Through Junction)
270
-
271
- Requires a junction collection plus `through`, `sourceField`, and `targetField`:
272
-
273
- ```ts title="collections/barber-services.ts"
274
- import { collection } from "#questpie/factories";
275
-
276
- // Junction table
277
- export default collection("barberServices")
278
- .fields(({ f }) => ({
279
- barber: f.relation("barbers").required().onDelete("cascade"),
280
- service: f.relation("services").required().onDelete("cascade"),
281
- }))
282
- .admin(({ c }) => ({ hidden: true }));
283
- ```
284
-
285
- ```ts title="collections/barbers.ts (inside .fields())"
286
- services: f.relation("services").manyToMany({
287
- through: "barberServices",
288
- sourceField: "barber", // FK in junction pointing to THIS collection
289
- targetField: "service", // FK in junction pointing to TARGET collection
290
- }),
291
- ```
292
-
293
- ```ts title="collections/services.ts (inside .fields())"
294
- barbers: f.relation("barbers").manyToMany({
295
- through: "barberServices",
296
- sourceField: "service",
297
- targetField: "barber",
298
- }),
299
- ```
300
-
301
- ### Querying Relations
302
-
303
- ```ts
304
- // Include related data
305
- const barber = await collections.barbers.findOne({
306
- where: { id: "abc" },
307
- with: { services: true },
308
- });
309
- // barber.services: Service[]
310
-
311
- // Filter by relation
312
- const appointments = await collections.appointments.find({
313
- where: { barber: barberId, status: "pending" },
314
- });
315
- ```
316
-
317
- ## Localization
318
-
319
- ### Locale Configuration
320
-
321
- ```ts title="config/app.ts"
322
- import { appConfig } from "questpie";
323
-
324
- export default appConfig({
325
- locale: {
326
- locales: [
327
- { code: "en", label: "English", fallback: true, flagCountryCode: "us" },
328
- { code: "sk", label: "Slovencina" },
329
- { code: "de", label: "Deutsch" },
330
- ],
331
- defaultLocale: "en",
332
- },
333
- });
334
- ```
335
-
336
- ### Localizing Fields
337
-
338
- Chain `.localized()` on any field that needs per-locale content:
339
-
340
- ```ts
341
- name: f.text().required().localized(),
342
- description: f.textarea().localized(),
343
- price: f.number().required(), // NOT localized -- same in all locales
344
- ```
345
-
346
- Localizable types: `text`, `textarea`, `richText`, `select`, `array`, `blocks`.
347
- Typically NOT localized: `number`, `boolean`, `date`, `relation`.
348
-
349
- ### Localized Arrays
350
-
351
- Arrays can be localized as a whole -- each locale gets its own array:
352
-
353
- ```ts
354
- navigation: f.object({
355
- label: f.text().required(),
356
- href: f.text().required(),
357
- }).array().localized(),
358
- ```
359
-
360
- ### Querying Localized Content
361
-
362
- ```ts
363
- // Server-side -- locale comes from request context
364
- const services = await collections.services.find({ where: { isActive: true } });
365
-
366
- // Client-side -- set locale explicitly
367
- client.setLocale("sk");
368
- const services = await client.collections.services.find({
369
- where: { isActive: true },
370
- });
371
- ```
372
-
373
- ### Admin UI Locale (Separate)
374
-
375
- The admin panel has its own locale config for the interface language:
376
-
377
- ```ts title="config/admin.ts"
378
- import { adminConfig } from "#questpie/factories";
379
-
380
- export default adminConfig({
381
- locale: {
382
- locales: ["en", "sk"],
383
- defaultLocale: "en",
384
- },
385
- });
386
- ```
387
-
388
- This controls the admin interface language, NOT content locales.
389
-
390
- ## Nested Objects and Reusable Patterns
391
-
392
- Use helper functions to avoid repetition in object fields:
393
-
394
- ```ts
395
- .fields(({ f }) => {
396
- const daySchedule = () => ({
397
- isOpen: f.boolean().default(true),
398
- start: f.time(),
399
- end: f.time(),
400
- });
401
-
402
- return {
403
- workingHours: f.object({
404
- fields: () => ({
405
- monday: f.object({ fields: daySchedule }),
406
- tuesday: f.object({ fields: daySchedule }),
407
- wednesday: f.object({ fields: daySchedule }),
408
- }),
409
- }),
410
- };
411
- })
412
- ```
413
-
414
- ## Form Layout
415
-
416
- ```ts
417
- .form(({ v, f }) =>
418
- v.collectionForm({
419
- sidebar: {
420
- position: "right",
421
- fields: [f.isActive, f.avatar],
422
- },
423
- fields: [
424
- {
425
- type: "section",
426
- label: { en: "Contact Information" },
427
- layout: "grid",
428
- columns: 2,
429
- fields: [f.name, f.slug, f.email, f.phone],
430
- },
431
- {
432
- type: "section",
433
- label: { en: "Profile" },
434
- fields: [f.bio],
435
- },
436
- ],
437
- }),
438
- )
439
- ```
440
-
441
- ## Common Mistakes
442
-
443
- ### CRITICAL: Using `.relations()` method
444
-
445
- The `.relations()` builder method was removed. All relations are now defined via `f.relation()` inside `.fields()`.
446
-
447
- ```ts
448
- // WRONG -- .relations() does not exist
449
- collection("posts").relations({ author: belongsTo("users") });
450
-
451
- // CORRECT -- use f.relation() inside .fields()
452
- collection("posts").fields(({ f }) => ({
453
- author: f.relation("users"),
454
- }));
455
- ```
456
-
457
- ### HIGH: Forgetting `export default`
458
-
459
- Codegen discovers collections/globals by their default export. Without it, the file is silently ignored.
460
-
461
- ```ts
462
- // WRONG -- no default export, codegen won't find it
463
- export const posts = collection("posts").fields(/* ... */);
464
-
465
- // CORRECT
466
- export default collection("posts").fields(/* ... */);
467
- ```
468
-
469
- Note: named exports alongside default are fine (e.g., `export const posts = ...` followed by `export default posts`).
470
-
471
- ### HIGH: manyToMany without junction table config
472
-
473
- A many-to-many relation MUST specify `through`, `sourceField`, and `targetField`. Use `.hasMany({ foreignKey })` for a plain one-to-many reverse relation.
474
-
475
- ```ts
476
- // WRONG -- missing through/sourceField/targetField
477
- services: f.relation("services").manyToMany({});
478
-
479
- // CORRECT
480
- services: f.relation("services").manyToMany({
481
- through: "barberServices",
482
- sourceField: "barber",
483
- targetField: "service",
484
- });
485
- ```
486
-
487
- ### MEDIUM: Forgetting `.localized()`
488
-
489
- If content should vary by locale but the field does not chain `.localized()`, queries with different locales will return the same value.
490
-
491
- ### MEDIUM: Object fields -- function vs plain object
492
-
493
- Both `f.object({ fields: {...} })` and `f.object({ fields: () => ({...}) })` are valid. Use the function form when reusing helpers or referencing `f`.