@beignet/cli 0.0.22 → 0.0.24

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 (59) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +35 -5
  3. package/dist/choices.d.ts +8 -0
  4. package/dist/choices.d.ts.map +1 -1
  5. package/dist/choices.js +4 -0
  6. package/dist/choices.js.map +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +8 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/inspect.d.ts.map +1 -1
  11. package/dist/inspect.js +184 -0
  12. package/dist/inspect.js.map +1 -1
  13. package/dist/lib.d.ts +2 -2
  14. package/dist/lib.d.ts.map +1 -1
  15. package/dist/lib.js +1 -1
  16. package/dist/lib.js.map +1 -1
  17. package/dist/make.d.ts +4 -3
  18. package/dist/make.d.ts.map +1 -1
  19. package/dist/make.js +349 -45
  20. package/dist/make.js.map +1 -1
  21. package/dist/mcp.d.ts.map +1 -1
  22. package/dist/mcp.js +6 -2
  23. package/dist/mcp.js.map +1 -1
  24. package/dist/templates/agents.d.ts.map +1 -1
  25. package/dist/templates/agents.js +9 -5
  26. package/dist/templates/agents.js.map +1 -1
  27. package/dist/templates/base.d.ts.map +1 -1
  28. package/dist/templates/base.js +4 -2
  29. package/dist/templates/base.js.map +1 -1
  30. package/dist/templates/db/mysql.js +2 -2
  31. package/dist/templates/db/postgres.js +2 -2
  32. package/dist/templates/db/sqlite.js +2 -2
  33. package/dist/templates/index.d.ts.map +1 -1
  34. package/dist/templates/index.js +1 -0
  35. package/dist/templates/index.js.map +1 -1
  36. package/dist/templates/server.d.ts +1 -0
  37. package/dist/templates/server.d.ts.map +1 -1
  38. package/dist/templates/server.js +90 -20
  39. package/dist/templates/server.js.map +1 -1
  40. package/dist/templates/shell.d.ts.map +1 -1
  41. package/dist/templates/shell.js +60 -29
  42. package/dist/templates/shell.js.map +1 -1
  43. package/dist/templates/todos.js +5 -5
  44. package/package.json +2 -2
  45. package/src/choices.ts +10 -0
  46. package/src/index.ts +11 -0
  47. package/src/inspect.ts +319 -0
  48. package/src/lib.ts +2 -1
  49. package/src/make.ts +449 -53
  50. package/src/mcp.ts +7 -1
  51. package/src/templates/agents.ts +9 -5
  52. package/src/templates/base.ts +4 -2
  53. package/src/templates/db/mysql.ts +2 -2
  54. package/src/templates/db/postgres.ts +2 -2
  55. package/src/templates/db/sqlite.ts +2 -2
  56. package/src/templates/index.ts +1 -0
  57. package/src/templates/server.ts +90 -20
  58. package/src/templates/shell.ts +61 -29
  59. package/src/templates/todos.ts +5 -5
package/src/mcp.ts CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  makeFactory,
13
13
  makeFeature,
14
14
  makeFeatureAddonChoices,
15
+ makeFeatureRecipeChoices,
15
16
  makeJob,
16
17
  makeListener,
17
18
  makeNotification,
