create-m5kdev 0.33.0 → 0.35.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 (147) hide show
  1. package/dist/src/catalog.cjs +21 -1
  2. package/dist/src/catalog.cjs.map +1 -1
  3. package/dist/src/catalog.d.cts +3 -1
  4. package/dist/src/create.cjs +6 -2
  5. package/dist/src/create.cjs.map +1 -1
  6. package/dist/src/doctor.cjs.map +1 -1
  7. package/dist/src/fs.cjs +10 -5
  8. package/dist/src/fs.cjs.map +1 -1
  9. package/dist/src/prompts.cjs +32 -0
  10. package/dist/src/prompts.cjs.map +1 -1
  11. package/dist/src/prompts.d.cts +7 -1
  12. package/dist/src/template.cjs +23 -4
  13. package/dist/src/template.cjs.map +1 -1
  14. package/dist/src/template.d.cts +17 -3
  15. package/dist/src/types.d.cts +11 -4
  16. package/package.json +4 -1
  17. package/templates/minimal-app/.cursor/rules/frontend-data-guide.mdc +1 -0
  18. package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +1 -1
  19. package/templates/minimal-app/.cursor/rules/module-grants-guide.mdc +3 -3
  20. package/templates/minimal-app/.cursor/rules/module-module-guide.mdc +3 -2
  21. package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +1 -1
  22. package/templates/minimal-app/.cursor/rules/server-event-emit.mdc +41 -0
  23. package/templates/minimal-app/.dockerignore.tpl +31 -0
  24. package/templates/minimal-app/.gitignore.tpl +1 -0
  25. package/templates/minimal-app/AGENTS.md.tpl +14 -10
  26. package/templates/minimal-app/CLAUDE.md.tpl +133 -131
  27. package/templates/minimal-app/README.md.tpl +51 -19
  28. package/templates/minimal-app/apps/e2e/package.json.tpl +23 -23
  29. package/templates/minimal-app/apps/e2e/playwright.config.ts.tpl +51 -39
  30. package/templates/minimal-app/apps/e2e/playwright.expo.config.ts.tpl +38 -38
  31. package/templates/minimal-app/apps/e2e/servers.ts.tpl +136 -112
  32. package/templates/minimal-app/apps/e2e/tests/conversation.spec.ts.tpl +22 -0
  33. package/templates/minimal-app/apps/e2e/tests/email.spec.ts.tpl +47 -47
  34. package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +73 -73
  35. package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +517 -517
  36. package/templates/minimal-app/apps/e2e/tests/expo-waitlist.spec.ts.tpl +94 -94
  37. package/templates/minimal-app/apps/e2e/tests/files.spec.ts.tpl +35 -0
  38. package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +289 -289
  39. package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +516 -516
  40. package/templates/minimal-app/apps/e2e/tests/waitlist.spec.ts.tpl +107 -107
  41. package/templates/minimal-app/apps/e2e/tests/workflow.spec.ts.tpl +26 -0
  42. package/templates/minimal-app/apps/e2e/tsconfig.json.tpl +10 -10
  43. package/templates/minimal-app/apps/email/package.json.tpl +34 -34
  44. package/templates/minimal-app/apps/email/src/emails/trialEndingEmail.tsx.tpl +40 -0
  45. package/templates/minimal-app/apps/email/src/index.ts.tpl +7 -0
  46. package/templates/minimal-app/apps/email/src/resources.ts.tpl +72 -65
  47. package/templates/minimal-app/apps/expo/app.json.tpl +36 -36
  48. package/templates/minimal-app/apps/expo/eslint.config.js.tpl +8 -8
  49. package/templates/minimal-app/apps/expo/metro.config.js.tpl +16 -16
  50. package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +31 -31
  51. package/templates/minimal-app/apps/expo/src/app/claim-account.tsx.tpl +151 -151
  52. package/templates/minimal-app/apps/expo/src/app/forgot-password.tsx.tpl +53 -53
  53. package/templates/minimal-app/apps/expo/src/app/index.tsx.tpl +8 -8
  54. package/templates/minimal-app/apps/expo/src/app/login.tsx.tpl +71 -71
  55. package/templates/minimal-app/apps/expo/src/app/logout.tsx.tpl +19 -19
  56. package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +85 -85
  57. package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +40 -40
  58. package/templates/minimal-app/apps/expo/src/app/reset-password.tsx.tpl +75 -75
  59. package/templates/minimal-app/apps/expo/src/app/signup.tsx.tpl +193 -193
  60. package/templates/minimal-app/apps/expo/src/components/AuthScaffold.tsx.tpl +34 -34
  61. package/templates/minimal-app/apps/expo/src/components/FormTextField.tsx.tpl +37 -37
  62. package/templates/minimal-app/apps/expo/src/components/LoadingScreen.tsx.tpl +10 -10
  63. package/templates/minimal-app/apps/expo/src/components/StatusText.tsx.tpl +22 -22
  64. package/templates/minimal-app/apps/expo/src/config.ts.tpl +9 -9
  65. package/templates/minimal-app/apps/expo/src/global.css.tpl +11 -11
  66. package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +20 -20
  67. package/templates/minimal-app/apps/expo/tsconfig.json.tpl +11 -11
  68. package/templates/minimal-app/apps/expo/uniwind-env.d.ts.tpl +1 -1
  69. package/templates/minimal-app/apps/landing/.env.production.example.tpl +7 -0
  70. package/templates/minimal-app/apps/landing/Dockerfile.tpl +39 -0
  71. package/templates/minimal-app/apps/landing/fly.toml.tpl +20 -0
  72. package/templates/minimal-app/apps/landing/index.html.tpl +35 -0
  73. package/templates/minimal-app/apps/landing/package.json.tpl +34 -0
  74. package/templates/minimal-app/apps/landing/src/App.tsx.tpl +12 -0
  75. package/templates/minimal-app/apps/landing/src/LandingPage.tsx.tpl +37 -0
  76. package/templates/minimal-app/apps/landing/src/index.css.tpl +24 -0
  77. package/templates/minimal-app/apps/landing/src/main.tsx.tpl +16 -0
  78. package/templates/minimal-app/apps/landing/src/vite-env.d.ts.tpl +11 -0
  79. package/templates/minimal-app/apps/landing/tsconfig.json.tpl +12 -0
  80. package/templates/minimal-app/apps/landing/vite.config.ts.tpl +25 -0
  81. package/templates/minimal-app/apps/server/AGENTS.md.tpl +55 -46
  82. package/templates/minimal-app/apps/server/{drizzle/seed.e2e.ts.tpl → db.e2e.ts.tpl} +57 -40
  83. package/templates/minimal-app/apps/server/{drizzle/seed.ts.tpl → db.ts.tpl} +27 -20
  84. package/templates/minimal-app/apps/server/drizzle/0003_silly_the_liberteens.sql +27 -0
  85. package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +56 -166
  86. package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +1796 -1910
  87. package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +1818 -1932
  88. package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +2137 -0
  89. package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +8 -1
  90. package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +3 -33
  91. package/templates/minimal-app/apps/server/package.json.tpl +5 -3
  92. package/templates/minimal-app/apps/server/src/app.ts.tpl +36 -0
  93. package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl +26 -0
  94. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.module.ts.tpl +51 -0
  95. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.service.ts.tpl +51 -0
  96. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.trpc.ts.tpl +16 -0
  97. package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +28 -28
  98. package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +28 -28
  99. package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +57 -54
  100. package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +67 -67
  101. package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +116 -116
  102. package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +37 -37
  103. package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl +279 -279
  104. package/templates/minimal-app/apps/server/src/schema.ts.tpl +31 -20
  105. package/templates/minimal-app/apps/server/src/types.ts.tpl +12 -12
  106. package/templates/minimal-app/apps/server/tsconfig.json.tpl +1 -1
  107. package/templates/minimal-app/apps/server/tsup.config.ts.tpl +23 -9
  108. package/templates/minimal-app/apps/shared/.env.example.tpl +5 -0
  109. package/templates/minimal-app/apps/shared/.env.production.example.tpl +23 -0
  110. package/templates/minimal-app/apps/shared/.env.tpl +5 -0
  111. package/templates/minimal-app/apps/shared/Dockerfile.tpl +48 -0
  112. package/templates/minimal-app/apps/shared/fly.toml.tpl +24 -0
  113. package/templates/minimal-app/apps/shared/package.json.tpl +26 -24
  114. package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +28 -28
  115. package/templates/minimal-app/apps/shared/src/modules/conversation/conversation.constants.ts.tpl +1 -0
  116. package/templates/minimal-app/apps/shared/src/modules/demo-workflow/demo-workflow.constants.ts.tpl +1 -0
  117. package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +3 -3
  118. package/templates/minimal-app/apps/shared/src/modules/posts/posts.constants.ts.tpl +5 -5
  119. package/templates/minimal-app/apps/shared/src/modules/posts/posts.schema.ts.tpl +25 -25
  120. package/templates/minimal-app/apps/webapp/package.json.tpl +53 -53
  121. package/templates/minimal-app/apps/webapp/public/push-sw.js.tpl +34 -34
  122. package/templates/minimal-app/apps/webapp/src/App.tsx.tpl +49 -44
  123. package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +96 -65
  124. package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +114 -93
  125. package/templates/minimal-app/apps/webapp/src/StarterServerEventProvider.tsx.tpl +38 -0
  126. package/templates/minimal-app/apps/webapp/src/index.css.tpl +15 -15
  127. package/templates/minimal-app/apps/webapp/src/main.tsx.tpl +26 -26
  128. package/templates/minimal-app/apps/webapp/src/modules/conversation/ConversationRoute.tsx.tpl +36 -0
  129. package/templates/minimal-app/apps/webapp/src/modules/files/FilesRoute.tsx.tpl +88 -0
  130. package/templates/minimal-app/apps/webapp/src/modules/files/hooks/useFilesRoute.ts.tpl +50 -0
  131. package/templates/minimal-app/apps/webapp/src/modules/workflows/WorkflowsRoute.tsx.tpl +97 -0
  132. package/templates/minimal-app/apps/webapp/src/modules/workflows/hooks/useWorkflowsRoute.ts.tpl +64 -0
  133. package/templates/minimal-app/apps/webapp/src/utils/i18n.ts.tpl +13 -13
  134. package/templates/minimal-app/apps/webapp/src/utils/trpc.ts.tpl +10 -10
  135. package/templates/minimal-app/apps/webapp/src/vite-env.d.ts.tpl +1 -1
  136. package/templates/minimal-app/apps/webapp/translations/en/app.json.tpl +7 -7
  137. package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +193 -135
  138. package/templates/minimal-app/apps/webapp/tsconfig.json.tpl +17 -17
  139. package/templates/minimal-app/apps/webapp/vite.config.ts.tpl +31 -31
  140. package/templates/minimal-app/biome.json.tpl +17 -12
  141. package/templates/minimal-app/package.json.tpl +7 -2
  142. package/templates/minimal-app/pnpm-workspace.yaml.tpl +14 -7
  143. package/templates/minimal-app/template.manifest.json +93 -2
  144. package/templates/minimal-app/apps/server/drizzle/db.ts.tpl +0 -26
  145. package/templates/minimal-app/apps/server/drizzle/guard.ts.tpl +0 -36
  146. package/templates/minimal-app/apps/server/drizzle/reset.ts.tpl +0 -40
  147. package/templates/minimal-app/apps/server/drizzle/sync.ts.tpl +0 -16
