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.
Files changed (152) hide show
  1. package/dist/index.mjs +362 -119
  2. package/package.json +2 -3
  3. package/templates/elysia/AGENTS.md +56 -0
  4. package/templates/elysia/CLAUDE.md +39 -0
  5. package/templates/elysia/Dockerfile +24 -0
  6. package/templates/elysia/README.md +148 -0
  7. package/templates/elysia/docker/init-extensions.sql +11 -0
  8. package/templates/elysia/docker-compose.yml +21 -0
  9. package/templates/elysia/env.example +16 -0
  10. package/templates/elysia/gitignore +6 -0
  11. package/templates/elysia/package.json +47 -0
  12. package/templates/elysia/questpie.config.ts +12 -0
  13. package/templates/elysia/src/index.ts +21 -0
  14. package/templates/elysia/src/lib/auth-client.ts +32 -0
  15. package/templates/elysia/src/lib/client.ts +13 -0
  16. package/templates/elysia/src/lib/env.ts +24 -0
  17. package/templates/elysia/src/lib/query-client.ts +18 -0
  18. package/templates/elysia/src/lib/query.ts +18 -0
  19. package/templates/elysia/src/questpie/server/.generated/context.gen.ts +200 -0
  20. package/templates/elysia/src/questpie/server/.generated/entities.gen.ts +84 -0
  21. package/templates/elysia/src/questpie/server/.generated/factories.ts +65 -0
  22. package/templates/elysia/src/questpie/server/.generated/index.ts +131 -0
  23. package/templates/elysia/src/questpie/server/.generated/names.gen.ts +25 -0
  24. package/templates/elysia/src/questpie/server/app.ts +10 -0
  25. package/templates/elysia/src/questpie/server/collections/index.ts +1 -0
  26. package/templates/elysia/src/questpie/server/collections/posts.collection.ts +10 -0
  27. package/templates/elysia/src/questpie/server/config/auth.ts +8 -0
  28. package/templates/elysia/src/questpie/server/config/openapi.ts +10 -0
  29. package/templates/elysia/src/questpie/server/globals/index.ts +1 -0
  30. package/templates/elysia/src/questpie/server/globals/site-settings.global.ts +10 -0
  31. package/templates/elysia/src/questpie/server/modules.ts +8 -0
  32. package/templates/elysia/src/questpie/server/questpie.config.ts +21 -0
  33. package/templates/elysia/tsconfig.json +28 -0
  34. package/templates/hono/AGENTS.md +56 -0
  35. package/templates/hono/CLAUDE.md +39 -0
  36. package/templates/hono/Dockerfile +24 -0
  37. package/templates/hono/README.md +148 -0
  38. package/templates/hono/docker/init-extensions.sql +11 -0
  39. package/templates/hono/docker-compose.yml +21 -0
  40. package/templates/hono/env.example +16 -0
  41. package/templates/hono/gitignore +6 -0
  42. package/templates/hono/package.json +47 -0
  43. package/templates/hono/questpie.config.ts +12 -0
  44. package/templates/hono/src/index.ts +30 -0
  45. package/templates/hono/src/lib/auth-client.ts +32 -0
  46. package/templates/hono/src/lib/client.ts +13 -0
  47. package/templates/hono/src/lib/env.ts +24 -0
  48. package/templates/hono/src/lib/query-client.ts +18 -0
  49. package/templates/hono/src/lib/query.ts +18 -0
  50. package/templates/hono/src/questpie/server/.generated/context.gen.ts +200 -0
  51. package/templates/hono/src/questpie/server/.generated/entities.gen.ts +84 -0
  52. package/templates/hono/src/questpie/server/.generated/factories.ts +65 -0
  53. package/templates/hono/src/questpie/server/.generated/index.ts +131 -0
  54. package/templates/hono/src/questpie/server/.generated/names.gen.ts +25 -0
  55. package/templates/hono/src/questpie/server/app.ts +10 -0
  56. package/templates/hono/src/questpie/server/collections/index.ts +1 -0
  57. package/templates/hono/src/questpie/server/collections/posts.collection.ts +10 -0
  58. package/templates/hono/src/questpie/server/config/auth.ts +8 -0
  59. package/templates/hono/src/questpie/server/config/openapi.ts +10 -0
  60. package/templates/hono/src/questpie/server/globals/index.ts +1 -0
  61. package/templates/hono/src/questpie/server/globals/site-settings.global.ts +10 -0
  62. package/templates/hono/src/questpie/server/modules.ts +8 -0
  63. package/templates/hono/src/questpie/server/questpie.config.ts +21 -0
  64. package/templates/hono/tsconfig.json +28 -0
  65. package/templates/next/AGENTS.md +55 -0
  66. package/templates/next/CLAUDE.md +39 -0
  67. package/templates/next/Dockerfile +25 -0
  68. package/templates/next/README.md +148 -0
  69. package/templates/next/components.json +22 -0
  70. package/templates/next/docker/init-extensions.sql +11 -0
  71. package/templates/next/docker-compose.yml +21 -0
  72. package/templates/next/env.example +16 -0
  73. package/templates/next/gitignore +10 -0
  74. package/templates/next/next-env.d.ts +5 -0
  75. package/templates/next/next.config.ts +20 -0
  76. package/templates/next/package.json +54 -0
  77. package/templates/next/postcss.config.mjs +8 -0
  78. package/templates/next/public/.gitkeep +0 -0
  79. package/templates/next/questpie.config.ts +12 -0
  80. package/templates/next/src/app/admin/[[...all]]/page.tsx +34 -0
  81. package/templates/next/src/app/admin/admin.css +4 -0
  82. package/templates/next/src/app/admin/layout.tsx +63 -0
  83. package/templates/next/src/app/api/[...all]/route.ts +24 -0
  84. package/templates/next/src/app/layout.tsx +24 -0
  85. package/templates/next/src/app/not-found.tsx +18 -0
  86. package/templates/next/src/app/page.tsx +74 -0
  87. package/templates/next/src/app/providers.tsx +11 -0
  88. package/templates/next/src/lib/auth-client.ts +12 -0
  89. package/templates/next/src/lib/client.ts +13 -0
  90. package/templates/next/src/lib/env.ts +24 -0
  91. package/templates/next/src/lib/query-client.ts +18 -0
  92. package/templates/next/src/lib/query.ts +18 -0
  93. package/templates/next/src/questpie/admin/.generated/client.ts +13 -0
  94. package/templates/next/src/questpie/admin/admin.ts +9 -0
  95. package/templates/next/src/questpie/admin/modules.ts +3 -0
  96. package/templates/next/src/questpie/server/.generated/context.gen.ts +204 -0
  97. package/templates/next/src/questpie/server/.generated/entities.gen.ts +100 -0
  98. package/templates/next/src/questpie/server/.generated/factories.ts +204 -0
  99. package/templates/next/src/questpie/server/.generated/index.ts +139 -0
  100. package/templates/next/src/questpie/server/.generated/names.gen.ts +31 -0
  101. package/templates/next/src/questpie/server/app.ts +10 -0
  102. package/templates/next/src/questpie/server/collections/index.ts +1 -0
  103. package/templates/next/src/questpie/server/collections/posts.collection.ts +58 -0
  104. package/templates/next/src/questpie/server/config/admin.ts +80 -0
  105. package/templates/next/src/questpie/server/config/auth.ts +8 -0
  106. package/templates/next/src/questpie/server/config/openapi.ts +10 -0
  107. package/templates/next/src/questpie/server/globals/index.ts +1 -0
  108. package/templates/next/src/questpie/server/globals/site-settings.global.ts +19 -0
  109. package/templates/next/src/questpie/server/modules.ts +9 -0
  110. package/templates/next/src/questpie/server/questpie.config.ts +21 -0
  111. package/templates/next/src/styles.css +125 -0
  112. package/templates/next/tsconfig.json +37 -0
  113. package/templates/tanstack-start/AGENTS.md +35 -607
  114. package/templates/tanstack-start/CLAUDE.md +26 -134
  115. package/templates/tanstack-start/README.md +13 -1
  116. package/templates/tanstack-start/docker/init-extensions.sql +11 -0
  117. package/templates/tanstack-start/docker-compose.yml +1 -0
  118. package/templates/tanstack-start/src/lib/auth-client.ts +1 -1
  119. package/templates/tanstack-start/src/lib/client.ts +1 -1
  120. package/templates/tanstack-start/src/lib/query.ts +18 -0
  121. package/templates/tanstack-start/src/questpie/server/collections/index.ts +1 -1
  122. package/templates/tanstack-start/src/questpie/server/globals/index.ts +1 -1
  123. package/templates/tanstack-start/src/questpie/server/questpie.config.ts +1 -1
  124. package/templates/tanstack-start/src/routes/__root.tsx +31 -1
  125. package/templates/tanstack-start/src/routes/api/$.ts +1 -1
  126. package/templates/tanstack-start/src/routes/index.tsx +97 -0
  127. package/skills/questpie/AGENTS.md +0 -2871
  128. package/skills/questpie/SKILL.md +0 -293
  129. package/skills/questpie/coverage.json +0 -213
  130. package/skills/questpie/references/auth.md +0 -236
  131. package/skills/questpie/references/business-logic.md +0 -620
  132. package/skills/questpie/references/codegen-plugin-api.md +0 -382
  133. package/skills/questpie/references/crud-api.md +0 -580
  134. package/skills/questpie/references/data-modeling.md +0 -509
  135. package/skills/questpie/references/extend.md +0 -584
  136. package/skills/questpie/references/field-types.md +0 -398
  137. package/skills/questpie/references/infrastructure-adapters.md +0 -720
  138. package/skills/questpie/references/mcp.md +0 -147
  139. package/skills/questpie/references/multi-tenancy.md +0 -363
  140. package/skills/questpie/references/production.md +0 -640
  141. package/skills/questpie/references/query-operators.md +0 -125
  142. package/skills/questpie/references/quickstart.md +0 -562
  143. package/skills/questpie/references/rules.md +0 -454
  144. package/skills/questpie/references/sandbox.md +0 -110
  145. package/skills/questpie/references/tanstack-query.md +0 -543
  146. package/skills/questpie/references/type-inference.md +0 -167
  147. package/skills/questpie/references/workflows.md +0 -155
  148. package/skills/questpie-admin/AGENTS.md +0 -1515
  149. package/skills/questpie-admin/SKILL.md +0 -443
  150. package/skills/questpie-admin/references/blocks.md +0 -331
  151. package/skills/questpie-admin/references/custom-ui.md +0 -305
  152. 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.