create-questpie 2.0.3 → 2.0.4
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/dist/index.mjs +244 -30
- package/package.json +1 -1
- package/skills/questpie/AGENTS.md +299 -98
- package/skills/questpie/SKILL.md +50 -17
- package/skills/questpie/coverage.json +213 -0
- package/skills/questpie/references/auth.md +119 -4
- package/skills/questpie/references/business-logic.md +126 -56
- package/skills/questpie/references/crud-api.md +231 -29
- package/skills/questpie/references/data-modeling.md +22 -6
- package/skills/questpie/references/extend.md +34 -7
- package/skills/questpie/references/field-types.md +14 -2
- package/skills/questpie/references/infrastructure-adapters.md +207 -32
- package/skills/questpie/references/mcp.md +147 -0
- package/skills/questpie/references/multi-tenancy.md +1 -2
- package/skills/questpie/references/production.md +218 -53
- package/skills/questpie/references/quickstart.md +6 -8
- package/skills/questpie/references/rules.md +86 -21
- package/skills/questpie/references/sandbox.md +110 -0
- package/skills/questpie/references/tanstack-query.md +34 -11
- package/skills/questpie/references/type-inference.md +167 -0
- package/skills/questpie/references/workflows.md +155 -0
- package/skills/questpie-admin/AGENTS.md +47 -40
- package/skills/questpie-admin/SKILL.md +46 -39
- package/skills/questpie-admin/references/custom-ui.md +1 -1
- package/templates/tanstack-start/AGENTS.md +15 -8
- package/templates/tanstack-start/CLAUDE.md +12 -5
- package/templates/tanstack-start/README.md +7 -6
- package/templates/tanstack-start/package.json +1 -0
- package/templates/tanstack-start/src/questpie/admin/modules.ts +3 -1
- package/templates/tanstack-start/src/questpie/server/.generated/factories.ts +10 -9
- package/templates/tanstack-start/src/questpie/server/config/auth.ts +1 -1
- package/templates/tanstack-start/src/questpie/server/modules.ts +4 -5
- package/templates/tanstack-start/src/questpie/server/questpie.config.ts +2 -1
- package/templates/tanstack-start/src/routes/api/$.ts +1 -2
- package/templates/tanstack-start/vite.config.ts +2 -2
package/skills/questpie/SKILL.md
CHANGED
|
@@ -21,6 +21,7 @@ Reference these guidelines when:
|
|
|
21
21
|
- Setting up access control, hooks, or validation
|
|
22
22
|
- Building typed client SDK queries or TanStack Query integrations
|
|
23
23
|
- Writing modules or plugins for QUESTPIE
|
|
24
|
+
- Exposing QUESTPIE through MCP tools or resources
|
|
24
25
|
- Scaffolding a new project or onboarding
|
|
25
26
|
|
|
26
27
|
## Import Paths — Critical
|
|
@@ -40,6 +41,8 @@ Reference these guidelines when:
|
|
|
40
41
|
| `runtimeConfig({...})` | `"questpie"` | No |
|
|
41
42
|
| `appConfig({...})` | `"questpie"` | No |
|
|
42
43
|
| `authConfig({...})` | `"questpie"` | No |
|
|
44
|
+
| `env({...})` | `"questpie/env"` | No |
|
|
45
|
+
| `clientEnv({...})` | `"questpie/env"` | No |
|
|
43
46
|
| `createClient<AppConfig>()` | `"questpie/client"` | No |
|
|
44
47
|
| `createQuestpieQueryOptions()` | `"@questpie/tanstack-query"` | No |
|
|
45
48
|
|
|
@@ -105,27 +108,36 @@ export default [
|
|
|
105
108
|
|
|
106
109
|
Factory modules are acceptable only for simple runtime-only modules whose plugin identity and codegen contributions do not change. For reusable packages that ship a `CodegenPlugin`, prefer **static module + `config/*.ts` singleton factory**.
|
|
107
110
|
|
|
111
|
+
## Admin Auth Contract - Critical
|
|
112
|
+
|
|
113
|
+
`adminModule` includes the starter auth model and owns the canonical Better Auth `user` collection shape used by admin setup and login guards. That contract includes `user.role` with at least `admin` and `user` values. Do not replace `collection("user")` from scratch in apps that use `adminModule`; merge `starterModule.collections.user` and extend it when custom user fields or layout are needed.
|
|
114
|
+
|
|
108
115
|
## Reference Topics
|
|
109
116
|
|
|
110
117
|
### Core
|
|
111
118
|
|
|
112
|
-
| Topic
|
|
113
|
-
|
|
|
114
|
-
| Quickstart
|
|
115
|
-
| Data Modeling
|
|
116
|
-
| Field Types
|
|
117
|
-
|
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
|
|
|
119
|
+
| Topic | File | Covers |
|
|
120
|
+
| ----------------- | ------------------------------- | ------------------------------------------------------------------ |
|
|
121
|
+
| Quickstart | `references/quickstart.md` | Scaffold, configure, codegen, migrate, serve — zero to running app |
|
|
122
|
+
| Data Modeling | `references/data-modeling.md` | Collections, globals, fields, relations, options, localization |
|
|
123
|
+
| Field Types | `references/field-types.md` | All built-in field types with options and operators |
|
|
124
|
+
| Type Inference | `references/type-inference.md` | The infer-first map: `CollectionDoc`, `AccessContext` helpers, per-op rule typing, cycle rules |
|
|
125
|
+
| Rules | `references/rules.md` | Access control (row/field level), hooks lifecycle, validation, derived request context |
|
|
126
|
+
| Business Logic | `references/business-logic.md` | Routes, jobs, services, email templates, context injection |
|
|
127
|
+
| Durable Workflows | `references/workflows.md` | Long-running workflows, steps, events, cron, admin UI |
|
|
128
|
+
| Sandboxed Code | `references/sandbox.md` | `ctx.executor.run()`, isolation modes, capability model, Deno engine deployment |
|
|
129
|
+
| CRUD API | `references/crud-api.md` | `find`, `create`, `updateById`/`updateMany`, `deleteById`/`deleteMany`, atomic conditional updates, globals API |
|
|
130
|
+
| Query Operators | `references/query-operators.md` | `where` clause operators by field type |
|
|
121
131
|
|
|
122
132
|
### Infrastructure
|
|
123
133
|
|
|
124
134
|
| Topic | File | Covers |
|
|
125
135
|
| ---------- | --------------------------------------- | ------------------------------------------------------------ |
|
|
126
136
|
| Production | `references/production.md` | Queue, search, realtime, storage, email, KV adapter setup |
|
|
137
|
+
| Environment | `references/env.md` | `env.ts` + `env.client.ts`: boot-validated, typed env, generated client modules |
|
|
127
138
|
| Auth | `references/auth.md` | Better Auth integration, session, providers, access patterns |
|
|
128
139
|
| Adapters | `references/infrastructure-adapters.md` | All adapter configs: pg-boss, S3, SMTP, pgNotify, Redis |
|
|
140
|
+
| MCP | `references/mcp.md` | MCP setup, CRUD tools, route tools, custom tools, security |
|
|
129
141
|
|
|
130
142
|
### Extend
|
|
131
143
|
|
|
@@ -133,7 +145,7 @@ Factory modules are acceptable only for simple runtime-only modules whose plugin
|
|
|
133
145
|
| ------------------ | ---------------------------------- | ------------------------------------------------------------ |
|
|
134
146
|
| Extend | `references/extend.md` | Custom modules, fields, operators, adapters, codegen plugins |
|
|
135
147
|
| Codegen Plugin API | `references/codegen-plugin-api.md` | Plugin architecture, category declarations, templates |
|
|
136
|
-
| Multi-Tenancy | `references/multi-tenancy.md` |
|
|
148
|
+
| Multi-Tenancy | `references/multi-tenancy.md` | `appConfig({ context })` resolver, scope isolation, ScopeProvider |
|
|
137
149
|
|
|
138
150
|
### Client
|
|
139
151
|
|
|
@@ -152,12 +164,13 @@ export default collection("posts")
|
|
|
152
164
|
.fields(({ f }) => ({
|
|
153
165
|
title: f.text().required(),
|
|
154
166
|
status: f.select([{ value: "draft", label: "Draft" }]),
|
|
155
|
-
author: f.relation("
|
|
167
|
+
author: f.relation("user").required(),
|
|
156
168
|
}))
|
|
157
169
|
.access({
|
|
158
170
|
read: true,
|
|
159
171
|
create: ({ session }) => !!session,
|
|
160
|
-
update
|
|
172
|
+
// update rules get the existing row as `data` (typed, non-optional)
|
|
173
|
+
update: ({ session, data }) => data.author === session?.user?.id,
|
|
161
174
|
})
|
|
162
175
|
.hooks({
|
|
163
176
|
beforeChange: async ({ data, operation }) => {
|
|
@@ -168,10 +181,25 @@ export default collection("posts")
|
|
|
168
181
|
.options({ versioning: true, timestamps: true });
|
|
169
182
|
```
|
|
170
183
|
|
|
184
|
+
### Extend a Module Collection (don't redefine!)
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import { starterModule } from "questpie/app";
|
|
188
|
+
import { collection } from "#questpie/factories";
|
|
189
|
+
|
|
190
|
+
// Registering the same key from scratch REPLACES the module's collection
|
|
191
|
+
// (drops its fields/hooks/auth wiring). Merge instead — fully typed:
|
|
192
|
+
export default collection("user")
|
|
193
|
+
.merge(starterModule.collections.user)
|
|
194
|
+
.fields(({ f }) => ({
|
|
195
|
+
isAnonymous: f.boolean().default(false),
|
|
196
|
+
}));
|
|
197
|
+
```
|
|
198
|
+
|
|
171
199
|
### Route
|
|
172
200
|
|
|
173
201
|
```ts
|
|
174
|
-
import { route } from "questpie";
|
|
202
|
+
import { route } from "questpie/services";
|
|
175
203
|
import z from "zod";
|
|
176
204
|
|
|
177
205
|
export default route()
|
|
@@ -185,7 +213,7 @@ export default route()
|
|
|
185
213
|
### Job
|
|
186
214
|
|
|
187
215
|
```ts
|
|
188
|
-
import { job } from "questpie";
|
|
216
|
+
import { job } from "questpie/services";
|
|
189
217
|
import z from "zod";
|
|
190
218
|
|
|
191
219
|
export default job({
|
|
@@ -206,12 +234,11 @@ export default job({
|
|
|
206
234
|
```ts
|
|
207
235
|
import { createClient } from "questpie/client";
|
|
208
236
|
import type { AppConfig } from "#questpie";
|
|
237
|
+
import { env } from "#questpie/env.client.vite"; // generated from env.client.ts
|
|
209
238
|
|
|
210
239
|
const client = createClient<AppConfig>({
|
|
211
240
|
baseURL:
|
|
212
|
-
typeof window !== "undefined"
|
|
213
|
-
? window.location.origin
|
|
214
|
-
: process.env.APP_URL,
|
|
241
|
+
typeof window !== "undefined" ? window.location.origin : env.APP_URL,
|
|
215
242
|
basePath: "/api",
|
|
216
243
|
});
|
|
217
244
|
|
|
@@ -237,11 +264,17 @@ await queue.sendReminder.publish({ userId: "abc" });
|
|
|
237
264
|
| CRITICAL | Files in wrong directory | Collections in `collections/`, routes in `routes/`, etc. |
|
|
238
265
|
| CRITICAL | Missing `export default` on convention files | Codegen silently ignores files without default export |
|
|
239
266
|
| CRITICAL | Importing route/job/service from `#questpie/factories` | Use `"questpie"` — only collection/global/block/adminConfig use `#questpie/factories` |
|
|
267
|
+
| CRITICAL | Redefining a module collection (e.g. starter `user`) from scratch | `.merge(starterModule.collections.user)` then add fields — see Extend pattern |
|
|
240
268
|
| HIGH | Forgetting `questpie generate` after adding files | Re-run codegen on any file add/remove in convention dirs |
|
|
241
269
|
| HIGH | Job handler uses `input` instead of `payload` | Jobs destructure `{ payload }`, routes destructure `{ input }` |
|
|
242
270
|
| HIGH | `queue.send("name", data)` | Use `queue.jobName.publish(data)` |
|
|
271
|
+
| HIGH | Raw `process.env.X` / `process.env.X!` in app code | Declare in `env.ts` with `env()` — typed, boot-validated (see `references/env.md`) |
|
|
243
272
|
| HIGH | `beforeCreate` / `afterCreate` hook names | Use `beforeChange` / `afterChange` with `operation === "create"` guard |
|
|
273
|
+
| HIGH | Module-level app singleton for Better Auth callbacks | `getContext<App>()` works inside `onLinkAccount`/`databaseHooks`/plugin hooks — see `references/auth.md` |
|
|
274
|
+
| HIGH | Hand-writing a type the schema already knows | Use the inference one-liner (`CollectionDoc`, `AccessContext`, `ctx.data`, …) — see `references/type-inference.md` |
|
|
244
275
|
| HIGH | Runtime options in codegen-aware modules | Use static `module({...})` + plugin-discovered `config/*.ts` factory |
|
|
276
|
+
| HIGH | Exposing MCP HTTP as trusted system access | HTTP MCP is user mode only; use stdio only in trusted local/system contexts |
|
|
277
|
+
| HIGH | Bare `Date` as where-equality (`{ createdAt: someDate }`) | Use the explicit operator: `{ createdAt: { eq: someDate } }` — see `references/crud-api.md` keyset recipe |
|
|
245
278
|
| MEDIUM | Using npm/yarn instead of Bun | QUESTPIE requires Bun as package manager |
|
|
246
279
|
| MEDIUM | Editing `.generated/` files | Never edit — re-run `questpie generate` |
|
|
247
280
|
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_readme": "Allowlist + surface map for scripts/skill-coverage.ts. Reviewed together with the skill: every public VALUE export must be mentioned in skills/questpie/** or tagged internal here (with a reason). Type-only exports are reported but never gate. Run: bun run scripts/skill-coverage.ts (--strict to gate, --json/--all for detail).",
|
|
3
|
+
"skillRoot": "skills/questpie",
|
|
4
|
+
"surfaces": [
|
|
5
|
+
{ "package": "questpie", "entries": ["packages/questpie/src/exports"] },
|
|
6
|
+
{ "package": "@questpie/tanstack-query", "entries": ["packages/tanstack-query/src/index.ts"] },
|
|
7
|
+
{ "package": "@questpie/workflows", "entries": ["packages/workflows/src/exports"] },
|
|
8
|
+
{ "package": "@questpie/mcp", "entries": ["packages/mcp/src/exports"] },
|
|
9
|
+
{ "package": "@questpie/sandbox", "entries": ["packages/sandbox/src/exports"] },
|
|
10
|
+
{ "package": "@questpie/hono", "entries": ["packages/hono/src/server.ts", "packages/hono/src/client.ts"] },
|
|
11
|
+
{ "package": "@questpie/elysia", "entries": ["packages/elysia/src/server.ts", "packages/elysia/src/client.ts"] },
|
|
12
|
+
{ "package": "@questpie/next", "entries": ["packages/next/src/server.ts"] },
|
|
13
|
+
{
|
|
14
|
+
"package": "@questpie/openapi",
|
|
15
|
+
"entries": ["packages/openapi/src/index.ts", "packages/openapi/src/server.ts", "packages/openapi/src/plugin.ts"]
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"skippedPackages": {
|
|
19
|
+
"@questpie/admin": "covered by the separate questpie-admin skill (audit hook: map it there)",
|
|
20
|
+
"@questpie/executor-engine (packages/executor)": "engine side of the sandbox primitive; apps target ctx.sandbox",
|
|
21
|
+
"@questpie/ai": "product layer, not framework surface",
|
|
22
|
+
"create-questpie": "scaffolder CLI, not a library surface",
|
|
23
|
+
"@questpie/vite-plugin-iconify": "admin build tooling; covered by the questpie-admin skill"
|
|
24
|
+
},
|
|
25
|
+
"internal": {
|
|
26
|
+
"createContextFactory": "only consumed by generated code (.generated/index.ts)",
|
|
27
|
+
"extractAppServices": "context plumbing; apps receive services on ctx",
|
|
28
|
+
"createWorkflowClient": "service factory internal; ctx.workflows.trigger is the taught surface",
|
|
29
|
+
"introspectRoutes": "powers openapi/mcp generators",
|
|
30
|
+
"handleError": "HTTP adapter internal",
|
|
31
|
+
"RouteBuilder": "class behind route(); apps use the factory",
|
|
32
|
+
"SandboxBroker": "engine side of sandbox bindings",
|
|
33
|
+
"hostFetch": "sandbox host RPC internal",
|
|
34
|
+
"coreBackendMessages": "override path covered by generic messages merge",
|
|
35
|
+
"ExecutorService": "service class behind ctx.executor.run (the taught surface); constructed by the core service factory",
|
|
36
|
+
"InProcessExecutorAdapter": "default trusted adapter, applied automatically; override via executor.trusted",
|
|
37
|
+
"HTTP_FETCH_BODY_CAP_BYTES": "executor wire constant",
|
|
38
|
+
"parseBindingMethod": "executor broker internal",
|
|
39
|
+
"checkBindingCapability": "sandbox broker internal",
|
|
40
|
+
"knowledgePathMatches": "sandbox broker internal",
|
|
41
|
+
"normalizeKnowledgePath": "sandbox broker internal",
|
|
42
|
+
"DOCUMENT_STORE_COLLECTION": "sandbox broker internal constant",
|
|
43
|
+
"buildGuestBindings": "sandbox guest-side internal",
|
|
44
|
+
"classifyIpLiteral": "sandbox egress validation internal",
|
|
45
|
+
"parseHostEntry": "sandbox egress validation internal",
|
|
46
|
+
"validateEgressHosts": "sandbox egress validation internal",
|
|
47
|
+
"validateHostEgress": "sandbox egress validation internal",
|
|
48
|
+
"BINDINGS_TOKEN_HEADER": "sandbox wire-protocol constant",
|
|
49
|
+
"FRAME_MARKER": "sandbox wire-protocol constant",
|
|
50
|
+
"toVectorLiteral": "pgvector adapter internal util",
|
|
51
|
+
"createSearchService": "service wiring internal; ctx.search is the taught surface",
|
|
52
|
+
"SearchServiceWrapper": "see createSearchService",
|
|
53
|
+
"FilesError": "files-sdk pass-through; taught via Files SDK docs",
|
|
54
|
+
"transfer": "files-sdk pass-through; taught via Files SDK docs",
|
|
55
|
+
"RESEND_API_BASE_URL": "adapter default constant",
|
|
56
|
+
"PLUNK_API_BASE_URL": "adapter default constant",
|
|
57
|
+
"BullMQAdapter": "class form; bullMQAdapter() factory is the taught surface",
|
|
58
|
+
"CloudflareKVAdapter": "class form; cloudflareKVAdapter() factory is the taught surface",
|
|
59
|
+
"CloudflareQueuesAdapter": "class form; cloudflareQueuesAdapter() factory is the taught surface",
|
|
60
|
+
"CloudflareRealtimeAdapter": "class form; cloudflareRealtimeAdapter() factory is the taught surface",
|
|
61
|
+
"PgBossAdapter": "class form; pgBossAdapter() factory is the taught surface",
|
|
62
|
+
"PgNotifyAdapter": "class form; pgNotifyAdapter() factory is the taught surface",
|
|
63
|
+
"RedisStreamsAdapter": "class form; redisStreamsAdapter() factory is the taught surface",
|
|
64
|
+
"PlunkAdapter": "class form; plunkAdapter() factory is the taught surface",
|
|
65
|
+
"ResendAdapter": "class form; resendAdapter() factory is the taught surface",
|
|
66
|
+
"HttpSandboxAdapter": "class form; httpSandboxAdapter() factory is the taught surface",
|
|
67
|
+
"OpenAIEmbeddingProvider": "class form; createOpenAIEmbeddingProvider() factory is the taught surface",
|
|
68
|
+
"CustomEmbeddingProvider": "class form; createCustomEmbeddingProvider() factory is the taught surface",
|
|
69
|
+
"createCloudflareFetchHandler": "granular handler; createCloudflareWorkerHandlers umbrella is taught",
|
|
70
|
+
"createCloudflareQueueHandler": "granular handler; createCloudflareWorkerHandlers umbrella is taught",
|
|
71
|
+
"createCloudflareRealtimeDurableObjectHandler": "granular handler; createCloudflareWorkerHandlers umbrella is taught",
|
|
72
|
+
"createCloudflareScheduledHandler": "granular handler; createCloudflareWorkerHandlers umbrella is taught",
|
|
73
|
+
"publishScheduledJobs": "Cloudflare scheduled-handler internal",
|
|
74
|
+
"toCloudflareQueuePushBatch": "Cloudflare queue-handler internal",
|
|
75
|
+
"assertCloudflareCompatible": "boot-time compatibility check, runs automatically",
|
|
76
|
+
"getCloudflareCompatibilityIssues": "see assertCloudflareCompatible",
|
|
77
|
+
"CloudflareCompatibilityError": "see assertCloudflareCompatible",
|
|
78
|
+
"CLOUD_ENV": "QUESTPIE Cloud env detection constant",
|
|
79
|
+
"isQuestpieCloud": "QUESTPIE Cloud env detection helper",
|
|
80
|
+
"createAdapterContext": "HTTP adapter wiring internal",
|
|
81
|
+
"createAdapterRoutes": "HTTP adapter wiring internal",
|
|
82
|
+
"evaluateRouteAccess": "route dispatcher internal",
|
|
83
|
+
"executeJsonRoute": "route dispatcher internal",
|
|
84
|
+
"executeRawRoute": "route dispatcher internal",
|
|
85
|
+
"isJsonRoute": "route dispatcher internal",
|
|
86
|
+
"isRawRoute": "route dispatcher internal",
|
|
87
|
+
"executeAccessRule": "CRUD access-control engine internal",
|
|
88
|
+
"mergeAuthOptions": "auth config merge internal (createApp applies it)",
|
|
89
|
+
"guardHookRecursion": "hook engine internal",
|
|
90
|
+
"GlobalCRUDGenerator": "CRUD engine internal; ctx.globals is the taught surface",
|
|
91
|
+
"createQueueClient": "service wiring internal; ctx.queue is the taught surface",
|
|
92
|
+
"startJobWorker": "worker wiring internal; app.queue.listen() is the taught surface",
|
|
93
|
+
"runJobWorkerOnce": "worker wiring internal; app.queue.runOnce() is the taught surface",
|
|
94
|
+
"createComponentCallbackProxy": "server-driven admin UI internal",
|
|
95
|
+
"program": "CLI entry object; the questpie CLI commands are the taught surface",
|
|
96
|
+
"builtinFields": "field registry internal; f.* factories are the taught surface",
|
|
97
|
+
"createFieldBuilder": "builder engine internal",
|
|
98
|
+
"createFieldsCallbackContext": "builder engine internal",
|
|
99
|
+
"extractFieldDefinitions": "builder engine internal",
|
|
100
|
+
"wrapBuilderWithExtensions": "builder engine internal",
|
|
101
|
+
"wrapFieldComplete": "builder engine internal",
|
|
102
|
+
"createCollectionValidationSchemas": "validation engine internal",
|
|
103
|
+
"mergeFieldsForValidation": "validation engine internal",
|
|
104
|
+
"softDeleteUniqueIndex": "schema generation internal",
|
|
105
|
+
"introspectCollection": "introspection engine internal (powers /schema routes)",
|
|
106
|
+
"introspectCollections": "introspection engine internal",
|
|
107
|
+
"introspectGlobal": "introspection engine internal",
|
|
108
|
+
"introspectGlobals": "introspection engine internal",
|
|
109
|
+
"getCurrentTransaction": "transaction plumbing; withTransaction/onAfterCommit are the taught surface",
|
|
110
|
+
"getTransactionContext": "transaction plumbing",
|
|
111
|
+
"isInTransaction": "transaction plumbing",
|
|
112
|
+
"isNotNull": "internal type guard util",
|
|
113
|
+
"DrizzleMigrationGenerator": "CLI migration machinery",
|
|
114
|
+
"MigrationRunner": "CLI migration machinery",
|
|
115
|
+
"OperationSnapshotManager": "CLI migration machinery",
|
|
116
|
+
"SeedRunner": "CLI seed machinery",
|
|
117
|
+
"resolveAutoSeedCategories": "CLI seed machinery",
|
|
118
|
+
"createInsertSchema": "drizzle-to-zod internal",
|
|
119
|
+
"createUpdateSchema": "drizzle-to-zod internal",
|
|
120
|
+
"drizzleColumnsToZod": "drizzle-to-zod internal",
|
|
121
|
+
"drizzleColumnToZod": "drizzle-to-zod internal",
|
|
122
|
+
"parseDatabaseError": "error mapping internal; ApiError is the taught surface",
|
|
123
|
+
"CMS_ERROR_CODES": "raw code map; ApiError + docs reference/error-codes are the taught surface",
|
|
124
|
+
"getHTTPStatusFromCode": "error mapping internal",
|
|
125
|
+
"mergeAdminConfig": "admin config merge internal (createApp applies it)",
|
|
126
|
+
"mergeAdminSidebar": "admin config merge internal",
|
|
127
|
+
"isAuthoritativeSidebar": "admin sidebar internal",
|
|
128
|
+
"shouldAutoAppendUnlistedSidebar": "admin sidebar internal",
|
|
129
|
+
"serializeFormLayoutProps": "server-driven admin UI internal",
|
|
130
|
+
"serializeReactivePropValue": "server-driven admin UI internal",
|
|
131
|
+
"serializeReactivePropsRecord": "server-driven admin UI internal",
|
|
132
|
+
"isReactiveConfig": "server-driven admin UI internal",
|
|
133
|
+
"isReactivePropPlaceholder": "server-driven admin UI internal",
|
|
134
|
+
"trackDependencies": "reactive admin fields internal",
|
|
135
|
+
"trackDepsFunction": "reactive admin fields internal",
|
|
136
|
+
"getDebounce": "reactive admin fields internal",
|
|
137
|
+
"extractDependencies": "reactive admin fields internal",
|
|
138
|
+
"createValidationTranslator": "i18n plumbing; messages merge + ctx.t are the taught surface",
|
|
139
|
+
"createZodErrorMap": "i18n plumbing",
|
|
140
|
+
"i18nParams": "i18n plumbing",
|
|
141
|
+
"isI18nLocaleMap": "i18n plumbing",
|
|
142
|
+
"isI18nTranslationKey": "i18n plumbing",
|
|
143
|
+
"mergeValidationMessages": "i18n plumbing",
|
|
144
|
+
"resolveI18nText": "i18n plumbing",
|
|
145
|
+
"validationMessagesCS": "built-in locale pack, loaded automatically",
|
|
146
|
+
"validationMessagesDE": "built-in locale pack",
|
|
147
|
+
"validationMessagesEN": "built-in locale pack",
|
|
148
|
+
"validationMessagesES": "built-in locale pack",
|
|
149
|
+
"validationMessagesFR": "built-in locale pack",
|
|
150
|
+
"validationMessagesPL": "built-in locale pack",
|
|
151
|
+
"validationMessagesPT": "built-in locale pack",
|
|
152
|
+
"validationMessagesSK": "built-in locale pack",
|
|
153
|
+
"dedupeBy": "shared util pass-through",
|
|
154
|
+
"deepMerge": "shared util pass-through; mergeRecord & friends are the taught merge surface",
|
|
155
|
+
"isNullish": "shared util pass-through",
|
|
156
|
+
"isPlainObject": "shared util pass-through",
|
|
157
|
+
"toCamelCase": "shared util pass-through",
|
|
158
|
+
"toKebabCase": "shared util pass-through",
|
|
159
|
+
"toPascalCase": "shared util pass-through",
|
|
160
|
+
"toSnakeCase": "shared util pass-through",
|
|
161
|
+
"DEFAULT_LOCALE": "framework default constant",
|
|
162
|
+
"DEFAULT_LOCALE_CONFIG": "framework default constant",
|
|
163
|
+
"questpieSearchTable": "search infrastructure table (managed by adapter migrations)",
|
|
164
|
+
"questpieSearchFacetsTable": "search infrastructure table",
|
|
165
|
+
"questpieRealtimeLogTable": "realtime outbox table (managed by adapter migrations)",
|
|
166
|
+
"extractFacetValues": "search indexing internal",
|
|
167
|
+
"indexRecordsJob": "search reindex job, registered automatically",
|
|
168
|
+
"indexRecordsSchema": "see indexRecordsJob",
|
|
169
|
+
"categoryRecordEntry": "codegen template emission util",
|
|
170
|
+
"categoryTypeEntry": "codegen template emission util",
|
|
171
|
+
"importStatement": "codegen template emission util",
|
|
172
|
+
"safeKey": "codegen template emission util",
|
|
173
|
+
"sortedValues": "codegen template emission util",
|
|
174
|
+
"sourceBasename": "codegen template emission util",
|
|
175
|
+
"checkRetroactiveMatch": "workflow engine internal",
|
|
176
|
+
"computeMatchHash": "workflow engine internal",
|
|
177
|
+
"createCompletedStepsMap": "workflow engine internal",
|
|
178
|
+
"cronFiredInWindow": "workflow engine internal",
|
|
179
|
+
"cronMatches": "workflow engine internal",
|
|
180
|
+
"dispatchEvent": "workflow engine internal; ctx.workflows.dispatchEvent is the taught surface",
|
|
181
|
+
"defaultWorkflowAccess": "default applied automatically; override via workflowsConfig({ access })",
|
|
182
|
+
"executeWorkflowHandler": "workflow engine internal",
|
|
183
|
+
"getMatchHashesForDispatch": "workflow engine internal",
|
|
184
|
+
"matchesCriteria": "workflow engine internal",
|
|
185
|
+
"parseCron": "workflow engine internal",
|
|
186
|
+
"parseDuration": "workflow engine internal",
|
|
187
|
+
"resolveDate": "workflow engine internal",
|
|
188
|
+
"runCompensations": "workflow engine internal",
|
|
189
|
+
"WILDCARD_HASH": "workflow engine internal constant",
|
|
190
|
+
"WorkflowLoggerImpl": "workflow engine internal",
|
|
191
|
+
"StepExecutionContext": "workflow engine internal",
|
|
192
|
+
"NonDeterministicError": "engine-raised error, surfaced in run records",
|
|
193
|
+
"StepFailedError": "engine-raised error, surfaced in run records",
|
|
194
|
+
"StepRetryError": "engine-raised error, surfaced in run records",
|
|
195
|
+
"StepSuspendError": "engine-raised control-flow error",
|
|
196
|
+
"WorkflowError": "engine-raised error, surfaced in run records",
|
|
197
|
+
"WorkflowTimeoutError": "engine-raised error, surfaced in run records",
|
|
198
|
+
"WorkflowsPage": "registered via workflowsClientModule (taught)",
|
|
199
|
+
"WorkflowListPage": "registered via workflowsClientModule",
|
|
200
|
+
"WorkflowDetailPage": "registered via workflowsClientModule"
|
|
201
|
+
},
|
|
202
|
+
"concepts": [
|
|
203
|
+
{ "name": "auth-callback context (getContext in Better Auth callbacks)", "patterns": ["onLinkAccount"] },
|
|
204
|
+
{ "name": "partial context overrides ({ accessMode } inherits session/db/locale)", "patterns": ["partial context override"] },
|
|
205
|
+
{ "name": "job-level cron (options.cron)", "patterns": ["options:\\s*\\{\\s*cron"] },
|
|
206
|
+
{ "name": "raw routes (.raw())", "patterns": ["\\.raw\\(\\)"] },
|
|
207
|
+
{ "name": "signed URLs for private files", "patterns": ["generateSignedUrlToken"] },
|
|
208
|
+
{ "name": "sandboxed code execution (ctx.executor)", "patterns": ["ctx\\.executor\\.run"] },
|
|
209
|
+
{ "name": "client-side auth (authClient)", "patterns": ["createAdminAuthClient", "authClient"] },
|
|
210
|
+
{ "name": "infer-first map (type-inference reference)", "patterns": ["CollectionDoc<"] },
|
|
211
|
+
{ "name": "realtime invalidation topics", "patterns": ["buildCollectionTopic"] }
|
|
212
|
+
]
|
|
213
|
+
}
|
|
@@ -8,8 +8,7 @@ Auth is configured via `config/auth.ts` using the `authConfig()` factory:
|
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
// src/questpie/server/config/auth.ts
|
|
11
|
-
import { authConfig } from "questpie";
|
|
12
|
-
|
|
11
|
+
import { authConfig } from "questpie/app";
|
|
13
12
|
export default authConfig({
|
|
14
13
|
emailAndPassword: {
|
|
15
14
|
enabled: true,
|
|
@@ -38,7 +37,7 @@ Codegen discovers this file automatically. No manual registration needed.
|
|
|
38
37
|
### In Routes
|
|
39
38
|
|
|
40
39
|
```ts
|
|
41
|
-
import { route } from "questpie";
|
|
40
|
+
import { route } from "questpie/services";
|
|
42
41
|
import z from "zod";
|
|
43
42
|
|
|
44
43
|
export default route()
|
|
@@ -95,16 +94,132 @@ export default route()
|
|
|
95
94
|
|
|
96
95
|
## User Collection
|
|
97
96
|
|
|
98
|
-
The `adminModule` provides
|
|
97
|
+
The `adminModule` includes the starter auth model and provides the canonical Better Auth `user` collection. It stores:
|
|
99
98
|
|
|
100
99
|
- `id` -- unique identifier
|
|
101
100
|
- `email` -- email address
|
|
102
101
|
- `name` -- display name
|
|
103
102
|
- `image` -- avatar URL
|
|
104
103
|
- `emailVerified` -- verification status
|
|
104
|
+
- `role` -- admin access role (`admin` or `user`)
|
|
105
|
+
- `avatar`, `banned`, `banReason`, `banExpires` -- admin-managed profile and access fields
|
|
105
106
|
|
|
106
107
|
This collection is automatically created when you add the admin module to your config.
|
|
107
108
|
|
|
109
|
+
Critical: the built-in admin setup route and admin `AuthGuard` depend on `user.role`. Setup checks whether any user has `role = "admin"`, and the admin UI expects `session.user.role === "admin"`. Do not replace `collection("user")` from scratch in an app that uses `adminModule`; merge `starterModule.collections.user` and extend it if custom user fields or admin layout are needed.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { starterModule } from "questpie/app";
|
|
113
|
+
import { collection } from "#questpie/factories";
|
|
114
|
+
|
|
115
|
+
export default collection("user")
|
|
116
|
+
.merge(starterModule.collections.user)
|
|
117
|
+
.fields(({ f }) => ({
|
|
118
|
+
internalNotes: f.textarea(),
|
|
119
|
+
}));
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`.fields()` is cumulative -- it adds to the merged starter fields and overrides them by key, never wipes them, so this recipe keeps the full starter user model.
|
|
123
|
+
|
|
124
|
+
### Anonymous Users (Better Auth plugin)
|
|
125
|
+
|
|
126
|
+
Better Auth plugins that extend the user model follow the same recipe. For the anonymous plugin, register it in `auth.ts` (merged after the built-in plugins) and extend the starter user with the `isAnonymous` field the plugin expects:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
// auth.ts
|
|
130
|
+
import { anonymous } from "better-auth/plugins";
|
|
131
|
+
import type { AuthConfig } from "questpie/app";
|
|
132
|
+
|
|
133
|
+
export default {
|
|
134
|
+
plugins: [anonymous()],
|
|
135
|
+
} satisfies AuthConfig;
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
// collections/user.ts
|
|
140
|
+
import { starterModule } from "questpie/app";
|
|
141
|
+
import { collection } from "#questpie/factories";
|
|
142
|
+
|
|
143
|
+
export default collection("user")
|
|
144
|
+
.merge(starterModule.collections.user)
|
|
145
|
+
.fields(({ f }) => ({
|
|
146
|
+
isAnonymous: f.boolean().default(false),
|
|
147
|
+
}));
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Run `questpie generate` and apply migrations to add the column. Anonymous sign-in (`authClient.signIn.anonymous()` on the client) creates throwaway users that Better Auth can later link to real accounts.
|
|
151
|
+
|
|
152
|
+
## Reaching the App from Better Auth Callbacks
|
|
153
|
+
|
|
154
|
+
The `/auth/*` catch-all is a plain **raw route**, and raw routes execute their handler inside `runWithContext()` (the request's AsyncLocalStorage scope). That means every Better Auth callback — `onLinkAccount`, `databaseHooks`, `sendMagicLink`, plugin hooks — already runs inside the request scope, and `getContext<App>()` returns the live app, session, db, and locale.
|
|
155
|
+
|
|
156
|
+
**Never build a module-level app singleton or a hand-rolled context bridge for auth callbacks.** The `App` import stays type-only, so there is no circular import:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
// config/auth.ts
|
|
160
|
+
import { anonymous } from "better-auth/plugins";
|
|
161
|
+
import { getContext } from "questpie";
|
|
162
|
+
import { authConfig } from "questpie/app";
|
|
163
|
+
import type { App } from "#questpie"; // type-only — no runtime cycle
|
|
164
|
+
|
|
165
|
+
export default authConfig({
|
|
166
|
+
plugins: [
|
|
167
|
+
anonymous({
|
|
168
|
+
// Fires when an anonymous user signs in with a real account —
|
|
169
|
+
// re-point the guest's rows onto the new user before the plugin
|
|
170
|
+
// deletes the anonymous user.
|
|
171
|
+
onLinkAccount: async ({ anonymousUser, newUser }) => {
|
|
172
|
+
const { app } = getContext<App>();
|
|
173
|
+
// Bare { accessMode: "system" } elevates ONLY the mode —
|
|
174
|
+
// session, db, and locale inherit from the request scope (ALS).
|
|
175
|
+
await app.collections.memberships.updateMany(
|
|
176
|
+
{
|
|
177
|
+
where: { user: anonymousUser.user.id },
|
|
178
|
+
data: { user: newUser.user.id },
|
|
179
|
+
},
|
|
180
|
+
{ accessMode: "system" },
|
|
181
|
+
);
|
|
182
|
+
},
|
|
183
|
+
}),
|
|
184
|
+
],
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Partial Context Overrides
|
|
189
|
+
|
|
190
|
+
CRUD context normalization merges what you pass with the ambient request scope — priority: explicit param → ALS scope → defaults (`accessMode: "system"`, `locale: "en"`). Passing only `{ accessMode: "system" }` elevates the mode while the request's session/db/locale ride along. The inverse also holds: `{ accessMode: "user" }` inside system-scoped code re-enables access rules against the inherited session without re-threading it:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
// Inside any handler — session comes from the request ALS scope
|
|
194
|
+
await app.collections.posts.find({}, { accessMode: "user" }); // rules enforced for the current user
|
|
195
|
+
await app.collections.posts.find({}, { accessMode: "system" }); // rules bypassed, same session/locale
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Client-Side Auth (authClient)
|
|
199
|
+
|
|
200
|
+
For session state and sign-in/out on the frontend, create a typed Better Auth client. In admin-equipped apps use the typed wrapper (session includes your merged user fields):
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
// src/lib/auth-client.ts
|
|
204
|
+
import { createAdminAuthClient } from "@questpie/admin/client";
|
|
205
|
+
import type { AppConfig } from "#questpie";
|
|
206
|
+
import { env } from "#questpie/env.client.vite"; // generated from env.client.ts
|
|
207
|
+
|
|
208
|
+
export const authClient = createAdminAuthClient<AppConfig>({
|
|
209
|
+
baseURL: typeof window !== "undefined" ? window.location.origin : env.APP_URL,
|
|
210
|
+
basePath: "/api/auth",
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
const { data: session, isPending } = authClient.useSession();
|
|
216
|
+
await authClient.signIn.email({ email, password });
|
|
217
|
+
await authClient.signIn.anonymous(); // with the anonymous plugin
|
|
218
|
+
await authClient.signOut();
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Apps without `@questpie/admin` use Better Auth's own `createAuthClient` from `better-auth/react` pointed at `${APP_URL}/api/auth` — same call surface, without the app-inferred session typing.
|
|
222
|
+
|
|
108
223
|
## Environment Variables
|
|
109
224
|
|
|
110
225
|
| Variable | Required | Description |
|