@@ -22,6 +22,13 @@
22
22
  "when": 1786951360347,
23
23
  "tag": "0002_concerned_proudstar",
24
24
  "breakpoints": true
25
+ },
26
+ {
27
+ "idx": 3,
28
+ "version": "6",
29
+ "when": 1787145865079,
30
+ "tag": "0003_silly_the_liberteens",
31
+ "breakpoints": true
25
32
  }
26
33
  ]
27
- }
34
+ }
@@ -1,33 +1,3 @@
1
- import * as dotenv from "dotenv";
2
- import { defineConfig } from "drizzle-kit";
3
-
4
- dotenv.config({ path: process.env.DRIZZLE_ENV_PATH || "../shared/.env" });
5
-
6
- const url = process.env.TURSO_DATABASE_URL || process.env.DATABASE_URL;
7
- const authToken = process.env.TURSO_AUTH_TOKEN;
8
- const schema = process.env.DRIZZLE_SCHEMA_PATH || "./src/schema.ts";
9
-
10
- if (!url) {
11
- throw new Error("DATABASE_URL or TURSO_DATABASE_URL must be set");
12
- }
13
-
14
- export default defineConfig(
15
- url && authToken
16
- ? {
17
- dialect: "turso",
18
- schema,
19
- out: "./drizzle",
20
- dbCredentials: {
21
- url,
22
- authToken,
23
- },
24
- }
25
- : {
26
- dialect: "sqlite",
27
- schema,
28
- out: "./drizzle",
29
- dbCredentials: {
30
- url,
31
- },
32
- }
33
- );
1
+ import { defineDrizzleKitConfig } from "@m5kdev/backend/db";
2
+
3
+ export default defineDrizzleKitConfig({ schema: "./src/schema.ts" });
@@ -13,9 +13,9 @@
13
13
  "drizzle:generate": "drizzle-kit generate",
