create-questpie 2.0.1 → 2.0.3

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 (40) 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 +2670 -0
  5. package/skills/questpie/SKILL.md +260 -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 +493 -0
  11. package/skills/questpie/references/extend.md +557 -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 +564 -0
  18. package/skills/questpie/references/rules.md +389 -0
  19. package/skills/questpie/references/tanstack-query.md +520 -0
  20. package/skills/questpie-admin/AGENTS.md +1508 -0
  21. package/skills/questpie-admin/SKILL.md +436 -0
  22. package/skills/questpie-admin/references/blocks.md +331 -0
  23. package/skills/questpie-admin/references/custom-ui.md +305 -0
  24. package/skills/questpie-admin/references/views.md +449 -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/lib/query-client.ts +10 -1
  32. package/templates/tanstack-start/src/questpie/server/config/admin.ts +27 -30
  33. package/templates/tanstack-start/src/routeTree.gen.ts +138 -0
  34. package/templates/tanstack-start/src/routes/__root.tsx +0 -2
  35. package/templates/tanstack-start/src/routes/admin/$.tsx +12 -1
  36. package/templates/tanstack-start/src/routes/admin/index.tsx +12 -5
  37. package/templates/tanstack-start/src/routes/admin.tsx +8 -1
  38. package/templates/tanstack-start/src/tanstack-start.d.ts +1 -0
  39. package/templates/tanstack-start/src/vite-env.d.ts +1 -0
  40. package/templates/tanstack-start/vite.config.ts +1 -3
