create-questpie 2.0.1 → 2.0.2

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 (37) hide show
  1. package/README.md +10 -6
  2. package/dist/index.mjs +139 -24
  3. package/package.json +5 -3
  4. package/skills/questpie/AGENTS.md +2664 -0
  5. package/skills/questpie/SKILL.md +181 -0
  6. package/skills/questpie/references/auth.md +121 -0
  7. package/skills/questpie/references/business-logic.md +550 -0
  8. package/skills/questpie/references/codegen-plugin-api.md +382 -0
  9. package/skills/questpie/references/crud-api.md +378 -0
  10. package/skills/questpie/references/data-modeling.md +489 -0
  11. package/skills/questpie/references/extend.md +493 -0
  12. package/skills/questpie/references/field-types.md +386 -0
  13. package/skills/questpie/references/infrastructure-adapters.md +545 -0
  14. package/skills/questpie/references/multi-tenancy.md +364 -0
  15. package/skills/questpie/references/production.md +475 -0
  16. package/skills/questpie/references/query-operators.md +125 -0
  17. package/skills/questpie/references/quickstart.md +549 -0
  18. package/skills/questpie/references/rules.md +327 -0
  19. package/skills/questpie/references/tanstack-query.md +520 -0
  20. package/skills/questpie-admin/AGENTS.md +1442 -0
  21. package/skills/questpie-admin/SKILL.md +410 -0
  22. package/skills/questpie-admin/references/blocks.md +307 -0
  23. package/skills/questpie-admin/references/custom-ui.md +305 -0
  24. package/skills/questpie-admin/references/views.md +433 -0
  25. package/templates/tanstack-start/AGENTS.md +17 -13
  26. package/templates/tanstack-start/CLAUDE.md +15 -12
  27. package/templates/tanstack-start/README.md +19 -13
  28. package/templates/tanstack-start/env.example +1 -1
  29. package/templates/tanstack-start/package.json +20 -6
  30. package/templates/tanstack-start/src/lib/env.ts +1 -1
  31. package/templates/tanstack-start/src/questpie/server/config/admin.ts +27 -30
  32. package/templates/tanstack-start/src/routeTree.gen.ts +138 -0
  33. package/templates/tanstack-start/src/routes/__root.tsx +0 -2
  34. package/templates/tanstack-start/src/routes/admin.tsx +8 -1
  35. package/templates/tanstack-start/src/tanstack-start.d.ts +1 -0
  36. package/templates/tanstack-start/src/vite-env.d.ts +1 -0
  37. package/templates/tanstack-start/vite.config.ts +1 -3
