create-questpie 2.0.4 → 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.
- package/dist/index.mjs +362 -119
- package/package.json +2 -3
- package/templates/elysia/AGENTS.md +56 -0
- package/templates/elysia/CLAUDE.md +39 -0
- package/templates/elysia/Dockerfile +24 -0
- package/templates/elysia/README.md +148 -0
- package/templates/elysia/docker/init-extensions.sql +11 -0
- package/templates/elysia/docker-compose.yml +21 -0
- package/templates/elysia/env.example +16 -0
- package/templates/elysia/gitignore +6 -0
- package/templates/elysia/package.json +47 -0
- package/templates/elysia/questpie.config.ts +12 -0
- package/templates/elysia/src/index.ts +21 -0
- package/templates/elysia/src/lib/auth-client.ts +32 -0
- package/templates/elysia/src/lib/client.ts +13 -0
- package/templates/elysia/src/lib/env.ts +24 -0
- package/templates/elysia/src/lib/query-client.ts +18 -0
- package/templates/elysia/src/lib/query.ts +18 -0
- package/templates/elysia/src/questpie/server/.generated/context.gen.ts +200 -0
- package/templates/elysia/src/questpie/server/.generated/entities.gen.ts +84 -0
- package/templates/elysia/src/questpie/server/.generated/factories.ts +65 -0
- package/templates/elysia/src/questpie/server/.generated/index.ts +131 -0
- package/templates/elysia/src/questpie/server/.generated/names.gen.ts +25 -0
- package/templates/elysia/src/questpie/server/app.ts +10 -0
- package/templates/elysia/src/questpie/server/collections/index.ts +1 -0
- package/templates/elysia/src/questpie/server/collections/posts.collection.ts +10 -0
- package/templates/elysia/src/questpie/server/config/auth.ts +8 -0
- package/templates/elysia/src/questpie/server/config/openapi.ts +10 -0
- package/templates/elysia/src/questpie/server/globals/index.ts +1 -0
- package/templates/elysia/src/questpie/server/globals/site-settings.global.ts +10 -0
- package/templates/elysia/src/questpie/server/modules.ts +8 -0
- package/templates/elysia/src/questpie/server/questpie.config.ts +21 -0
- package/templates/elysia/tsconfig.json +28 -0
- package/templates/hono/AGENTS.md +56 -0
- package/templates/hono/CLAUDE.md +39 -0
- package/templates/hono/Dockerfile +24 -0
- package/templates/hono/README.md +148 -0
- package/templates/hono/docker/init-extensions.sql +11 -0
- package/templates/hono/docker-compose.yml +21 -0
- package/templates/hono/env.example +16 -0
- package/templates/hono/gitignore +6 -0
- package/templates/hono/package.json +47 -0
- package/templates/hono/questpie.config.ts +12 -0
- package/templates/hono/src/index.ts +30 -0
- package/templates/hono/src/lib/auth-client.ts +32 -0
- package/templates/hono/src/lib/client.ts +13 -0
- package/templates/hono/src/lib/env.ts +24 -0
- package/templates/hono/src/lib/query-client.ts +18 -0
- package/templates/hono/src/lib/query.ts +18 -0
- package/templates/hono/src/questpie/server/.generated/context.gen.ts +200 -0
- package/templates/hono/src/questpie/server/.generated/entities.gen.ts +84 -0
- package/templates/hono/src/questpie/server/.generated/factories.ts +65 -0
- package/templates/hono/src/questpie/server/.generated/index.ts +131 -0
- package/templates/hono/src/questpie/server/.generated/names.gen.ts +25 -0
- package/templates/hono/src/questpie/server/app.ts +10 -0
- package/templates/hono/src/questpie/server/collections/index.ts +1 -0
- package/templates/hono/src/questpie/server/collections/posts.collection.ts +10 -0
- package/templates/hono/src/questpie/server/config/auth.ts +8 -0
- package/templates/hono/src/questpie/server/config/openapi.ts +10 -0
- package/templates/hono/src/questpie/server/globals/index.ts +1 -0
- package/templates/hono/src/questpie/server/globals/site-settings.global.ts +10 -0
- package/templates/hono/src/questpie/server/modules.ts +8 -0
- package/templates/hono/src/questpie/server/questpie.config.ts +21 -0
- package/templates/hono/tsconfig.json +28 -0
- package/templates/next/AGENTS.md +55 -0
- package/templates/next/CLAUDE.md +39 -0
- package/templates/next/Dockerfile +25 -0
- package/templates/next/README.md +148 -0
- package/templates/next/components.json +22 -0
- package/templates/next/docker/init-extensions.sql +11 -0
- package/templates/next/docker-compose.yml +21 -0
- package/templates/next/env.example +16 -0
- package/templates/next/gitignore +10 -0
- package/templates/next/next-env.d.ts +5 -0
- package/templates/next/next.config.ts +20 -0
- package/templates/next/package.json +54 -0
- package/templates/next/postcss.config.mjs +8 -0
- package/templates/next/public/.gitkeep +0 -0
- package/templates/next/questpie.config.ts +12 -0
- package/templates/next/src/app/admin/[[...all]]/page.tsx +34 -0
- package/templates/next/src/app/admin/admin.css +4 -0
- package/templates/next/src/app/admin/layout.tsx +63 -0
- package/templates/next/src/app/api/[...all]/route.ts +24 -0
- package/templates/next/src/app/layout.tsx +24 -0
- package/templates/next/src/app/not-found.tsx +18 -0
- package/templates/next/src/app/page.tsx +74 -0
- package/templates/next/src/app/providers.tsx +11 -0
- package/templates/next/src/lib/auth-client.ts +12 -0
- package/templates/next/src/lib/client.ts +13 -0
- package/templates/next/src/lib/env.ts +24 -0
- package/templates/next/src/lib/query-client.ts +18 -0
- package/templates/next/src/lib/query.ts +18 -0
- package/templates/next/src/questpie/admin/.generated/client.ts +13 -0
- package/templates/next/src/questpie/admin/admin.ts +9 -0
- package/templates/next/src/questpie/admin/modules.ts +3 -0
- package/templates/next/src/questpie/server/.generated/context.gen.ts +204 -0
- package/templates/next/src/questpie/server/.generated/entities.gen.ts +100 -0
- package/templates/next/src/questpie/server/.generated/factories.ts +204 -0
- package/templates/next/src/questpie/server/.generated/index.ts +139 -0
- package/templates/next/src/questpie/server/.generated/names.gen.ts +31 -0
- package/templates/next/src/questpie/server/app.ts +10 -0
- package/templates/next/src/questpie/server/collections/index.ts +1 -0
- package/templates/next/src/questpie/server/collections/posts.collection.ts +58 -0
- package/templates/next/src/questpie/server/config/admin.ts +80 -0
- package/templates/next/src/questpie/server/config/auth.ts +8 -0
- package/templates/next/src/questpie/server/config/openapi.ts +10 -0
- package/templates/next/src/questpie/server/globals/index.ts +1 -0
- package/templates/next/src/questpie/server/globals/site-settings.global.ts +19 -0
- package/templates/next/src/questpie/server/modules.ts +9 -0
- package/templates/next/src/questpie/server/questpie.config.ts +21 -0
- package/templates/next/src/styles.css +125 -0
- package/templates/next/tsconfig.json +37 -0
- package/templates/tanstack-start/AGENTS.md +35 -607
- package/templates/tanstack-start/CLAUDE.md +26 -134
- package/templates/tanstack-start/README.md +13 -1
- package/templates/tanstack-start/docker/init-extensions.sql +11 -0
- package/templates/tanstack-start/docker-compose.yml +1 -0
- package/templates/tanstack-start/src/lib/auth-client.ts +1 -1
- package/templates/tanstack-start/src/lib/client.ts +1 -1
- package/templates/tanstack-start/src/lib/query.ts +18 -0
- package/templates/tanstack-start/src/questpie/server/collections/index.ts +1 -1
- package/templates/tanstack-start/src/questpie/server/globals/index.ts +1 -1
- package/templates/tanstack-start/src/questpie/server/questpie.config.ts +1 -1
- package/templates/tanstack-start/src/routes/__root.tsx +31 -1
- package/templates/tanstack-start/src/routes/api/$.ts +1 -1
- package/templates/tanstack-start/src/routes/index.tsx +97 -0
- package/skills/questpie/AGENTS.md +0 -2871
- package/skills/questpie/SKILL.md +0 -293
- package/skills/questpie/coverage.json +0 -213
- package/skills/questpie/references/auth.md +0 -236
- package/skills/questpie/references/business-logic.md +0 -620
- package/skills/questpie/references/codegen-plugin-api.md +0 -382
- package/skills/questpie/references/crud-api.md +0 -580
- package/skills/questpie/references/data-modeling.md +0 -509
- package/skills/questpie/references/extend.md +0 -584
- package/skills/questpie/references/field-types.md +0 -398
- package/skills/questpie/references/infrastructure-adapters.md +0 -720
- package/skills/questpie/references/mcp.md +0 -147
- package/skills/questpie/references/multi-tenancy.md +0 -363
- package/skills/questpie/references/production.md +0 -640
- package/skills/questpie/references/query-operators.md +0 -125
- package/skills/questpie/references/quickstart.md +0 -562
- package/skills/questpie/references/rules.md +0 -454
- package/skills/questpie/references/sandbox.md +0 -110
- package/skills/questpie/references/tanstack-query.md +0 -543
- package/skills/questpie/references/type-inference.md +0 -167
- package/skills/questpie/references/workflows.md +0 -155
- package/skills/questpie-admin/AGENTS.md +0 -1515
- package/skills/questpie-admin/SKILL.md +0 -443
- package/skills/questpie-admin/references/blocks.md +0 -331
- package/skills/questpie-admin/references/custom-ui.md +0 -305
- package/skills/questpie-admin/references/views.md +0 -449
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
# Type Inference Reference
|
|
2
|
-
|
|
3
|
-
The schema is the single source of types. If you are hand-writing a type that restates a schema (a row shape, a session shape, a payload), stop — there is a sanctioned inference one-liner for it. Hand-rolled structural types drift silently (real schema `string | null` vs hand-rolled `string | undefined`) and structural mirrors of the CRUD generics produce deep error walls at every call site.
|
|
4
|
-
|
|
5
|
-
## The Map — "I Need Type X"
|
|
6
|
-
|
|
7
|
-
| # | You need | Write exactly this | Notes |
|
|
8
|
-
| --- | --- | --- | --- |
|
|
9
|
-
| 1 | Row of **another** collection | `import type { CollectionDoc } from "#questpie"` → `CollectionDoc<"toys">` | Type-only import. See cycle rules below |
|
|
10
|
-
| 2 | Own row inside `.access()` / `.hooks()` | Nothing — `ctx.data` / `ctx.input` are already typed by the builder | Never name your own doc type inside the defining collection |
|
|
11
|
-
| 3 | Shared access-helper parameter | Collection-imported helper: `AccessContext` from `"questpie"`. Anywhere else: `AccessRuleContext<"posts">` from `#questpie` (narrows `ctx.data`) | See cycle rules below |
|
|
12
|
-
| 4 | Shared hook-helper parameter | `HookContext` from `"questpie"` (collection-imported) or `HookRuleContext<"posts">` from `#questpie` | Same rules as #3 |
|
|
13
|
-
| 5 | App/services in a function without a ctx param | `getContext<App>()` with `import type { App } from "#questpie"` | Type-only `App` import — no runtime cycle |
|
|
14
|
-
| 6 | Global doc | `import type { GlobalDoc } from "#questpie"` → `GlobalDoc<"siteSettings">` | Same cycle rules as `CollectionDoc` |
|
|
15
|
-
| 7 | Session / user shape | In handlers: `ctx.session?.user` is typed. Standalone: `import type { AppSession, AppSessionUser } from "#questpie"` | Generated from the app auth config |
|
|
16
|
-
| 8 | Route input/output in the handler | Nothing — inferred from `.schema()` / return type | |
|
|
17
|
-
| 9 | Route input/output standalone | `InferRouteInput<typeof def>` / `InferRouteOutput<typeof def>` / `InferRouteParams<typeof def>` from `questpie/types` | tRPC-style; `def` is the route file's default export |
|
|
18
|
-
| 10 | Client-side types | `createClient<AppConfig>()` — everything flows from the generic | See `references/tanstack-query.md` |
|
|
19
|
-
| 11 | Job payload in the handler | Nothing — `payload` is typed from `schema` | |
|
|
20
|
-
| 12 | Job payload standalone | `InferJobPayload<typeof jobDef>` from `questpie/queue` (or `z.infer<typeof jobDef.schema>`) | |
|
|
21
|
-
| 13 | `db` / `session` inside job/workflow handlers | Honest gap: generated job context types them `unknown` today | Use `collections` (typed) or narrow explicitly; do not restate schemas |
|
|
22
|
-
| 14 | Publishing jobs outside job files | `ctx.queue.<name>.publish(payload)` — payload typed | |
|
|
23
|
-
| 15 | Relation target autocomplete | Nothing — codegen populates `Questpie.CollectionKeys` from discovered files; `f.relation("…")` autocompletes after `questpie generate` | Plain strings always compile |
|
|
24
|
-
| 16 | Realtime payloads | `live()` / `liveIter()` snapshots are typed; raw `client.realtime.subscribe` data is untyped — annotate with `CollectionDoc<"posts">` | Typed realtime contract is planned |
|
|
25
|
-
| 17 | Env vars | `env.ts` / `env.client.ts` with `env()` — see `references/env.md` | Never `process.env.X!` |
|
|
26
|
-
| 18 | Field-level rule ctx (`.access({ fields })`) | `doc` is typed as the row, `user` is typed from the generated session — destructure, don't annotate | |
|
|
27
|
-
| 19 | Derived request context (tenant, role) | `appConfig({ context })` result is inferred and arrives flat on rules — see `references/rules.md` | |
|
|
28
|
-
| 20 | Select-option unions | `CollectionDoc<"events">["type"]` (server-side) | No client-safe union export yet; clients infer from SDK responses |
|
|
29
|
-
|
|
30
|
-
## The Two Cycle Rules
|
|
31
|
-
|
|
32
|
-
Type inference flows through the generated index (`#questpie`), and collections are part of that graph. Two rules keep every inference path compiling:
|
|
33
|
-
|
|
34
|
-
**Rule 1 — inside the defining collection, trust builder inference.** `ctx.data` and `ctx.input` are already typed per operation (table below). Naming your own doc type (`CollectionDoc<"production_orders">` inside `collections/production-orders.ts`, or `ctx.data as OrderDoc`) forces TypeScript to resolve `typeof <own default export>` while that export's type is still being inferred — TS2456/TS7022, or worse, a silently degraded type.
|
|
35
|
-
|
|
36
|
-
**Rule 2 — helpers imported by collections must not import generated aliases, and must cut the inference loop with an explicit return annotation.** The verified pattern (from `examples/toy-factory-backend/src/questpie/server/lib/access.ts`):
|
|
37
|
-
|
|
38
|
-
```ts
|
|
39
|
-
// lib/access.ts — imported by a collection, so:
|
|
40
|
-
// - the helper param is the package-level AccessContext (cycle-safe)
|
|
41
|
-
// - the return type is annotated explicitly with a CROSS-collection
|
|
42
|
-
// CollectionDoc (type-only) — this cut breaks the inference loop that
|
|
43
|
-
// otherwise forms when the helper dereferences ctx.collections back
|
|
44
|
-
// into the module graph (TS7022/TS2502 without it)
|
|
45
|
-
import type { AccessContext } from "questpie";
|
|
46
|
-
import type { CollectionDoc } from "#questpie";
|
|
47
|
-
|
|
48
|
-
export async function resolveOrderToy(
|
|
49
|
-
ctx: AccessContext,
|
|
50
|
-
toyId: string,
|
|
51
|
-
): Promise<{ toy: CollectionDoc<"toys"> | null; userId: string | null }> {
|
|
52
|
-
const toy = await ctx.collections.toys.findOne(
|
|
53
|
-
{ where: { id: toyId } },
|
|
54
|
-
{ accessMode: "system" },
|
|
55
|
-
);
|
|
56
|
-
return { toy, userId: ctx.session?.user.id ?? null };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Narrow `data` structurally when the helper only reads a few fields. */
|
|
60
|
-
export function canCancelOrder(ctx: AccessContext<{ priority?: string | null }>) {
|
|
61
|
-
if (ctx.data?.priority === "rush") return !!ctx.session?.user;
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
`ctx.app`, `ctx.collections`, and `ctx.session` are fully typed on `AccessContext` through the (lazily merged) AppContext augmentation — the explicit return annotation stays mandatory in collection-imported helpers (it cuts the inference loop).
|
|
67
|
-
|
|
68
|
-
Helpers **not** imported by any collection (scripts, routes, services, jobs) may freely use `CollectionDoc<K>` in parameters and locals — Rule 2 only binds files that collections import.
|
|
69
|
-
|
|
70
|
-
## Per-Operation Access Rule Typing
|
|
71
|
-
|
|
72
|
-
`.access()` rules are typed per operation by the builder — no annotations, no casts:
|
|
73
|
-
|
|
74
|
-
| Rule | `ctx.data` | `ctx.input` |
|
|
75
|
-
| --- | --- | --- |
|
|
76
|
-
| `read` | not loaded (return `AccessWhere` to filter) | — |
|
|
77
|
-
| `create` | — (no row exists yet) | typed insert shape (pre-validation) |
|
|
78
|
-
| `update` | the existing row — **non-optional** | typed update patch |
|
|
79
|
-
| `delete` | the existing row — **non-optional** | — |
|
|
80
|
-
| `transition` / `serve` | the existing row — non-optional | — |
|
|
81
|
-
|
|
82
|
-
```ts
|
|
83
|
-
export default collection("production_orders")
|
|
84
|
-
.fields(({ f }) => ({
|
|
85
|
-
toy: f.relation("toys").required(),
|
|
86
|
-
priority: f.select([{ value: "normal" }, { value: "rush" }]),
|
|
87
|
-
}))
|
|
88
|
-
.access({
|
|
89
|
-
create: ({ session, input }) => !!session && input?.priority !== "rush",
|
|
90
|
-
update: async (ctx) => {
|
|
91
|
-
ctx.data; // typed row, non-optional — no `as` cast, no isRecord() dance
|
|
92
|
-
ctx.input; // typed patch
|
|
93
|
-
return (await resolveOrderToy(ctx, ctx.data.toy)).userId !== null;
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
The package-level helper types are exported from `questpie/types` (also re-exported from `questpie`): `AccessContext`, `RowAccessRule`, `AccessRule`, `AccessWhere`, `CollectionAccess`, `HookContext`, `FieldAccessRule`, `FieldAccessRuleContext`.
|
|
99
|
-
|
|
100
|
-
## Typed `getContext<App>()`
|
|
101
|
-
|
|
102
|
-
For functions that need the app without threading a ctx parameter (and for Better Auth callbacks — see `references/auth.md`):
|
|
103
|
-
|
|
104
|
-
```ts
|
|
105
|
-
import { getContext } from "questpie";
|
|
106
|
-
import type { App } from "#questpie"; // type-only — no runtime cycle
|
|
107
|
-
|
|
108
|
-
async function logActivity(action: string) {
|
|
109
|
-
const { app, session, locale } = getContext<App>();
|
|
110
|
-
await app.collections.activity_log.create({
|
|
111
|
-
user: session?.user.id,
|
|
112
|
-
action,
|
|
113
|
-
locale,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Untyped `getContext()` returns the bare context; the `<App>` generic types `app`, `session`, and the derived request-context extensions.
|
|
119
|
-
|
|
120
|
-
## Standalone Route And Job Types
|
|
121
|
-
|
|
122
|
-
```ts
|
|
123
|
-
import type { InferRouteInput, InferRouteOutput } from "questpie/types";
|
|
124
|
-
import type { InferJobPayload } from "questpie/queue";
|
|
125
|
-
import createBooking from "../routes/create-booking";
|
|
126
|
-
import sendReminder from "../jobs/send-reminder";
|
|
127
|
-
|
|
128
|
-
type BookingInput = InferRouteInput<typeof createBooking>;
|
|
129
|
-
type BookingResult = InferRouteOutput<typeof createBooking>;
|
|
130
|
-
type ReminderPayload = InferJobPayload<typeof sendReminder>;
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
## Key Registries (Advanced, Optional)
|
|
134
|
-
|
|
135
|
-
Names-only registries give `f.relation()` target autocomplete without entering the type graph (no imports — they cannot cycle). Codegen does **not** populate them yet; augment manually when you want the autocomplete:
|
|
136
|
-
|
|
137
|
-
```ts
|
|
138
|
-
// types/questpie-keys.d.ts (any ambient file)
|
|
139
|
-
declare global {
|
|
140
|
-
namespace Questpie {
|
|
141
|
-
interface CollectionKeys { toys: unknown; production_orders: unknown }
|
|
142
|
-
interface GlobalKeys { factorySettings: unknown }
|
|
143
|
-
interface JobKeys { sendReminder: unknown }
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
export {};
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
`f.relation("toys")` then autocompletes, while arbitrary strings keep compiling (`(string & {})` fallback) — this is autocomplete, not strictness. `KnownCollectionKey` / `KnownGlobalKey` / `KnownJobKey` from `questpie/types` consume the registries in your own signatures.
|
|
150
|
-
|
|
151
|
-
## Escape Hatches (When Inference Needs Help)
|
|
152
|
-
|
|
153
|
-
For columns whose value type the field cannot infer, stay declarative — see `references/field-types.md`:
|
|
154
|
-
|
|
155
|
-
- `.zod(schema)` — type **and** runtime validation (preferred for `f.json()`)
|
|
156
|
-
- `.$type<T>()` — type-only narrowing
|
|
157
|
-
- `.drizzle(fn)` — raw column builder; `$type` on it narrows the value type
|
|
158
|
-
|
|
159
|
-
## Never Do
|
|
160
|
-
|
|
161
|
-
| Anti-pattern | Why | Instead |
|
|
162
|
-
| --- | --- | --- |
|
|
163
|
-
| Hand-rolled `EventDoc = { id: string; ownerUser?: string }` | Silent nullability drift vs the real schema | `CollectionDoc<"events">` (row 1) |
|
|
164
|
-
| `ctx.data as MemberDoc` inside own `.access()` | Builder already types it; self-key casts can cycle (TS2456) | Trust `ctx.data` (row 2) |
|
|
165
|
-
| Hand-rolled `CollectionsLike` / `AccessRuleCtx` ctx mirrors | Structural matching of CRUD generics → deep error walls, tsc 5.9 crashes | `AccessContext` param (row 3) |
|
|
166
|
-
| Module-level `app` singleton for callbacks | Import cycles; stale instance in tests | `getContext<App>()` (row 5) |
|
|
167
|
-
| Collection-imported helper returning unannotated `ctx.collections` results | TS7022/TS2502 self-reference | Explicit return annotation (Rule 2) |
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: questpie-core/workflows
|
|
3
|
-
description:
|
|
4
|
-
QUESTPIE durable workflows long-running business processes replay steps sleep waitForEvent invoke compensation cron admin UI workflow service
|
|
5
|
-
- questpie-core
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Durable Workflows
|
|
9
|
-
|
|
10
|
-
Use `@questpie/workflows` when business logic spans multiple steps, waits on time or external events, needs retry-safe side effects, or should survive process restarts.
|
|
11
|
-
|
|
12
|
-
## Install And Register
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
bun add @questpie/workflows
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
```ts title="modules.ts"
|
|
19
|
-
import { workflowsModule } from "@questpie/workflows/modules/workflows";
|
|
20
|
-
export default [workflowsModule] as const;
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
`workflowsModule` carries its codegen plugin. Do not also add `workflowsPlugin()` to `questpie.config.ts` unless you are doing a custom module setup that deliberately omits `workflowsModule`.
|
|
24
|
-
|
|
25
|
-
Runtime options (route access rule — default admin-only — and execution-lease settings) go in plugin-discovered `config/workflows.ts` using the `workflowsConfig()` factory from `@questpie/workflows/server`.
|
|
26
|
-
|
|
27
|
-
For admin UI pages/widgets, register the client module:
|
|
28
|
-
|
|
29
|
-
```ts title="questpie/admin/modules.ts"
|
|
30
|
-
import adminClientModule from "@questpie/admin/client-module";
|
|
31
|
-
import { workflowsClientModule } from "@questpie/workflows/client/modules/workflows";
|
|
32
|
-
export default {
|
|
33
|
-
name: "app-admin" as const,
|
|
34
|
-
views: { ...adminClientModule.views, ...workflowsClientModule.views },
|
|
35
|
-
components: {
|
|
36
|
-
...adminClientModule.components,
|
|
37
|
-
...workflowsClientModule.components,
|
|
38
|
-
},
|
|
39
|
-
fields: { ...adminClientModule.fields, ...workflowsClientModule.fields },
|
|
40
|
-
pages: { ...adminClientModule.pages, ...workflowsClientModule.pages },
|
|
41
|
-
widgets: { ...adminClientModule.widgets, ...workflowsClientModule.widgets },
|
|
42
|
-
blocks: { ...adminClientModule.blocks, ...workflowsClientModule.blocks },
|
|
43
|
-
};
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Define A Workflow
|
|
47
|
-
|
|
48
|
-
Put workflow definitions in `workflows/*.ts`:
|
|
49
|
-
|
|
50
|
-
```ts title="workflows/production-order.ts"
|
|
51
|
-
import { workflow } from "@questpie/workflows";
|
|
52
|
-
import { z } from "zod";
|
|
53
|
-
|
|
54
|
-
export default workflow({
|
|
55
|
-
name: "production-order",
|
|
56
|
-
schema: z.object({
|
|
57
|
-
orderId: z.string(),
|
|
58
|
-
}),
|
|
59
|
-
timeout: "7d",
|
|
60
|
-
handler: async ({ input, step, ctx, log }) => {
|
|
61
|
-
const order = await step.run("load-order", async () => {
|
|
62
|
-
return ctx.collections.productionOrders.findOne({
|
|
63
|
-
where: { id: input.orderId },
|
|
64
|
-
with: { toy: true },
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
if (!order) throw new Error("Production order not found");
|
|
68
|
-
|
|
69
|
-
await step.run("reserve-materials", async () => {
|
|
70
|
-
await ctx.queue.recalculateMaterialPlan.publish({
|
|
71
|
-
orderId: input.orderId,
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
await step.waitForEvent("materials-ready", {
|
|
76
|
-
event: "materials.available",
|
|
77
|
-
match: { orderId: input.orderId },
|
|
78
|
-
timeout: "2d",
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
await step.run("notify-scheduled", async () => {
|
|
82
|
-
await ctx.email.sendTemplate({
|
|
83
|
-
template: "productionScheduled",
|
|
84
|
-
input: { orderId: input.orderId },
|
|
85
|
-
to: order.ownerEmail,
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
log.info("Production order workflow completed");
|
|
90
|
-
return { status: "scheduled" };
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Run codegen after adding workflow files:
|
|
96
|
-
|
|
97
|
-
```bash
|
|
98
|
-
bun questpie generate
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## Trigger And Signal
|
|
102
|
-
|
|
103
|
-
Use injected `workflows` from route, job, hook, or service context:
|
|
104
|
-
|
|
105
|
-
```ts title="routes/start-production.ts"
|
|
106
|
-
import { route } from "questpie/services";
|
|
107
|
-
import { z } from "zod";
|
|
108
|
-
|
|
109
|
-
export default route()
|
|
110
|
-
.post()
|
|
111
|
-
.schema(z.object({ orderId: z.string() }))
|
|
112
|
-
.handler(async ({ input, workflows }) => {
|
|
113
|
-
const result = await workflows.trigger("production-order", {
|
|
114
|
-
orderId: input.orderId,
|
|
115
|
-
});
|
|
116
|
-
return { instanceId: result.instanceId };
|
|
117
|
-
});
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
Signal waiting workflows:
|
|
121
|
-
|
|
122
|
-
```ts
|
|
123
|
-
await workflows.sendEvent(
|
|
124
|
-
"materials.available",
|
|
125
|
-
{ receivedAt: new Date().toISOString() },
|
|
126
|
-
{ orderId },
|
|
127
|
-
);
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## Cron Workflows
|
|
131
|
-
|
|
132
|
-
Use workflow-level `cron` for recurring long-running processes:
|
|
133
|
-
|
|
134
|
-
```ts
|
|
135
|
-
export default workflow({
|
|
136
|
-
name: "nightly-material-plan",
|
|
137
|
-
schema: z.object({}),
|
|
138
|
-
cron: { schedule: "0 2 * * *", overlap: "skip" },
|
|
139
|
-
handler: async ({ step, ctx }) => {
|
|
140
|
-
await step.run("recalculate", async () => {
|
|
141
|
-
await ctx.queue.recalculateMaterialPlan.publish({});
|
|
142
|
-
});
|
|
143
|
-
},
|
|
144
|
-
});
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
On Node/Bun workers, `app.queue.listen()` runs workflow jobs and maintenance. On Cloudflare Workers, use `cloudflareQueuesAdapter`, export `createCloudflareWorkerHandlers(app)`, and configure a Cron Trigger for workflow maintenance.
|
|
148
|
-
|
|
149
|
-
## Rules
|
|
150
|
-
|
|
151
|
-
- Keep external side effects inside `step.run()` so replay does not repeat them.
|
|
152
|
-
- Use stable step names. Renaming a step changes replay identity.
|
|
153
|
-
- Use idempotency keys when calling external APIs.
|
|
154
|
-
- Use `step.waitForEvent()` for durable waits instead of polling loops.
|
|
155
|
-
- Keep workflow definitions in `workflows/`; do not define them inside route/job files.
|