@@ -0,0 +1,557 @@
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
+ Use direct `runtimeConfig({ plugins })` registration only for standalone codegen plugins or custom setups that do not ship a module. Reusable packages should usually attach the plugin to a static module and let codegen extract it from `modules.ts`.
90
+
91
+ ### Configurable Codegen-Aware Modules
92
+
93
+ When a package ships a module and a `CodegenPlugin`, keep module identity static and put runtime options in a plugin-discovered config file. Codegen imports `modules.ts` before runtime app creation, so it must be able to see the same module/plugin tree regardless of environment or runtime options.
94
+
95
+ #### DO THIS
96
+
97
+ ```ts title="modules.ts"
98
+ import { observabilityModule } from "@questpie/observability/server";
99
+
100
+ export default [observabilityModule] as const;
101
+ ```
102
+
103
+ ```ts title="config/observability.ts"
104
+ import { observabilityConfig } from "@questpie/observability/server";
105
+
106
+ export default observabilityConfig({
107
+ serviceName: "barbershop",
108
+ enabled: process.env.NODE_ENV === "production",
109
+ otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
110
+ });
111
+ ```
112
+
113
+ ```ts title="@questpie/observability/server.ts"
114
+ export const observabilityModule = module({
115
+ name: "questpie-observability",
116
+ plugin: observabilityPlugin(),
117
+ services: {
118
+ observability: service({
119
+ namespace: null,
120
+ lifecycle: "singleton",
121
+ create: ({ app, logger }) =>
122
+ createObservabilityService(app.state.config?.observability, logger),
123
+ }),
124
+ },
125
+ });
126
+ ```
127
+
128
+ The plugin contributes `config/observability.ts` as a discover pattern and a typed singleton factory such as `observabilityConfig()`. The service reads the resolved config at runtime from `app.state.config.observability`.
129
+
130
+ #### DON'T DO THIS
131
+
132
+ Do not make runtime options the main API for modules that contribute codegen plugins:
133
+
134
+ ```ts title="modules.ts"
135
+ export default [
136
+ observabilityModule({
137
+ serviceName: "barbershop",
138
+ enabled: process.env.NODE_ENV === "production",
139
+ }),
140
+ ] as const;
141
+ ```
142
+
143
+ Do not conditionally include codegen-aware modules or plugins:
144
+
145
+ ```ts title="modules.ts"
146
+ export default [
147
+ process.env.OTEL_ENABLED ? observabilityModule : undefined,
148
+ ].filter(Boolean);
149
+ ```
150
+
151
+ Factory modules are acceptable only for simple runtime-only modules whose plugin identity and generated contributions do not change. If the package contributes discover patterns, generated factories, module categories, views, components, fields, or collection/global extensions, use **static module + `config/*.ts` singleton factory**.
152
+
153
+ ### Plugin Lifecycle
154
+
155
+ 1. **Discovery** -- codegen scans for files matching category patterns and discover patterns
156
+ 2. **Import** -- files are imported and exports are read
157
+ 3. **Transform** -- `transform(ctx)` callbacks can modify the codegen context
158
+ 4. **Generation** -- types and runtime code are emitted
159
+ 5. **Validation** -- cross-target validators check projection consistency
160
+
161
+ ### Real-World Example: Admin Plugin
162
+
163
+ 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`.
164
+
165
+ ## Building a Module
166
+
167
+ A module is a reusable package that contributes entities to any QUESTPIE project.
168
+
169
+ ```ts
170
+ import { module, collection, job } from "questpie";
171
+ import { z } from "zod";
172
+
173
+ const notificationsCollection = collection("notifications")
174
+ .fields(({ f }) => ({
175
+ title: f.text().required(),
176
+ body: f.textarea(),
177
+ read: f.boolean().default(false),
178
+ userId: f.relation("user").required(),
179
+ }))
180
+ .admin(({ c }) => ({
181
+ label: { en: "Notifications" },
182
+ icon: c.icon("ph:bell"),
183
+ }));
184
+
185
+ const sendPushNotification = job({
186
+ name: "sendPushNotification",
187
+ schema: z.object({
188
+ userId: z.string(),
189
+ title: z.string(),
190
+ body: z.string(),
191
+ }),
192
+ handler: async ({ payload }) => {
193
+ // Send push notification logic
194
+ },
195
+ });
196
+
197
+ export const notificationsModule = module({
198
+ name: "notifications",
199
+ collections: { notifications: notificationsCollection },
200
+ jobs: { sendPushNotification },
201
+ sidebar: {
202
+ items: [
203
+ {
204
+ sectionId: "operations",
205
+ type: "collection",
206
+ collection: "notifications",
207
+ },
208
+ ],
209
+ },
210
+ messages: {
211
+ en: {
212
+ "notifications.title": "Notifications",
213
+ "notifications.markRead": "Mark as read",
214
+ },
215
+ },
216
+ });
217
+ ```
218
+
219
+ ### Module Options
220
+
221
+ | Property | Type | Description |
222
+ | ------------- | ------------- | ------------------------ |
223
+ | `name` | `string` | Module identifier |
224
+ | `modules` | `Module[]` | Module dependencies |
225
+ | `collections` | `Record` | Collection contributions |
226
+ | `globals` | `Record` | Global contributions |
227
+ | `jobs` | `Record` | Job contributions |
228
+ | `functions` | `Record` | Function contributions |
229
+ | `services` | `Record` | Service contributions |
230
+ | `routes` | `Record` | Route contributions |
231
+ | `fields` | `Record` | Custom field types |
232
+ | `sidebar` | `object` | Sidebar items |
233
+ | `dashboard` | `object` | Dashboard widgets |
234
+ | `migrations` | `Migration[]` | Database migrations |
235
+ | `seeds` | `Seed[]` | Seed data |
236
+ | `messages` | `Record` | i18n translations |
237
+
238
+ ### Using a Module
239
+
240
+ ```ts title="modules.ts"
241
+ import { adminModule } from "@questpie/admin/server";
242
+ import { notificationsModule } from "my-notifications-package";
243
+
244
+ export default [adminModule, notificationsModule] as const;
245
+ ```
246
+
247
+ ## Custom Field Types
248
+
249
+ Custom fields are registered through modules and become available on the `f` builder after codegen.
250
+
251
+ ### Registration
252
+
253
+ ```ts
254
+ const myModule = module({
255
+ name: "custom-fields",
256
+ fields: {
257
+ color: colorField,
258
+ currency: currencyField,
259
+ },
260
+ });
261
+ ```
262
+
263
+ Once registered and codegen runs:
264
+
265
+ ```ts
266
+ .fields(({ f }) => ({
267
+ brandColor: f.color().default("#000000"),
268
+ price: f.currency({ currency: "USD" }),
269
+ }))
270
+ ```
271
+
272
+ ### Field Definition
273
+
274
+ A custom field defines:
275
+
276
+ - **Storage type** -- how the value is stored in the database (via `toColumn`)
277
+ - **Validation** -- Zod schema for the value (via `toZodSchema`)
278
+ - **Operators** -- query operators (via `getOperators`)
279
+ - **Metadata** -- introspection metadata (via `getMetadata`)
280
+
281
+ The `Field` class is an immutable builder:
282
+
283
+ ```ts
284
+ import { Field } from "questpie";
285
+
286
+ // Each method returns a new Field with updated type state
287
+ f.text(255).required().label({ en: "Name" }).admin({ placeholder: "..." });
288
+ ```
289
+
290
+ ### Admin Renderer
291
+
292
+ Register a React component to render the field in the admin panel:
293
+
294
+ ```tsx
295
+ function ColorFieldRenderer({ value, onChange }) {
296
+ return (
297
+ <input
298
+ type="color"
299
+ value={value || "#000000"}
300
+ onChange={(e) => onChange(e.target.value)}
301
+ />
302
+ );
303
+ }
304
+ ```
305
+
306
+ Place it in `questpie/admin/fields/color.tsx` -- codegen discovers it automatically.
307
+
308
+ ## Custom Adapters
309
+
310
+ QUESTPIE ships with adapters for Hono, Elysia, and Next.js. For other frameworks, use `createFetchHandler` directly.
311
+
312
+ ### Generic Fetch Handler
313
+
314
+ ```ts
315
+ import { createFetchHandler } from "questpie";
316
+ import { app } from "#questpie";
317
+
318
+ const handler = createFetchHandler(app, { basePath: "/api" });
319
+ // Use with any framework supporting standard Request/Response
320
+ const response = await handler(request);
321
+ ```
322
+
323
+ ### Framework Adapters
324
+
325
+ **Elysia:**
326
+
327
+ ```ts
328
+ import { Elysia } from "elysia";
329
+ import { questpieElysia } from "@questpie/elysia/server";
330
+ import { app } from "#questpie";
331
+
332
+ const server = new Elysia()
333
+ .use(questpieElysia(app, { basePath: "/api" }))
334
+ .listen(3000);
335
+ ```
336
+
337
+ **Hono:**
338
+
339
+ ```ts
340
+ import { Hono } from "hono";
341
+ import { questpieHono } from "@questpie/hono/server";
342
+ import { app } from "#questpie";
343
+
344
+ const server = new Hono().route("/api", questpieHono(app));
345
+ export default server;
346
+ ```
347
+
348
+ **Next.js (App Router):**
349
+
350
+ ```ts title="app/api/[...slug]/route.ts"
351
+ import { questpieNextRouteHandlers } from "@questpie/next";
352
+ import { app } from "#questpie";
353
+
354
+ export const { GET, POST, PATCH, DELETE } = questpieNextRouteHandlers(app, {
355
+ basePath: "/api",
356
+ });
357
+ ```
358
+
359
+ **TanStack Start (no adapter needed):**
360
+
361
+ ```ts title="src/routes/api/$.ts"
362
+ import { createAPIFileRoute } from "@tanstack/react-start/api";
363
+ import { createFetchHandler } from "questpie";
364
+ import { app } from "#questpie";
365
+
366
+ const handler = createFetchHandler(app, { basePath: "/api" });
367
+ export const Route = createAPIFileRoute("/api/$")({
368
+ GET: ({ request }) => handler(request),
369
+ POST: ({ request }) => handler(request),
370
+ PATCH: ({ request }) => handler(request),
371
+ DELETE: ({ request }) => handler(request),
372
+ });
373
+ ```
374
+
375
+ ## Type Registries
376
+
377
+ Three augmentation interfaces allow plugins to extend discriminant types:
378
+
379
+ | Interface | Package | Purpose | Fallback |
380
+ | ----------------------- | ------------------------ | ------------------------------------------------ | ------------- |
381
+ | `FieldTypeRegistry` | `questpie` | Field type names (`"text"`, `"number"`, etc.) | `string` |
382
+ | `ComponentTypeRegistry` | `@questpie/admin/server` | Component type names (`"icon"`, `"badge"`, etc.) | `string` |
383
+ | `ViewKindRegistry` | `@questpie/admin/server` | View kind names (`"list"`, `"edit"`) | literal union |
384
+
385
+ ### How Registries Work
386
+
387
+ ```text
388
+ Server: f.text().required()
389
+ -> Generated: { type: "text", options: {...} }
390
+ -> Admin Client: fieldRegistry.get("text")
391
+ -> React: <TextFieldRenderer value={...} onChange={...} />
392
+ ```
393
+
394
+ ### Extending Registries
395
+
396
+ Place files in admin directory -- codegen discovers them automatically:
397
+
398
+ ```text
399
+ questpie/admin/
400
+ fields/
401
+ color.tsx # Custom color field renderer
402
+ currency.tsx # Custom currency field renderer
403
+ views/
404
+ kanban.tsx # Custom kanban list view
405
+ ```
406
+
407
+ ### Module Augmentation Pattern
408
+
409
+ Codegen generates `declare module` augmentations:
410
+
411
+ ```ts
412
+ declare global {
413
+ namespace Questpie {
414
+ interface FieldTypeRegistry {
415
+ color: {};
416
+ currency: {};
417
+ }
418
+ }
419
+ }
420
+ ```
421
+
422
+ The companion type alias uses the `[keyof Registry] extends [never] ? string : keyof Registry` pattern to fall back to `string` when the registry is empty.
423
+
424
+ ## Package Distribution
425
+
426
+ ### Package Structure
427
+
428
+ ```text
429
+ packages/my-package/
430
+ src/
431
+ exports/ # Public API entry points
432
+ index.ts # Main entry (.)
433
+ client.ts # Client entry (./client)
434
+ server.ts # Server entry (./server)
435
+ dist/ # Build output (gitignored)
436
+ tsdown.config.ts
437
+ package.json
438
+ ```
439
+
440
+ ### Dual Exports Strategy
441
+
442
+ ```json title="package.json"
443
+ {
444
+ "type": "module",
445
+ "exports": {
446
+ ".": {
447
+ "types": "./dist/index.d.mts",
448
+ "default": "./src/exports/index.ts"
449
+ }
450
+ },
451
+ "publishConfig": {
452
+ "exports": {
453
+ ".": {
454
+ "types": "./dist/index.d.mts",
455
+ "default": "./dist/index.mjs"
456
+ }
457
+ }
458
+ },
459
+ "files": ["dist"]
460
+ }
461
+ ```
462
+
463
+ During development: `exports.default` points to `.ts` source (no build step needed). When published: `publishConfig.exports` overrides with compiled `.mjs` + `.d.mts`.
464
+
465
+ ### Build with tsdown
466
+
467
+ ```ts title="tsdown.config.ts"
468
+ import { defineConfig } from "tsdown";
469
+
470
+ export default defineConfig({
471
+ entry: ["src/exports/*.ts"],
472
+ outDir: "dist",
473
+ format: ["esm"],
474
+ clean: true,
475
+ dts: { sourcemap: false },
476
+ unbundle: true,
477
+ });
478
+ ```
479
+
480
+ ### Publishing a Module
481
+
482
+ ```json title="package.json"
483
+ {
484
+ "name": "questpie-notifications",
485
+ "main": "dist/index.js",
486
+ "types": "dist/index.d.ts",
487
+ "peerDependencies": {
488
+ "questpie": "^2.0.0"
489
+ }
490
+ }
491
+ ```
492
+
493
+ ### Versioning with Changesets
494
+
495
+ Core packages use lock-step versioning. Run `bun changeset` to create, `bun run version` to apply, `bun run release` to publish.
496
+
497
+ ## Common Mistakes
498
+
499
+ ### HIGH: Adding `& Record<string, unknown>` to Registry augmentation
500
+
501
+ This erases named keys via index signature intersection. The resulting type becomes `string` instead of a union of literal keys.
502
+
503
+ ```ts
504
+ // WRONG -- erases literal types
505
+ declare global {
506
+ namespace Questpie {
507
+ interface FieldTypeRegistry extends Record<string, unknown> {
508
+ color: {};
509
+ }
510
+ }
511
+ }
512
+
513
+ // CORRECT -- only named keys
514
+ declare global {
515
+ namespace Questpie {
516
+ interface FieldTypeRegistry {
517
+ color: {};
518
+ }
519
+ }
520
+ }
521
+ ```
522
+
523
+ ### HIGH: Stale `.js`/`.d.ts` artifacts in src/
524
+
525
+ tsdown prefers `.js` over `.ts` when both exist. Delete stale artifacts before building:
526
+
527
+ ```bash
528
+ # Remove tsc incremental artifacts that may shadow .ts files
529
+ find src -name '*.d.ts' -o -name '*.js' | xargs rm -f
530
+ ```
531
+
532
+ ### MEDIUM: Not including `"skills"` in package.json files array
533
+
534
+ Skills must ship with the npm package. Add the directory to `files`:
535
+
536
+ ```json
537
+ {
538
+ "files": ["dist", "skills"]
539
+ }
540
+ ```
541
+
542
+ ### MEDIUM: Complex conditional mapped types collapsing to `{}` in tsdown `.d.ts` emit
543
+
544
+ 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.
545
+
546
+ ### MEDIUM: Missing `extractFromModules` for new categories
547
+
548
+ 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.
549
+
550
+ ## References
551
+
552
+ | Need to... | Read |
553
+ | -------------------------------- | ---------------------------------- |
554
+ | See full CodegenPlugin API | `references/codegen-plugin-api.md` |
555
+ | Define collections and fields | `questpie-core/data-modeling` |
556
+ | Set up access, hooks, validation | `questpie-core/rules` |
557
+ | Add functions, jobs, routes | `questpie-core/business-logic` |