@@ -0,0 +1,493 @@
1
+ ---
2
+ name: questpie-core/extend
3
+ description: QUESTPIE extensibility — codegen plugins CodegenPlugin CategoryDeclaration CallbackParamDefinition, building modules, custom field types field() factory toColumn toZodSchema getOperators getMetadata, custom adapters createFetchHandler Elysia Hono Next.js TanStack Start, type registries FieldTypeRegistry ComponentTypeRegistry ViewKindRegistry declare module augmentation, package distribution tsdown npm publishing changesets
4
+ - questpie-core
5
+ ---
6
+
7
+ ## Overview
8
+
9
+ This skill covers extending QUESTPIE: building codegen plugins, reusable modules, custom field types, framework adapters, type registries, and package distribution.
10
+
11
+ ## Building a Codegen Plugin
12
+
13
+ A plugin tells codegen what to discover and what types to generate. Plugins contribute to one or more codegen **targets** (e.g., `"server"`, `"admin-client"`).
14
+
15
+ ### Plugin Structure
16
+
17
+ ```ts
18
+ import type { CodegenPlugin } from "questpie";
19
+
20
+ export function myPlugin(): CodegenPlugin {
21
+ return {
22
+ name: "my-plugin",
23
+
24
+ targets: {
25
+ // Contribute to the server target
26
+ server: {
27
+ root: ".",
28
+ outputFile: "index.ts",
29
+
30
+ // Directory-pattern categories to discover
31
+ categories: {
32
+ widgets: {
33
+ dirs: ["widgets"],
34
+ prefix: "widget",
35
+ emit: "record",
36
+ registryKey: "widgets",
37
+ includeInAppState: true,
38
+ },
39
+ },
40
+
41
+ // Single-file / glob discover patterns
42
+ discover: {
43
+ widgetConfig: { pattern: "widget-config.ts", cardinality: "single" },
44
+ },
45
+
46
+ // Extension methods for collection()/global() factories
47
+ registries: {
48
+ collectionExtensions: {
49
+ widget: {
50
+ stateKey: "~widget",
51
+ configType: "WidgetConfig",
52
+ imports: [{ name: "WidgetConfig", from: "my-plugin-package" }],
53
+ },
54
+ },
55
+ singletonFactories: {
56
+ widgetConfig: {
57
+ configType: "WidgetConfig",
58
+ imports: [{ name: "WidgetConfig", from: "my-plugin-package" }],
59
+ },
60
+ },
61
+ },
62
+
63
+ // Callback param definitions for extension methods
64
+ callbackParams: {
65
+ w: {
66
+ factory: "createWidgetNameProxy",
67
+ from: "my-plugin-package",
68
+ },
69
+ },
70
+ },
71
+ },
72
+ };
73
+ }
74
+ ```
75
+
76
+ ### Register in Config
77
+
78
+ ```ts title="questpie.config.ts"
79
+ import { runtimeConfig } from "questpie";
80
+ import { myPlugin } from "my-plugin-package";
81
+
82
+ export default runtimeConfig({
83
+ plugins: [myPlugin()],
84
+ db: { url: process.env.DATABASE_URL! },
85
+ app: { url: process.env.APP_URL! },
86
+ });
87
+ ```
88
+
89
+ ### Plugin Lifecycle
90
+
91
+ 1. **Discovery** -- codegen scans for files matching category patterns and discover patterns
92
+ 2. **Import** -- files are imported and exports are read
93
+ 3. **Transform** -- `transform(ctx)` callbacks can modify the codegen context
94
+ 4. **Generation** -- types and runtime code are emitted
95
+ 5. **Validation** -- cross-target validators check projection consistency
96
+
97
+ ### Real-World Example: Admin Plugin
98
+
99
+ The admin module contributes a codegen plugin to both `"server"` and `"admin-client"` targets -- declaring categories (`blocks`, `views`, `components`, field types), discovering `config/admin.ts`, adding collection/global/field extensions, and defining callback context params such as `v`, `f`, `c`, and `a`.
100
+
101
+ ## Building a Module
102
+
103
+ A module is a reusable package that contributes entities to any QUESTPIE project.
104
+
105
+ ```ts
106
+ import { module, collection, job } from "questpie";
107
+ import { z } from "zod";
108
+
109
+ const notificationsCollection = collection("notifications")
110
+ .fields(({ f }) => ({
111
+ title: f.text().required(),
112
+ body: f.textarea(),
113
+ read: f.boolean().default(false),
114
+ userId: f.relation("user").required(),
115
+ }))
116
+ .admin(({ c }) => ({
117
+ label: { en: "Notifications" },
118
+ icon: c.icon("ph:bell"),
119
+ }));
120
+
121
+ const sendPushNotification = job({
122
+ name: "sendPushNotification",
123
+ schema: z.object({
124
+ userId: z.string(),
125
+ title: z.string(),
126
+ body: z.string(),
127
+ }),
128
+ handler: async ({ payload }) => {
129
+ // Send push notification logic
130
+ },
131
+ });
132
+
133
+ export const notificationsModule = module({
134
+ name: "notifications",
135
+ collections: { notifications: notificationsCollection },
136
+ jobs: { sendPushNotification },
137
+ sidebar: {
138
+ items: [
139
+ {
140
+ sectionId: "operations",
141
+ type: "collection",
142
+ collection: "notifications",
143
+ },
144
+ ],
145
+ },
146
+ messages: {
147
+ en: {
148
+ "notifications.title": "Notifications",
149
+ "notifications.markRead": "Mark as read",
150
+ },
151
+ },
152
+ });
153
+ ```
154
+
155
+ ### Module Options
156
+
157
+ | Property | Type | Description |
158
+ | ------------- | ------------- | ------------------------ |
159
+ | `name` | `string` | Module identifier |
160
+ | `modules` | `Module[]` | Module dependencies |
161
+ | `collections` | `Record` | Collection contributions |
162
+ | `globals` | `Record` | Global contributions |
163
+ | `jobs` | `Record` | Job contributions |
164
+ | `functions` | `Record` | Function contributions |
165
+ | `services` | `Record` | Service contributions |
166
+ | `routes` | `Record` | Route contributions |
167
+ | `fields` | `Record` | Custom field types |
168
+ | `sidebar` | `object` | Sidebar items |
169
+ | `dashboard` | `object` | Dashboard widgets |
170
+ | `migrations` | `Migration[]` | Database migrations |
171
+ | `seeds` | `Seed[]` | Seed data |
172
+ | `messages` | `Record` | i18n translations |
173
+
174
+ ### Using a Module
175
+
176
+ ```ts title="modules.ts"
177
+ import { adminModule } from "@questpie/admin/server";
178
+ import { notificationsModule } from "my-notifications-package";
179
+
180
+ export default [adminModule, notificationsModule] as const;
181
+ ```
182
+
183
+ ## Custom Field Types
184
+
185
+ Custom fields are registered through modules and become available on the `f` builder after codegen.
186
+
187
+ ### Registration
188
+
189
+ ```ts
190
+ const myModule = module({
191
+ name: "custom-fields",
192
+ fields: {
193
+ color: colorField,
194
+ currency: currencyField,
195
+ },
196
+ });
197
+ ```
198
+
199
+ Once registered and codegen runs:
200
+
201
+ ```ts
202
+ .fields(({ f }) => ({
203
+ brandColor: f.color().default("#000000"),
204
+ price: f.currency({ currency: "USD" }),
205
+ }))
206
+ ```
207
+
208
+ ### Field Definition
209
+
210
+ A custom field defines:
211
+
212
+ - **Storage type** -- how the value is stored in the database (via `toColumn`)
213
+ - **Validation** -- Zod schema for the value (via `toZodSchema`)
214
+ - **Operators** -- query operators (via `getOperators`)
215
+ - **Metadata** -- introspection metadata (via `getMetadata`)
216
+
217
+ The `Field` class is an immutable builder:
218
+
219
+ ```ts
220
+ import { Field } from "questpie";
221
+
222
+ // Each method returns a new Field with updated type state
223
+ f.text(255).required().label({ en: "Name" }).admin({ placeholder: "..." });
224
+ ```
225
+
226
+ ### Admin Renderer
227
+
228
+ Register a React component to render the field in the admin panel:
229
+
230
+ ```tsx
231
+ function ColorFieldRenderer({ value, onChange }) {
232
+ return (
233
+ <input
234
+ type="color"
235
+ value={value || "#000000"}
236
+ onChange={(e) => onChange(e.target.value)}
237
+ />
238
+ );
239
+ }
240
+ ```
241
+
242
+ Place it in `questpie/admin/fields/color.tsx` -- codegen discovers it automatically.
243
+
244
+ ## Custom Adapters
245
+
246
+ QUESTPIE ships with adapters for Hono, Elysia, and Next.js. For other frameworks, use `createFetchHandler` directly.
247
+
248
+ ### Generic Fetch Handler
249
+
250
+ ```ts
251
+ import { createFetchHandler } from "questpie";
252
+ import { app } from "#questpie";
253
+
254
+ const handler = createFetchHandler(app, { basePath: "/api" });
255
+ // Use with any framework supporting standard Request/Response
256
+ const response = await handler(request);
257
+ ```
258
+
259
+ ### Framework Adapters
260
+
261
+ **Elysia:**
262
+
263
+ ```ts
264
+ import { Elysia } from "elysia";
265
+ import { questpieElysia } from "@questpie/elysia/server";
266
+ import { app } from "#questpie";
267
+
268
+ const server = new Elysia()
269
+ .use(questpieElysia(app, { basePath: "/api" }))
270
+ .listen(3000);
271
+ ```
272
+
273
+ **Hono:**
274
+
275
+ ```ts
276
+ import { Hono } from "hono";
277
+ import { questpieHono } from "@questpie/hono/server";
278
+ import { app } from "#questpie";
279
+
280
+ const server = new Hono().route("/api", questpieHono(app));
281
+ export default server;
282
+ ```
283
+
284
+ **Next.js (App Router):**
285
+
286
+ ```ts title="app/api/[...slug]/route.ts"
287
+ import { questpieNextRouteHandlers } from "@questpie/next";
288
+ import { app } from "#questpie";
289
+
290
+ export const { GET, POST, PATCH, DELETE } = questpieNextRouteHandlers(app, {
291
+ basePath: "/api",
292
+ });
293
+ ```
294
+
295
+ **TanStack Start (no adapter needed):**
296
+
297
+ ```ts title="src/routes/api/$.ts"
298
+ import { createAPIFileRoute } from "@tanstack/react-start/api";
299
+ import { createFetchHandler } from "questpie";
300
+ import { app } from "#questpie";
301
+
302
+ const handler = createFetchHandler(app, { basePath: "/api" });
303
+ export const Route = createAPIFileRoute("/api/$")({
304
+ GET: ({ request }) => handler(request),
305
+ POST: ({ request }) => handler(request),
306
+ PATCH: ({ request }) => handler(request),
307
+ DELETE: ({ request }) => handler(request),
308
+ });
309
+ ```
310
+
311
+ ## Type Registries
312
+
313
+ Three augmentation interfaces allow plugins to extend discriminant types:
314
+
315
+ | Interface | Package | Purpose | Fallback |
316
+ | ----------------------- | ------------------------ | ------------------------------------------------ | ------------- |
317
+ | `FieldTypeRegistry` | `questpie` | Field type names (`"text"`, `"number"`, etc.) | `string` |
318
+ | `ComponentTypeRegistry` | `@questpie/admin/server` | Component type names (`"icon"`, `"badge"`, etc.) | `string` |
319
+ | `ViewKindRegistry` | `@questpie/admin/server` | View kind names (`"list"`, `"edit"`) | literal union |
320
+
321
+ ### How Registries Work
322
+
323
+ ```text
324
+ Server: f.text().required()
325
+ -> Generated: { type: "text", options: {...} }
326
+ -> Admin Client: fieldRegistry.get("text")
327
+ -> React: <TextFieldRenderer value={...} onChange={...} />
328
+ ```
329
+
330
+ ### Extending Registries
331
+
332
+ Place files in admin directory -- codegen discovers them automatically:
333
+
334
+ ```text
335
+ questpie/admin/
336
+ fields/
337
+ color.tsx # Custom color field renderer
338
+ currency.tsx # Custom currency field renderer
339
+ views/
340
+ kanban.tsx # Custom kanban list view
341
+ ```
342
+
343
+ ### Module Augmentation Pattern
344
+
345
+ Codegen generates `declare module` augmentations:
346
+
347
+ ```ts
348
+ declare global {
349
+ namespace Questpie {
350
+ interface FieldTypeRegistry {
351
+ color: {};
352
+ currency: {};
353
+ }
354
+ }
355
+ }
356
+ ```
357
+
358
+ The companion type alias uses the `[keyof Registry] extends [never] ? string : keyof Registry` pattern to fall back to `string` when the registry is empty.
359
+
360
+ ## Package Distribution
361
+
362
+ ### Package Structure
363
+
364
+ ```text
365
+ packages/my-package/
366
+ src/
367
+ exports/ # Public API entry points
368
+ index.ts # Main entry (.)
369
+ client.ts # Client entry (./client)
370
+ server.ts # Server entry (./server)
371
+ dist/ # Build output (gitignored)
372
+ tsdown.config.ts
373
+ package.json
374
+ ```
375
+
376
+ ### Dual Exports Strategy
377
+
378
+ ```json title="package.json"
379
+ {
380
+ "type": "module",
381
+ "exports": {
382
+ ".": {
383
+ "types": "./dist/index.d.mts",
384
+ "default": "./src/exports/index.ts"
385
+ }
386
+ },
387
+ "publishConfig": {
388
+ "exports": {
389
+ ".": {
390
+ "types": "./dist/index.d.mts",
391
+ "default": "./dist/index.mjs"
392
+ }
393
+ }
394
+ },
395
+ "files": ["dist"]
396
+ }
397
+ ```
398
+
399
+ During development: `exports.default` points to `.ts` source (no build step needed). When published: `publishConfig.exports` overrides with compiled `.mjs` + `.d.mts`.
400
+
401
+ ### Build with tsdown
402
+
403
+ ```ts title="tsdown.config.ts"
404
+ import { defineConfig } from "tsdown";
405
+
406
+ export default defineConfig({
407
+ entry: ["src/exports/*.ts"],
408
+ outDir: "dist",
409
+ format: ["esm"],
410
+ clean: true,
411
+ dts: { sourcemap: false },
412
+ unbundle: true,
413
+ });
414
+ ```
415
+
416
+ ### Publishing a Module
417
+
418
+ ```json title="package.json"
419
+ {
420
+ "name": "questpie-notifications",
421
+ "main": "dist/index.js",
422
+ "types": "dist/index.d.ts",
423
+ "peerDependencies": {
424
+ "questpie": "^2.0.0"
425
+ }
426
+ }
427
+ ```
428
+
429
+ ### Versioning with Changesets
430
+
431
+ Core packages use lock-step versioning. Run `bun changeset` to create, `bun run version` to apply, `bun run release` to publish.
432
+
433
+ ## Common Mistakes
434
+
435
+ ### HIGH: Adding `& Record<string, unknown>` to Registry augmentation
436
+
437
+ This erases named keys via index signature intersection. The resulting type becomes `string` instead of a union of literal keys.
438
+
439
+ ```ts
440
+ // WRONG -- erases literal types
441
+ declare global {
442
+ namespace Questpie {
443
+ interface FieldTypeRegistry extends Record<string, unknown> {
444
+ color: {};
445
+ }
446
+ }
447
+ }
448
+
449
+ // CORRECT -- only named keys
450
+ declare global {
451
+ namespace Questpie {
452
+ interface FieldTypeRegistry {
453
+ color: {};
454
+ }
455
+ }
456
+ }
457
+ ```
458
+
459
+ ### HIGH: Stale `.js`/`.d.ts` artifacts in src/
460
+
461
+ tsdown prefers `.js` over `.ts` when both exist. Delete stale artifacts before building:
462
+
463
+ ```bash
464
+ # Remove tsc incremental artifacts that may shadow .ts files
465
+ find src -name '*.d.ts' -o -name '*.js' | xargs rm -f
466
+ ```
467
+
468
+ ### MEDIUM: Not including `"skills"` in package.json files array
469
+
470
+ Skills must ship with the npm package. Add the directory to `files`:
471
+
472
+ ```json
473
+ {
474
+ "files": ["dist", "skills"]
475
+ }
476
+ ```
477
+
478
+ ### MEDIUM: Complex conditional mapped types collapsing to `{}` in tsdown `.d.ts` emit
479
+
480
+ Ensure source types have literal generic parameters. If a type like `FilterViewsByKind<TKind>` collapses to `{}` in declaration output, provide explicit `as ViewDefinition<"name", "kind", Config>` casts.
481
+
482
+ ### MEDIUM: Missing `extractFromModules` for new categories
483
+
484
+ If a plugin declares a new category but does not set `extractFromModules: true`, module-contributed entities of that category will not appear in the generated `App*` type.
485
+
486
+ ## References
487
+
488
+ | Need to... | Read |
489
+ | -------------------------------- | ---------------------------------- |
490
+ | See full CodegenPlugin API | `references/codegen-plugin-api.md` |
491
+ | Define collections and fields | `questpie-core/data-modeling` |
492
+ | Set up access, hooks, validation | `questpie-core/rules` |
493
+ | Add functions, jobs, routes | `questpie-core/business-logic` |