14
14
  "drizzle:migrate": "drizzle-kit migrate",
15
15
  "drizzle:studio": "drizzle-kit studio",
16
- "drizzle:sync": "tsx --env-file=../shared/.env drizzle/sync.ts",
17
- "drizzle:seed": "tsx --env-file=../shared/.env drizzle/seed.ts",
18
- "drizzle:reset": "tsx --env-file=../shared/.env drizzle/reset.ts"
16
+ "drizzle:sync": "tsx --env-file=../shared/.env db.ts sync",
17
+ "drizzle:seed": "tsx --env-file=../shared/.env db.ts seed",
18
+ "drizzle:reset": "tsx --env-file=../shared/.env db.ts reset"
19
19
  },
20
20
  "dependencies": {
21
21
  "{{PACKAGE_SCOPE}}/email": "workspace:*",
@@ -23,6 +23,8 @@
23
23
  "@libsql/client": "catalog:m5kdev",
24
24
  "@m5kdev/backend": "catalog:m5kdev",
25
25
  "@m5kdev/commons": "catalog:m5kdev",
26
+ "@mastra/core": "catalog:m5kdev",
27
+ "@openrouter/ai-sdk-provider": "catalog:m5kdev",
26
28
  "@opentelemetry/api": "catalog:m5kdev",
27
29
  "@trpc/server": "catalog:m5kdev",
28
30
  "better-auth": "catalog:m5kdev",
@@ -1,10 +1,21 @@
1
1
  import { createBackendApp } from "@m5kdev/backend/app";
2
2
  import { shutdownTelemetry } from "@m5kdev/backend/lib/otel";
3
+ // m5k:ai:start
4
+ import { AIModule } from "@m5kdev/backend/modules/ai/ai.module";
5
+ // m5k:ai:end
3
6
  import { createBetterAuth } from "@m5kdev/backend/modules/auth/auth.lib";
4
7
  import { AuthModule } from "@m5kdev/backend/modules/auth/auth.module";
5
8
  import { EmailModule } from "@m5kdev/backend/modules/email/email.module";
6
9
  import { EmailPreviewModule } from "@m5kdev/backend/modules/email/email.preview.module";
10
+ // m5k:files:start
11
+ import { FileModule } from "@m5kdev/backend/modules/file/file.module";
12
+ // m5k:files:end
13
+ // m5k:notifications:start
14
+ import { NotificationModule } from "@m5kdev/backend/modules/notification/notification.module";
15
+ // m5k:notifications:end
16
+ // m5k:workflows:start
7
17
  import { WorkflowModule } from "@m5kdev/backend/modules/workflow/workflow.module";
18
+ // m5k:workflows:end
8
19
  import { templates } from "{{PACKAGE_SCOPE}}/email";
9
20
  import { emailResources } from "{{PACKAGE_SCOPE}}/email/resources";
10
21
  import {
@@ -12,6 +23,12 @@ import {
12
23
  APP_NAME,
13
24
  APP_ROLES_CONFIG,
14
25
  } from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
26
+ // m5k:ai:start
27
+ import { mastra, openrouter } from "./modules/conversation/conversation.mastra";
28
+ // m5k:ai:end
29
+ // m5k:workflows:start
30
+ import { DemoWorkflowModule } from "./modules/demo-workflow/demo-workflow.module";
31
+ // m5k:workflows:end
15
32
  import { PostsModule } from "./modules/posts/posts.module";
16
33
  // m5k:test-harness:start
17
34
  import { TestHarnessModule } from "./modules/test-harness/test-harness.module";
@@ -23,7 +40,9 @@ const serverUrl = process.env.VITE_SERVER_URL ?? "http://localhost:8080";
23
40
  const databaseUrl = process.env.DATABASE_URL ?? "file:./local.db";
24
41
  const syncUrl = process.env.TURSO_DATABASE_URL;
25
42
  const authToken = process.env.TURSO_AUTH_TOKEN;
43
+ // m5k:workflows:start
26
44
  const redisUrl = process.env.REDIS_URL ?? "redis://127.0.0.1:6379";
45
+ // m5k:workflows:end
27
46
  const resendApiKey = process.env.RESEND_API_KEY;
28
47
  const enableWaitlist = process.env.VITE_ENABLE_WAITLIST === "true";
29
48
 
@@ -57,10 +76,13 @@ export const builtBackendApp = createBackendApp(
57
76
  i18n: {
58
77
  resources: emailResources,
59
78
  },
79
+ spa: { root: "./client" },
80
+ // m5k:workflows:start
60
81
  redis: {
61
82
  url: redisUrl,
62
83
  options: { maxRetriesPerRequest: null },
63
84
  },
85
+ // m5k:workflows:end
64
86
  resend: resendApiKey ? { apiKey: resendApiKey } : undefined,
65
87
  email: {
66
88
  mode: resendApiKey ? "send" : "store",
@@ -93,6 +115,15 @@ export const builtBackendApp = createBackendApp(
93
115
  [
94
116
  new EmailModule(templates as never),
95
117
  new AuthModule(),
118
+ // m5k:ai:start
119
+ new AIModule({
120
+ libs: { mastra, openrouter },
121
+ }),
122
+ // m5k:ai:end
123
+ // m5k:files:start
124
+ new FileModule(),
125
+ // m5k:files:end
126
+ // m5k:workflows:start
96
127
  new WorkflowModule({
97
128
  queues: {
98
129
  fast: { concurrency: 5 },
@@ -104,6 +135,11 @@ export const builtBackendApp = createBackendApp(
104
135
  },
105
136
  reconcile: { enabled: true },
106
137
  }),
138
+ new DemoWorkflowModule(),
139
+ // m5k:workflows:end
140
+ // m5k:notifications:start
141
+ new NotificationModule(),
142
+ // m5k:notifications:end
107
143
  new PostsModule(),
108
144
  new EmailPreviewModule({ allowDelete: process.env.NODE_ENV !== "production" }),
109
145
  // m5k:test-harness:start
@@ -0,0 +1,26 @@
1
+ import { Mastra } from "@mastra/core";
2
+ import { Agent } from "@mastra/core/agent";
3
+ import { createOpenRouter } from "@openrouter/ai-sdk-provider";
4
+ import { STARTER_ASSISTANT_AGENT_ID } from "{{PACKAGE_SCOPE}}/shared/modules/conversation/conversation.constants";
5
+
6
+ /** Cheap OpenRouter chat model for the starter Conversation example. */
7
+ const STARTER_CHAT_MODEL = "google/gemini-2.5-flash";
8
+
9
+ export const openrouter = createOpenRouter({
10
+ apiKey: process.env.OPENROUTER_API_KEY ?? "",
11
+ });
12
+
13
+ const assistant = new Agent({
14
+ id: STARTER_ASSISTANT_AGENT_ID,
15
+ name: "Assistant",
16
+ instructions: "You are a helpful assistant for this starter app. Keep answers concise.",
17
+ model: openrouter.chat(STARTER_CHAT_MODEL, {
18
+ usage: { include: true },
19
+ }),
20
+ });
21
+
22
+ export const mastra = new Mastra({
23
+ agents: {
24
+ [STARTER_ASSISTANT_AGENT_ID]: assistant,
25
+ },
26
+ });
@@ -0,0 +1,51 @@
1
+ import { createBackendRouterMap } from "@m5kdev/backend/app";
2
+ import type { AuthModule } from "@m5kdev/backend/modules/auth/auth.module";
3
+ import {
4
+ BaseModule,
5
+ type ModuleServicesContext,
6
+ type ModuleTRPCContext,
7
+ type TableMap,
8
+ } from "@m5kdev/backend/modules/base/base.module";
9
+ import type { WorkflowModule } from "@m5kdev/backend/modules/workflow/workflow.module";
10
+ import { DemoWorkflowService } from "./demo-workflow.service";
11
+ import { createDemoWorkflowTRPC } from "./demo-workflow.trpc";
12
+
13
+ type DemoWorkflowModuleDeps = { workflow: WorkflowModule; auth: AuthModule };
14
+ type DemoWorkflowModuleServices = {
15
+ demoWorkflow: DemoWorkflowService;
16
+ };
17
+ type DemoWorkflowModuleRouters = {
18
+ demoWorkflow: ReturnType<typeof createDemoWorkflowTRPC>;
19
+ };
20
+
21
+ export class DemoWorkflowModule extends BaseModule<
22
+ DemoWorkflowModuleDeps,
23
+ TableMap,
24
+ Record<string, never>,
25
+ DemoWorkflowModuleServices,
26
+ DemoWorkflowModuleRouters
27
+ > {
28
+ readonly id = "demo-workflow";
29
+ override readonly dependsOn = ["workflow", "auth"] as const;
30
+
31
+ override services({
32
+ deps,
33
+ }: ModuleServicesContext<DemoWorkflowModuleDeps, Record<string, never>>) {
34
+ return {
35
+ demoWorkflow: new DemoWorkflowService({
36
+ workflow: deps.workflow.services.workflow,
37
+ auth: deps.auth.services.auth,
38
+ }),
39
+ };
40
+ }
41
+
42
+ override trpc({
43
+ trpc,
44
+ services,
45
+ }: ModuleTRPCContext<DemoWorkflowModuleDeps, DemoWorkflowModuleServices>) {
46
+ return createBackendRouterMap(
47
+ "demoWorkflow",
48
+ createDemoWorkflowTRPC(trpc, services.demoWorkflow)
49
+ );
50
+ }
51
+ }
@@ -0,0 +1,51 @@
1
+ import type { AuthService } from "@m5kdev/backend/modules/auth/auth.service";
2
+ import type { ServerResultAsync } from "@m5kdev/backend/modules/base/base.dto";
3
+ import { BaseService } from "@m5kdev/backend/modules/base/base.service";
4
+ import type { WorkflowService } from "@m5kdev/backend/modules/workflow/workflow.service";
5
+ import type { FireAndForgetJobDefinition } from "@m5kdev/backend/modules/workflow/workflow.types";
6
+ import type { Context } from "@m5kdev/backend/utils/trpc";
7
+ import { workflowTriggerOutputSchema } from "@m5kdev/commons/modules/workflow/workflow.schema";
8
+ import { DEMO_WORKFLOW_SERVER_EVENT_RESOURCE } from "{{PACKAGE_SCOPE}}/shared/modules/demo-workflow/demo-workflow.constants";
9
+ import { ok } from "neverthrow";
10
+
11
+ const DEMO_PING_JOB_NAME = "demo.ping";
12
+
13
+ interface DemoPingJobPayload {
14
+ readonly userId: string;
15
+ }
16
+
17
+ export class DemoWorkflowService extends BaseService<
18
+ Record<string, never>,
19
+ { workflow: WorkflowService; auth: AuthService },
20
+ Context
21
+ > {
22
+ readonly demoPingJob: FireAndForgetJobDefinition<DemoPingJobPayload>;
23
+
24
+ constructor(services: { workflow: WorkflowService; auth: AuthService }) {
25
+ super({}, services);
26
+
27
+ this.demoPingJob = this.service.workflow
28
+ .job<DemoPingJobPayload>({
29
+ name: DEMO_PING_JOB_NAME,
30
+ queue: "fast",
31
+ meta: (payload) => ({ userId: payload.userId }),
32
+ })
33
+ .handle(async (payload) => {
34
+ this.service.auth.userEmit({
35
+ userId: payload.userId,
36
+ resource: DEMO_WORKFLOW_SERVER_EVENT_RESOURCE,
37
+ id: DEMO_PING_JOB_NAME,
38
+ change: "updated",
39
+ organizationId: null,
40
+ });
41
+ });
42
+ }
43
+
44
+ readonly runDemo = this.procedure("runDemo")
45
+ .output(workflowTriggerOutputSchema)
46
+ .requireAuth()
47
+ .handle(async ({ ctx }): ServerResultAsync<{ jobId: string }> => {
48
+ const jobId = await this.demoPingJob.trigger({ userId: ctx.actor.userId });
49
+ return ok({ jobId });
50
+ });
51
+ }
@@ -0,0 +1,16 @@
1
+ import { handleTRPCResult, type TRPCMethods } from "@m5kdev/backend/utils/trpc";
2
+ import { workflowTriggerOutputSchema } from "@m5kdev/commons/modules/workflow/workflow.schema";
3
+ import type { DemoWorkflowService } from "./demo-workflow.service";
4
+
5
+ export function createDemoWorkflowTRPC(
6
+ { router, privateProcedure }: TRPCMethods,
7
+ demoWorkflowService: DemoWorkflowService
8
+ ) {
9
+ return router({
10
+ run: privateProcedure
11
+ .output(workflowTriggerOutputSchema)
12
+ .mutation(async ({ ctx }) =>
13
+ handleTRPCResult(await demoWorkflowService.runDemo(undefined, ctx))
14
+ ),
15
+ });
16
+ }
@@ -1,28 +1,28 @@
1
- import { members, organizations, teams, users } from "@m5kdev/backend/modules/auth/auth.db";
2
- import { integer, sqliteTable as table, text } from "drizzle-orm/sqlite-core";
3
- import { v4 as uuidv4 } from "uuid";
4
-
5
- export const posts = table("posts", {
6
- id: text("id").primaryKey().$default(uuidv4),
7
- authorUserId: text("author_user_id").references(() => users.id, {
8
- onDelete: "set null",
9
- }),
10
- memberId: text("member_id").references(() => members.id, {
11
- onDelete: "set null",
12
- }),
13
- organizationId: text("organization_id").references(() => organizations.id, {
14
- onDelete: "set null",
15
- }),
16
- teamId: text("team_id").references(() => teams.id, { onDelete: "set null" }),
17
- title: text("title").notNull(),
18
- slug: text("slug").notNull().unique(),
19
- excerpt: text("excerpt"),
20
- content: text("content").notNull(),
21
- status: text("status").$type<"draft" | "published">().notNull().default("draft"),
22
- publishedAt: integer("published_at", { mode: "timestamp" }),
23
- createdAt: integer("created_at", { mode: "timestamp" })
24
- .notNull()
25
- .$default(() => new Date()),
26
- updatedAt: integer("updated_at", { mode: "timestamp" }),
27
- deletedAt: integer("deleted_at", { mode: "timestamp" }),
28
- });
1
+ import { members, organizations, teams, users } from "@m5kdev/backend/modules/auth/auth.db";
2
+ import { integer, sqliteTable as table, text } from "drizzle-orm/sqlite-core";
3
+ import { v4 as uuidv4 } from "uuid";
4
+
5
+ export const posts = table("posts", {
6
+ id: text("id").primaryKey().$default(uuidv4),
7
+ authorUserId: text("author_user_id").references(() => users.id, {
8
+ onDelete: "set null",
9
+ }),
10
+ memberId: text("member_id").references(() => members.id, {
11
+ onDelete: "set null",
12
+ }),
13
+ organizationId: text("organization_id").references(() => organizations.id, {
14
+ onDelete: "set null",
15
+ }),
16
+ teamId: text("team_id").references(() => teams.id, { onDelete: "set null" }),
17
+ title: text("title").notNull(),
18
+ slug: text("slug").notNull().unique(),
19
+ excerpt: text("excerpt"),
20
+ content: text("content").notNull(),
21
+ status: text("status").$type<"draft" | "published">().notNull().default("draft"),
22
+ publishedAt: integer("published_at", { mode: "timestamp" }),
23
+ createdAt: integer("created_at", { mode: "timestamp" })
24
+ .notNull()
25
+ .$default(() => new Date()),
26
+ updatedAt: integer("updated_at", { mode: "timestamp" }),
27
+ deletedAt: integer("deleted_at", { mode: "timestamp" }),
28
+ });
@@ -1,28 +1,28 @@
1
- import { flattenNestedGrants } from "@m5kdev/backend/modules/base/base.grants";
2
-
3
- export const postsGrants = flattenNestedGrants({
4
- cash: {
5
- user: {
6
- admin: {
7
- read: "all",
8
- write: "all",
9
- delete: "all",
10
- publish: "all",
11
- },
12
- },
13
- organization: {
14
- owner: {
15
- read: "org",
16
- write: "org",
17
- delete: "org",
18
- publish: "org",
19
- },
20
- member: {
21
- read: "own",
22
- write: "own",
23
- delete: "own",
24
- publish: "own",
25
- },
26
- },
27
- },
28
- });
1
+ import { flattenNestedGrants } from "@m5kdev/backend/modules/base/base.grants";
2
+
3
+ export const postsGrants = flattenNestedGrants({
4
+ cash: {
5
+ user: {
6
+ admin: {
7
+ read: "all",
8
+ write: "all",
9
+ delete: "all",
10
+ publish: "all",
11
+ },
12
+ },
13
+ organization: {
14
+ owner: {
15
+ read: "org",
16
+ write: "org",
17
+ delete: "org",
18
+ publish: "org",
19
+ },
20
+ member: {
21
+ read: "own",
22
+ write: "own",
23
+ delete: "own",
24
+ publish: "own",
25
+ },
26
+ },
27
+ },
28
+ });
@@ -1,54 +1,57 @@
1
- import { createBackendRouterMap } from "@m5kdev/backend/app";
2
- import {
3
- BaseModule,
4
- type ModuleRepositoriesContext,
5
- type ModuleServicesContext,
6
- type ModuleTRPCContext,
7
- } from "@m5kdev/backend/modules/base/base.module";
8
- import type * as postsTables from "./posts.db";
9
- import { postsGrants } from "./posts.grants";
10
- import { PostsRepository } from "./posts.repository";
11
- import { PostsService } from "./posts.service";
12
- import { createPostsTRPC } from "./posts.trpc";
13
-
14
- type PostsModuleTables = typeof postsTables;
15
- type PostsModuleRepositories = {
16
- posts: PostsRepository;
17
- };
18
- type PostsModuleServices = {
19
- posts: PostsService;
20
- };
21
- type PostsModuleRouters = {
22
- posts: ReturnType<typeof createPostsTRPC>;
23
- };
24
-
25
- export class PostsModule extends BaseModule<
26
- never,
27
- PostsModuleTables,
28
- PostsModuleRepositories,
29
- PostsModuleServices,
30
- PostsModuleRouters
31
- > {
32
- readonly id = "posts";
33
- override readonly dbDependsOn = ["auth"] as const;
34
-
35
- override repositories({ db }: ModuleRepositoriesContext<never, PostsModuleTables>) {
36
- return {
37
- posts: new PostsRepository({
38
- orm: db.orm,
39
- schema: db.schema,
40
- table: db.schema.posts,
41
- }),
42
- };
43
- }
44
-
45
- override services({ repositories }: ModuleServicesContext<never, PostsModuleRepositories>) {
46
- return {
47
- posts: new PostsService({ posts: repositories.posts }, {}, postsGrants),
48
- };
49
- }
50
-
51
- override trpc({ trpc, services }: ModuleTRPCContext<never, PostsModuleServices>) {
52
- return createBackendRouterMap("posts", createPostsTRPC(trpc, services.posts));
53
- }
54
- }
1
+ import { createBackendRouterMap } from "@m5kdev/backend/app";
2
+ import {
3
+ BaseModule,
4
+ type ModuleRepositoriesContext,
5
+ type ModuleServicesContext,
6
+ type ModuleTRPCContext,
7
+ } from "@m5kdev/backend/modules/base/base.module";
8
+ import type * as postsTables from "./posts.db";
9
+ import { postsGrants } from "./posts.grants";
10
+ import { PostsRepository } from "./posts.repository";
11
+ import { PostsService } from "./posts.service";
12
+ import { createPostsTRPC } from "./posts.trpc";
13
+
14
+ type PostsModuleDeps = Record<string, never>;
15
+ type PostsModuleTables = typeof postsTables;
16
+ type PostsModuleRepositories = {
17
+ posts: PostsRepository;
18
+ };
19
+ type PostsModuleServices = {
20
+ posts: PostsService;
21
+ };
22
+ type PostsModuleRouters = {
23
+ posts: ReturnType<typeof createPostsTRPC>;
24
+ };
25
+
26
+ export class PostsModule extends BaseModule<
27
+ PostsModuleDeps,
28
+ PostsModuleTables,
29
+ PostsModuleRepositories,
30
+ PostsModuleServices,
31
+ PostsModuleRouters
32
+ > {
33
+ readonly id = "posts";
34
+ override readonly dbDependsOn = ["auth"] as const;
35
+
36
+ override repositories({ db }: ModuleRepositoriesContext<PostsModuleDeps, PostsModuleTables>) {
37
+ return {
38
+ posts: new PostsRepository({
39
+ orm: db.orm,
40
+ schema: db.schema,
41
+ table: db.schema.posts,
42
+ }),
43
+ };
44
+ }
45
+
46
+ override services({
47
+ repositories,
48
+ }: ModuleServicesContext<PostsModuleDeps, PostsModuleRepositories>) {
49
+ return {
50
+ posts: new PostsService({ posts: repositories.posts }, {}, postsGrants),
51
+ };
52
+ }
53
+
54
+ override trpc({ trpc, services }: ModuleTRPCContext<PostsModuleDeps, PostsModuleServices>) {
55
+ return createBackendRouterMap("posts", createPostsTRPC(trpc, services.posts));
56
+ }
57
+ }