@@ -76,6 +77,10 @@ const makeInputSchema = {
76
77
  .describe(
77
78
  "feature only: add feature-owned artifacts such as policy, factories, seeds, events, listeners, jobs, notifications, schedules, tasks, ui, and uploads.",
78
79
  ),
80
+ recipe: z
81
+ .enum(makeFeatureRecipeChoices)
82
+ .optional()
83
+ .describe("feature only: generate a named recipe such as full-slice."),
79
84
  event: z
80
85
  .string()
81
86
  .optional()
@@ -132,6 +137,7 @@ type MakeToolInput = {
132
137
  artifact: MakeArtifact;
133
138
  name?: string;
134
139
  with?: Array<(typeof makeFeatureAddonChoices)[number]>;
140
+ recipe?: (typeof makeFeatureRecipeChoices)[number];
135
141
  event?: string;
136
142
  cron?: string;
137
143
  timezone?: string;
@@ -216,7 +222,7 @@ async function runMakeTool(
216
222
  case "factory":
217
223
  return makeFactory(base);
218
224
  case "feature":
219
- return makeFeature({ ...base, with: input.with });
225
+ return makeFeature({ ...base, with: input.with, recipe: input.recipe });
220
226
  case "job":
221
227
  return makeJob(base);
222
228
  case "listener":
@@ -52,8 +52,9 @@ is a silent failure — the file exists but never runs:
52
52
 
53
53
  The starter ships no workflow registries — generators create them on first
54
54
  use, so their absence is fine. \`${cli} make event\`, \`${cli} make job\`,
55
- \`${cli} make schedule\`, \`${cli} make task\`, \`${cli} make seed\`, and
56
- \`${cli} make upload\` create or update their required app entrypoints.
55
+ \`${cli} make listener\`, \`${cli} make schedule\`, \`${cli} make task\`,
56
+ \`${cli} make seed\`, and \`${cli} make upload\` create or update their
57
+ required app entrypoints.
57
58
  \`${cli} doctor\` detects registration drift. \`${cli} doctor --fix\` repairs
58
59
  route-group, schedule, task, and outbox registration; listener drift is
59
60
  report-only and must be fixed by hand.
@@ -62,8 +63,10 @@ report-only and must be fixed by hand.
62
63
 
63
64
  \`${cli} make <artifact> <name>\` creates correctly placed, pre-registered
64
65
  files — prefer it over hand-writing them. See \`${cli} make --help\` for the
65
- artifact list. After changing the Drizzle schema in \`infra/db/schema/\`, run
66
- \`${cli} db generate\` then \`${cli} db migrate\`.
66
+ artifact list. Use \`${cli} make feature <name> --recipe full-slice\` when you
67
+ need a richer reference slice with policy, client helpers, workflow artifacts,
68
+ events, listener registration, jobs, and outbox wiring. After changing the Drizzle schema in
69
+ \`infra/db/schema/\`, run \`${cli} db generate\` then \`${cli} db migrate\`.
67
70
 
68
71
  ## Validation loop
69
72
 
@@ -98,7 +101,8 @@ skill-loading block.
98
101
 
99
102
  - Feature artifacts live under \`features/<feature>/\`, with tests in
100
103
  \`features/<feature>/tests/\` (not \`__tests__/\`).
101
- - Feature-specific client data-fetching helpers and hooks may live in
104
+ - Feature-specific client data-fetching helpers, query options, mutation
105
+ options, invalidation helpers, and hooks live in
102
106
  \`features/<feature>/client/\`; shared client setup stays in root \`client/\`.
103
107
  - Domain and use-case code must not import infra, providers, or React.
104
108
  - Routes must not import concrete infra.
@@ -273,6 +273,7 @@ ${cli} doctor
273
273
  \`\`\`
274
274
 
275
275
  \`make feature\` creates a contract-to-test vertical slice with Drizzle schema and repository files, so regenerate and migrate the database before running the app against the new feature.
276
+ Use \`${cli} make feature projects --recipe full-slice\` when you want a richer reference slice with policy, feature client helpers, workflow artifacts, events, listener registration, jobs, and outbox wiring.
276
277
 
277
278
  ## App map
278
279
 
@@ -418,7 +419,7 @@ export function envExample(ctx: TemplateContext): string {
418
419
  "# Copy to .env.local and fill in values for your app.",
419
420
  "APP_URL=http://localhost:3000",
420
421
  "",
421
- "# Dev-only default; remove in production.",
422
+ "# Dev-only default; remove or protect in production.",
422
423
  "DEVTOOLS_ENABLED=true",
423
424
  "",
424
425
  "LOG_LEVEL=info",
@@ -427,7 +428,8 @@ export function envExample(ctx: TemplateContext): string {
427
428
  "",
428
429
  ...databaseLines,
429
430
  "",
430
- "BETTER_AUTH_SECRET=local-dev-better-auth-secret-change-me",
431
+ "# Optional for local development; required in production.",
432
+ "# BETTER_AUTH_SECRET=replace-with-a-random-32-character-secret",
431
433
  "BETTER_AUTH_URL=http://localhost:3000",
432
434
  "# BETTER_AUTH_TRUSTED_ORIGINS=http://localhost:3000",
433
435
  "",
@@ -115,7 +115,7 @@ export const todos = mysqlTable(
115
115
  }),
116
116
  );
117
117
  `,
118
- drizzleTodoRepository: `import type { beignetServerOnly } from "@beignet/core/server-only";
118
+ drizzleTodoRepository: `import "@beignet/core/server-only";
119
119
  import { offsetPageResult } from "@beignet/core/pagination";
120
120
  import type { DrizzleMysqlDatabase } from "@beignet/provider-db-drizzle/mysql";
121
121
  import { count, desc, eq } from "drizzle-orm";
@@ -255,7 +255,7 @@ async function prepare(pool: Pool): Promise<void> {
255
255
  await migrate(drizzle(pool), { migrationsFolder: "drizzle" });
256
256
  }
257
257
  `,
258
- dbProvider: `import type { beignetServerOnly } from "@beignet/core/server-only";
258
+ dbProvider: `import "@beignet/core/server-only";
259
259
  import { createProvider } from "@beignet/core/providers";
260
260
  import {
261
261
  createDrizzleMysqlIdempotencyPort,
@@ -105,7 +105,7 @@ export const todos = pgTable(
105
105
  }),
106
106
  );
107
107
  `,
108
- drizzleTodoRepository: `import type { beignetServerOnly } from "@beignet/core/server-only";
108
+ drizzleTodoRepository: `import "@beignet/core/server-only";
109
109
  import { offsetPageResult } from "@beignet/core/pagination";
110
110
  import type { DrizzlePostgresDatabase } from "@beignet/provider-db-drizzle/postgres";
111
111
  import { count, desc, eq } from "drizzle-orm";
@@ -243,7 +243,7 @@ async function prepare(pool: Pool): Promise<void> {
243
243
  await migrate(drizzle(pool), { migrationsFolder: "drizzle" });
244
244
  }
245
245
  `,
246
- dbProvider: `import type { beignetServerOnly } from "@beignet/core/server-only";
246
+ dbProvider: `import "@beignet/core/server-only";
247
247
  import { createProvider } from "@beignet/core/providers";
248
248
  import {
249
249
  createDrizzlePostgresIdempotencyPort,
@@ -101,7 +101,7 @@ export const todos = sqliteTable(
101
101
  }),
102
102
  );
103
103
  `,
104
- drizzleTodoRepository: `import type { beignetServerOnly } from "@beignet/core/server-only";
104
+ drizzleTodoRepository: `import "@beignet/core/server-only";
105
105
  import { offsetPageResult } from "@beignet/core/pagination";
106
106
  import type { DrizzleSqliteDatabase } from "@beignet/provider-db-drizzle/sqlite";
107
107
  import { count, desc, eq } from "drizzle-orm";
@@ -242,7 +242,7 @@ async function prepare(client: Client): Promise<void> {
242
242
  await migrate(drizzle(client), { migrationsFolder: "drizzle" });
243
243
  }
244
244
  `,
245
- dbProvider: `import type { beignetServerOnly } from "@beignet/core/server-only";
245
+ dbProvider: `import "@beignet/core/server-only";
246
246
  import { createProvider } from "@beignet/core/providers";
247
247
  import {
248
248
  createDrizzleSqliteIdempotencyPort,
@@ -132,6 +132,7 @@ export function getTemplateFiles(ctx: TemplateContext): TemplateFile[] {
132
132
  { path: "lib/env.ts", content: env(ctx) },
133
133
  { path: "lib/auth.ts", content: serverFiles.authHelpers },
134
134
  { path: "lib/better-auth.ts", content: betterAuth(ctx) },
135
+ { path: "lib/tenant.ts", content: serverFiles.tenantHelper },
135
136
  { path: "lib/use-case.ts", content: serverFiles.useCaseBuilder },
136
137
  { path: "server/index.ts", content: server(ctx) },
137
138
  { path: "server/context.ts", content: serverFiles.serverContext },
@@ -8,7 +8,7 @@ import {
8
8
  export function serverProviders(ctx: TemplateContext): string {
9
9
  const db = databaseDescriptor(ctx);
10
10
  const imports = [
11
- 'import type { beignetServerOnly } from "@beignet/core/server-only";',
11
+ 'import "@beignet/core/server-only";',
12
12
  'import { createDevtoolsProvider } from "@beignet/devtools";',
13
13
  'import { createAuthBetterAuthProvider } from "@beignet/provider-auth-better-auth";',
14
14
  `import { ${db.providerFactory} } from "@beignet/provider-db-drizzle/${db.subpath}";`,
@@ -155,6 +155,19 @@ export function env(ctx: TemplateContext): string {
155
155
  import { z } from "zod";
156
156
 
157
157
  const BooleanEnv = z.enum(["true", "false"]).transform((value) => value === "true");
158
+ const LOCAL_BETTER_AUTH_SECRET = "local-dev-better-auth-secret-change-me";
159
+ const isProductionRuntime =
160
+ process.env.NODE_ENV === "production" &&
161
+ process.env.NEXT_PHASE !== "phase-production-build";
162
+ const BetterAuthSecret = z.string().min(32).superRefine((value, ctx) => {
163
+ if (isProductionRuntime && value === LOCAL_BETTER_AUTH_SECRET) {
164
+ ctx.addIssue({
165
+ code: "custom",
166
+ message:
167
+ "BETTER_AUTH_SECRET must be unique in production. Generate a strong secret instead of using the local development placeholder.",
168
+ });
169
+ }
170
+ });
158
171
 
159
172
  export const env = createEnv({
160
173
  server: {
@@ -163,10 +176,9 @@ export const env = createEnv({
163
176
  .default("development"),
164
177
  APP_URL: z.string().url().default("http://localhost:3000"),
165
178
  DEVTOOLS_ENABLED: BooleanEnv.optional(),
166
- BETTER_AUTH_SECRET: z
167
- .string()
168
- .min(32)
169
- .default("local-dev-better-auth-secret-change-me"),
179
+ BETTER_AUTH_SECRET: isProductionRuntime
180
+ ? BetterAuthSecret
181
+ : BetterAuthSecret.default(LOCAL_BETTER_AUTH_SECRET),
170
182
  BETTER_AUTH_URL: z.string().url().default("http://localhost:3000"),
171
183
  BETTER_AUTH_TRUSTED_ORIGINS: z.string().optional(),
172
184
  ${databaseEnvLines}
@@ -283,7 +295,7 @@ export function server(ctx: TemplateContext): string {
283
295
  \t\t\tDB_URL: env.${db.urlEnvVar},
284
296
  \t\t},`;
285
297
 
286
- return `import type { beignetServerOnly } from "@beignet/core/server-only";
298
+ return `import "@beignet/core/server-only";
287
299
  import { createNextServer } from "@beignet/next";
288
300
  import { createErrorReportingHooks, createIdempotencyHooks } from "@beignet/core/server";
289
301
  import type { AppContext } from "@/app-context";
@@ -369,7 +381,7 @@ export const errors = defineErrors({
369
381
 
370
382
  export const appError = createAppError(errors);
371
383
  `,
372
- useCaseBuilder: `import type { beignetServerOnly } from "@beignet/core/server-only";
384
+ useCaseBuilder: `import "@beignet/core/server-only";
373
385
  import { createUseCase } from "@beignet/core/application";
374
386
  import type { AppContext } from "@/app-context";
375
387
 
@@ -403,10 +415,14 @@ export type AuthUser = {
403
415
  id: string;
404
416
  email?: string;
405
417
  name?: string;
418
+ tenantId?: string;
419
+ organizationId?: string;
406
420
  };
407
421
 
408
422
  export type AuthSessionMetadata = {
409
423
  id?: string;
424
+ tenantId?: string;
425
+ organizationId?: string;
410
426
  };
411
427
 
412
428
  export type AuthSession = BeignetAuthSession<
@@ -420,7 +436,7 @@ export type AuthPort = BeignetAuthPort<
420
436
  AuthRequest
421
437
  >;
422
438
  `,
423
- serverRoutes: `import type { beignetServerOnly } from "@beignet/core/server-only";
439
+ serverRoutes: `import "@beignet/core/server-only";
424
440
  import { contractsFromRoutes, defineRoutes } from "@beignet/next";
425
441
  import type { AppContext } from "@/app-context";
426
442
  import { todoRoutes } from "@/features/todos/routes";
@@ -430,17 +446,17 @@ export const routes = defineRoutes<AppContext>([
430
446
  ]);
431
447
  export const contracts = contractsFromRoutes(routes);
432
448
  `,
433
- serverContext: `import type { beignetServerOnly } from "@beignet/core/server-only";
449
+ serverContext: `import "@beignet/core/server-only";
434
450
  import {
435
451
  type ActivityActor,
436
452
  createAnonymousActor,
437
453
  createServiceActor,
438
- createTenant,
439
454
  createUserActor,
440
455
  } from "@beignet/core/ports";
441
456
  import { defineServerContext } from "@beignet/core/server";
442
457
  import type { TraceContext } from "@beignet/core/tracing";
443
458
  import type { AppContext, AppRuntimePorts } from "@/app-context";
459
+ import { resolveRequestTenant, resolveServiceTenant } from "@/lib/tenant";
444
460
 
445
461
  export type AppServiceContextInput =
446
462
  | {
@@ -456,7 +472,7 @@ export const appContext = defineServerContext<AppContext, AppRuntimePorts>()({
456
472
  gate: (ports) => ports.gate,
457
473
  request: async ({ req, ports, requestId, trace }) => {
458
474
  const auth = await ports.auth.getSession(req);
459
- const tenantId = req.headers.get("x-tenant-id") || undefined;
475
+ const tenant = resolveRequestTenant({ auth });
460
476
 
461
477
  return {
462
478
  requestId,
@@ -466,7 +482,7 @@ export const appContext = defineServerContext<AppContext, AppRuntimePorts>()({
466
482
  auth,
467
483
  ...trace,
468
484
  ports,
469
- tenant: tenantId ? createTenant(tenantId) : undefined,
485
+ ...(tenant ? { tenant } : {}),
470
486
  };
471
487
  },
472
488
  service: ({
@@ -479,15 +495,67 @@ export const appContext = defineServerContext<AppContext, AppRuntimePorts>()({
479
495
  input: AppServiceContextInput;
480
496
  requestId: string;
481
497
  trace: TraceContext;
482
- }) => ({
483
- requestId,
484
- actor: input?.actor ?? createServiceActor("beignet-service"),
485
- auth: null,
486
- ...trace,
487
- ports,
488
- tenant: createTenant(input?.tenantId ?? "tenant_default"),
489
- }),
498
+ }) => {
499
+ const tenant = resolveServiceTenant(input?.tenantId);
500
+
501
+ return {
502
+ requestId,
503
+ actor: input?.actor ?? createServiceActor("beignet-service"),
504
+ auth: null,
505
+ ...trace,
506
+ ports,
507
+ ...(tenant ? { tenant } : {}),
508
+ };
509
+ },
490
510
  });
511
+ `,
512
+ tenantHelper: `import { type ActivityTenant, createTenant } from "@beignet/core/ports";
513
+ import type { AuthSession } from "@/ports/auth";
514
+
515
+ export type TenantResolutionInput = {
516
+ auth: AuthSession | null;
517
+ };
518
+
519
+ export function resolveRequestTenant({
520
+ auth,
521
+ }: TenantResolutionInput): ActivityTenant | undefined {
522
+ const tenantId = auth ? tenantIdFromAuth(auth) : undefined;
523
+
524
+ return tenantId ? createTenant(tenantId) : undefined;
525
+ }
526
+
527
+ export function resolveServiceTenant(
528
+ tenantId: string | undefined,
529
+ ): ActivityTenant | undefined {
530
+ const normalizedTenantId = normalizeTenantId(tenantId);
531
+
532
+ return normalizedTenantId ? createTenant(normalizedTenantId) : undefined;
533
+ }
534
+
535
+ function tenantIdFromAuth(auth: AuthSession) {
536
+ return (
537
+ stringProperty(auth.session, "tenantId") ??
538
+ stringProperty(auth.session, "organizationId") ??
539
+ stringProperty(auth.user, "tenantId") ??
540
+ stringProperty(auth.user, "organizationId")
541
+ );
542
+ }
543
+
544
+ function stringProperty(value: unknown, key: string) {
545
+ if (!isRecord(value)) return undefined;
546
+
547
+ return normalizeTenantId(value[key]);
548
+ }
549
+
550
+ function normalizeTenantId(value: unknown) {
551
+ return typeof value === "string" && value.trim().length > 0
552
+ ? value.trim()
553
+ : undefined;
554
+ }
555
+
556
+ function isRecord(value: unknown): value is Record<string, unknown> {
557
+ return typeof value === "object" && value !== null;
558
+ }
491
559
  `,
492
560
  apiCatchAllRoute: `import { server } from "@/server";
493
561
 
@@ -553,11 +621,13 @@ export const { GET, POST } = createDevtoolsRoute(server.ports.devtools, {
553
621
  });
554
622
  `,
555
623
  openApiRoute: `import { createOpenAPIHandler } from "@beignet/next";
624
+ import { env } from "@/lib/env";
556
625
  import { server } from "@/server";
557
626
 
558
627
  export const GET = createOpenAPIHandler(server.contracts, {
559
628
  title: "Beignet starter API",
560
629
  version: "0.1.0",
630
+ servers: [{ url: env.APP_URL }],
561
631
  });
562
632
  `,
563
633
  authRoute: `import { toNextJsHandler } from "better-auth/next-js";
@@ -1035,12 +1035,41 @@ export function SettingsNav() {
1035
1035
  }
1036
1036
  `;
1037
1037
 
1038
+ const todoClientQueries = `import type { QueryClient } from "@tanstack/react-query";
1039
+ import { rq } from "@/client";
1040
+ import {
1041
+ createTodo,
1042
+ deleteTodo,
1043
+ listTodos,
1044
+ updateTodo,
1045
+ } from "@/features/todos/contracts";
1046
+
1047
+ export function listTodosQueryOptions() {
1048
+ return rq(listTodos).queryOptions();
1049
+ }
1050
+
1051
+ export function createTodoMutationOptions() {
1052
+ return rq(createTodo).mutationOptions();
1053
+ }
1054
+
1055
+ export function updateTodoMutationOptions() {
1056
+ return rq(updateTodo).mutationOptions();
1057
+ }
1058
+
1059
+ export function deleteTodoMutationOptions() {
1060
+ return rq(deleteTodo).mutationOptions();
1061
+ }
1062
+
1063
+ export function invalidateTodos(queryClient: QueryClient) {
1064
+ return rq(listTodos).invalidate(queryClient);
1065
+ }
1066
+ `;
1067
+
1038
1068
  const todoApp = `"use client";
1039
1069
 
1040
1070
  import { rootFormError } from "@beignet/react-hook-form";
1041
1071
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
1042
1072
  import { Trash2Icon } from "lucide-react";
1043
- import { rq } from "@/client";
1044
1073
  import { rhf } from "@/client/forms";
1045
1074
  import { Button } from "@/components/ui/button";
1046
1075
  import { Card, CardContent } from "@/components/ui/card";
@@ -1048,45 +1077,44 @@ import { Checkbox } from "@/components/ui/checkbox";
1048
1077
  import { Input } from "@/components/ui/input";
1049
1078
  import { Label } from "@/components/ui/label";
1050
1079
  import {
1051
- createTodo,
1052
- deleteTodo,
1053
- listTodos,
1054
- updateTodo,
1055
- } from "@/features/todos/contracts";
1080
+ createTodoMutationOptions,
1081
+ deleteTodoMutationOptions,
1082
+ invalidateTodos,
1083
+ listTodosQueryOptions,
1084
+ updateTodoMutationOptions,
1085
+ } from "@/features/todos/client/queries";
1086
+ import { createTodo } from "@/features/todos/contracts";
1056
1087
  import { cn } from "@/lib/utils";
1057
1088
 
1058
1089
  const createTodoForm = rhf(createTodo);
1059
1090
 
1060
1091
  export function TodoApp() {
1061
1092
  const queryClient = useQueryClient();
1062
- const todosQuery = useQuery(rq(listTodos).queryOptions());
1093
+ const todosQuery = useQuery(listTodosQueryOptions());
1063
1094
  const form = createTodoForm.useForm({
1064
1095
  defaultValues: { title: "" },
1065
1096
  });
1066
1097
 
1067
- const invalidateTodos = () => rq(listTodos).invalidate(queryClient);
1098
+ const invalidateTodoQueries = () => invalidateTodos(queryClient);
1068
1099
 
1069
- const createTodoMutation = useMutation(
1070
- rq(createTodo).mutationOptions({
1071
- onSuccess: async () => {
1072
- form.reset();
1073
- await invalidateTodos();
1074
- },
1075
- onError: (error) => {
1076
- form.setError("root", rootFormError(error, "Could not create the todo."));
1077
- },
1078
- }),
1079
- );
1080
- const updateTodoMutation = useMutation(
1081
- rq(updateTodo).mutationOptions({
1082
- onSuccess: invalidateTodos,
1083
- }),
1084
- );
1085
- const deleteTodoMutation = useMutation(
1086
- rq(deleteTodo).mutationOptions({
1087
- onSuccess: invalidateTodos,
1088
- }),
1089
- );
1100
+ const createTodoMutation = useMutation({
1101
+ ...createTodoMutationOptions(),
1102
+ onSuccess: async () => {
1103
+ form.reset();
1104
+ await invalidateTodoQueries();
1105
+ },
1106
+ onError: (error) => {
1107
+ form.setError("root", rootFormError(error, "Could not create the todo."));
1108
+ },
1109
+ });
1110
+ const updateTodoMutation = useMutation({
1111
+ ...updateTodoMutationOptions(),
1112
+ onSuccess: invalidateTodoQueries,
1113
+ });
1114
+ const deleteTodoMutation = useMutation({
1115
+ ...deleteTodoMutationOptions(),
1116
+ onSuccess: invalidateTodoQueries,
1117
+ });
1090
1118
 
1091
1119
  const onSubmit = form.handleSubmit((body) => {
1092
1120
  form.clearErrors("root");
@@ -1215,6 +1243,10 @@ export function shellTemplateFiles(ctx: TemplateContext): TemplateFile[] {
1215
1243
  { path: "components/theme-toggle.tsx", content: themeToggle },
1216
1244
  { path: "components/app-sidebar.tsx", content: appSidebar },
1217
1245
  { path: "components/settings-nav.tsx", content: settingsNav },
1246
+ {
1247
+ path: "features/todos/client/queries.ts",
1248
+ content: todoClientQueries,
1249
+ },
1218
1250
  {
1219
1251
  path: "features/todos/components/todo-app.tsx",
1220
1252
  content: todoApp,
@@ -174,7 +174,7 @@ export const todoPolicy = definePolicy({
174
174
  authorizeOwner(ctx, todo, "delete"),
175
175
  });
176
176
  `,
177
- routes: `import type { beignetServerOnly } from "@beignet/core/server-only";
177
+ routes: `import "@beignet/core/server-only";
178
178
  import { defineRouteGroup } from "@beignet/next";
179
179
  import type { AppContext } from "@/app-context";
180
180
  import {
@@ -200,7 +200,7 @@ export const todoRoutes = defineRouteGroup<AppContext>()({
200
200
  ],
201
201
  });
202
202
  `,
203
- listTodosUseCase: `import type { beignetServerOnly } from "@beignet/core/server-only";
203
+ listTodosUseCase: `import "@beignet/core/server-only";
204
204
  import { normalizeOffsetPage } from "@beignet/core/pagination";
205
205
  import { requireUser } from "@/lib/auth";
206
206
  import { useCase } from "@/lib/use-case";
@@ -220,7 +220,7 @@ export const listTodosUseCase = useCase
220
220
  return ctx.ports.todos.list(user.id, page);
221
221
  });
222
222
  `,
223
- createTodoUseCase: `import type { beignetServerOnly } from "@beignet/core/server-only";
223
+ createTodoUseCase: `import "@beignet/core/server-only";
224
224
  import { requireUser } from "@/lib/auth";
225
225
  import { useCase } from "@/lib/use-case";
226
226
  import { CreateTodoInputSchema, TodoSchema } from "../schemas";
@@ -238,7 +238,7 @@ export const createTodoUseCase = useCase
238
238
  );
239
239
  });
240
240
  `,
241
- updateTodoUseCase: `import type { beignetServerOnly } from "@beignet/core/server-only";
241
+ updateTodoUseCase: `import "@beignet/core/server-only";
242
242
  import { appError } from "@/features/shared/errors";
243
243
  import { requireUser } from "@/lib/auth";
244
244
  import { useCase } from "@/lib/use-case";
@@ -263,7 +263,7 @@ export const updateTodoUseCase = useCase
263
263
  });
264
264
  });
265
265
  `,
266
- deleteTodoUseCase: `import type { beignetServerOnly } from "@beignet/core/server-only";
266
+ deleteTodoUseCase: `import "@beignet/core/server-only";
267
267
  import { appError } from "@/features/shared/errors";
268
268
  import { requireUser } from "@/lib/auth";
269
269
  import { useCase } from "@/lib/use-case";