create-m5kdev 0.33.0 → 0.34.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/src/catalog.cjs +21 -1
- package/dist/src/catalog.cjs.map +1 -1
- package/dist/src/catalog.d.cts +3 -1
- package/dist/src/create.cjs +6 -2
- package/dist/src/create.cjs.map +1 -1
- package/dist/src/doctor.cjs.map +1 -1
- package/dist/src/fs.cjs +10 -5
- package/dist/src/fs.cjs.map +1 -1
- package/dist/src/prompts.cjs +32 -0
- package/dist/src/prompts.cjs.map +1 -1
- package/dist/src/prompts.d.cts +7 -1
- package/dist/src/template.cjs +23 -4
- package/dist/src/template.cjs.map +1 -1
- package/dist/src/template.d.cts +17 -3
- package/dist/src/types.d.cts +11 -4
- package/package.json +4 -1
- package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +1 -1
- package/templates/minimal-app/.cursor/rules/module-grants-guide.mdc +3 -3
- package/templates/minimal-app/.cursor/rules/module-module-guide.mdc +3 -2
- package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +1 -1
- package/templates/minimal-app/.dockerignore.tpl +31 -0
- package/templates/minimal-app/.gitignore.tpl +1 -0
- package/templates/minimal-app/AGENTS.md.tpl +13 -10
- package/templates/minimal-app/CLAUDE.md.tpl +132 -131
- package/templates/minimal-app/README.md.tpl +50 -19
- package/templates/minimal-app/apps/e2e/package.json.tpl +23 -23
- package/templates/minimal-app/apps/e2e/playwright.config.ts.tpl +51 -39
- package/templates/minimal-app/apps/e2e/playwright.expo.config.ts.tpl +38 -38
- package/templates/minimal-app/apps/e2e/servers.ts.tpl +136 -112
- package/templates/minimal-app/apps/e2e/tests/conversation.spec.ts.tpl +22 -0
- package/templates/minimal-app/apps/e2e/tests/email.spec.ts.tpl +47 -47
- package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +73 -73
- package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +517 -517
- package/templates/minimal-app/apps/e2e/tests/expo-waitlist.spec.ts.tpl +94 -94
- package/templates/minimal-app/apps/e2e/tests/files.spec.ts.tpl +35 -0
- package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +289 -289
- package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +516 -516
- package/templates/minimal-app/apps/e2e/tests/waitlist.spec.ts.tpl +107 -107
- package/templates/minimal-app/apps/e2e/tests/workflow.spec.ts.tpl +26 -0
- package/templates/minimal-app/apps/e2e/tsconfig.json.tpl +10 -10
- package/templates/minimal-app/apps/email/package.json.tpl +34 -34
- package/templates/minimal-app/apps/email/src/emails/trialEndingEmail.tsx.tpl +40 -0
- package/templates/minimal-app/apps/email/src/index.ts.tpl +7 -0
- package/templates/minimal-app/apps/email/src/resources.ts.tpl +72 -65
- package/templates/minimal-app/apps/expo/app.json.tpl +36 -36
- package/templates/minimal-app/apps/expo/eslint.config.js.tpl +8 -8
- package/templates/minimal-app/apps/expo/metro.config.js.tpl +16 -16
- package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +31 -31
- package/templates/minimal-app/apps/expo/src/app/claim-account.tsx.tpl +151 -151
- package/templates/minimal-app/apps/expo/src/app/forgot-password.tsx.tpl +53 -53
- package/templates/minimal-app/apps/expo/src/app/index.tsx.tpl +8 -8
- package/templates/minimal-app/apps/expo/src/app/login.tsx.tpl +71 -71
- package/templates/minimal-app/apps/expo/src/app/logout.tsx.tpl +19 -19
- package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +85 -85
- package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +40 -40
- package/templates/minimal-app/apps/expo/src/app/reset-password.tsx.tpl +75 -75
- package/templates/minimal-app/apps/expo/src/app/signup.tsx.tpl +193 -193
- package/templates/minimal-app/apps/expo/src/components/AuthScaffold.tsx.tpl +34 -34
- package/templates/minimal-app/apps/expo/src/components/FormTextField.tsx.tpl +37 -37
- package/templates/minimal-app/apps/expo/src/components/LoadingScreen.tsx.tpl +10 -10
- package/templates/minimal-app/apps/expo/src/components/StatusText.tsx.tpl +22 -22
- package/templates/minimal-app/apps/expo/src/config.ts.tpl +9 -9
- package/templates/minimal-app/apps/expo/src/global.css.tpl +11 -11
- package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +20 -20
- package/templates/minimal-app/apps/expo/tsconfig.json.tpl +11 -11
- package/templates/minimal-app/apps/expo/uniwind-env.d.ts.tpl +1 -1
- package/templates/minimal-app/apps/landing/.env.production.example.tpl +7 -0
- package/templates/minimal-app/apps/landing/Dockerfile.tpl +39 -0
- package/templates/minimal-app/apps/landing/fly.toml.tpl +20 -0
- package/templates/minimal-app/apps/landing/index.html.tpl +35 -0
- package/templates/minimal-app/apps/landing/package.json.tpl +34 -0
- package/templates/minimal-app/apps/landing/scripts/fly-deploy.mjs.tpl +75 -0
- package/templates/minimal-app/apps/landing/scripts/fly-secrets.mjs.tpl +31 -0
- package/templates/minimal-app/apps/landing/src/App.tsx.tpl +12 -0
- package/templates/minimal-app/apps/landing/src/LandingPage.tsx.tpl +37 -0
- package/templates/minimal-app/apps/landing/src/index.css.tpl +24 -0
- package/templates/minimal-app/apps/landing/src/main.tsx.tpl +16 -0
- package/templates/minimal-app/apps/landing/src/vite-env.d.ts.tpl +11 -0
- package/templates/minimal-app/apps/landing/tsconfig.json.tpl +12 -0
- package/templates/minimal-app/apps/landing/vite.config.ts.tpl +25 -0
- package/templates/minimal-app/apps/server/AGENTS.md.tpl +54 -46
- package/templates/minimal-app/apps/server/{drizzle/seed.e2e.ts.tpl → db.e2e.ts.tpl} +57 -40
- package/templates/minimal-app/apps/server/{drizzle/seed.ts.tpl → db.ts.tpl} +27 -20
- package/templates/minimal-app/apps/server/drizzle/0003_silly_the_liberteens.sql +27 -0
- package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +56 -166
- package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +1796 -1910
- package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +1818 -1932
- package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +2137 -0
- package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +8 -1
- package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +3 -33
- package/templates/minimal-app/apps/server/package.json.tpl +5 -3
- package/templates/minimal-app/apps/server/src/app.ts.tpl +36 -0
- package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl +26 -0
- package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.module.ts.tpl +47 -0
- package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.service.ts.tpl +43 -0
- package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.trpc.ts.tpl +16 -0
- package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +28 -28
- package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +28 -28
- package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +54 -54
- package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +67 -67
- package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +116 -116
- package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +37 -37
- package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl +279 -279
- package/templates/minimal-app/apps/server/src/schema.ts.tpl +31 -20
- package/templates/minimal-app/apps/server/src/types.ts.tpl +12 -12
- package/templates/minimal-app/apps/server/tsconfig.json.tpl +1 -1
- package/templates/minimal-app/apps/server/tsup.config.ts.tpl +23 -9
- package/templates/minimal-app/apps/shared/.env.example.tpl +5 -0
- package/templates/minimal-app/apps/shared/.env.production.example.tpl +23 -0
- package/templates/minimal-app/apps/shared/.env.tpl +5 -0
- package/templates/minimal-app/apps/shared/Dockerfile.tpl +48 -0
- package/templates/minimal-app/apps/shared/fly.toml.tpl +24 -0
- package/templates/minimal-app/apps/shared/package.json.tpl +25 -24
- package/templates/minimal-app/apps/shared/scripts/fly-deploy.mjs.tpl +75 -0
- package/templates/minimal-app/apps/shared/scripts/fly-secrets.mjs.tpl +31 -0
- package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +28 -28
- package/templates/minimal-app/apps/shared/src/modules/conversation/conversation.constants.ts.tpl +1 -0
- package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +3 -3
- package/templates/minimal-app/apps/shared/src/modules/posts/posts.constants.ts.tpl +5 -5
- package/templates/minimal-app/apps/shared/src/modules/posts/posts.schema.ts.tpl +25 -25
- package/templates/minimal-app/apps/webapp/package.json.tpl +53 -53
- package/templates/minimal-app/apps/webapp/public/push-sw.js.tpl +34 -34
- package/templates/minimal-app/apps/webapp/src/App.tsx.tpl +46 -44
- package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +96 -65
- package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +114 -93
- package/templates/minimal-app/apps/webapp/src/index.css.tpl +15 -15
- package/templates/minimal-app/apps/webapp/src/main.tsx.tpl +26 -26
- package/templates/minimal-app/apps/webapp/src/modules/conversation/ConversationRoute.tsx.tpl +36 -0
- package/templates/minimal-app/apps/webapp/src/modules/files/FilesRoute.tsx.tpl +88 -0
- package/templates/minimal-app/apps/webapp/src/modules/files/hooks/useFilesRoute.ts.tpl +50 -0
- package/templates/minimal-app/apps/webapp/src/modules/workflows/WorkflowsRoute.tsx.tpl +97 -0
- package/templates/minimal-app/apps/webapp/src/modules/workflows/hooks/useWorkflowsRoute.ts.tpl +64 -0
- package/templates/minimal-app/apps/webapp/src/utils/i18n.ts.tpl +13 -13
- package/templates/minimal-app/apps/webapp/src/utils/trpc.ts.tpl +10 -10
- package/templates/minimal-app/apps/webapp/src/vite-env.d.ts.tpl +1 -1
- package/templates/minimal-app/apps/webapp/translations/en/app.json.tpl +7 -7
- package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +193 -135
- package/templates/minimal-app/apps/webapp/tsconfig.json.tpl +17 -17
- package/templates/minimal-app/apps/webapp/vite.config.ts.tpl +31 -31
- package/templates/minimal-app/biome.json.tpl +17 -12
- package/templates/minimal-app/package.json.tpl +6 -2
- package/templates/minimal-app/pnpm-workspace.yaml.tpl +14 -7
- package/templates/minimal-app/template.manifest.json +92 -2
- package/templates/minimal-app/apps/server/drizzle/db.ts.tpl +0 -26
- package/templates/minimal-app/apps/server/drizzle/guard.ts.tpl +0 -36
- package/templates/minimal-app/apps/server/drizzle/reset.ts.tpl +0 -40
- package/templates/minimal-app/apps/server/drizzle/sync.ts.tpl +0 -16
|
@@ -1,33 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
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
|
|
17
|
-
"drizzle:seed": "tsx --env-file=../shared/.env
|
|
18
|
-
"drizzle:reset": "tsx --env-file=../shared/.env
|
|
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
|
+
});
|
package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.module.ts.tpl
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createBackendRouterMap } from "@m5kdev/backend/app";
|
|
2
|
+
import {
|
|
3
|
+
BaseModule,
|
|
4
|
+
type ModuleServicesContext,
|
|
5
|
+
type ModuleTRPCContext,
|
|
6
|
+
type TableMap,
|
|
7
|
+
} from "@m5kdev/backend/modules/base/base.module";
|
|
8
|
+
import type { WorkflowModule } from "@m5kdev/backend/modules/workflow/workflow.module";
|
|
9
|
+
import { DemoWorkflowService } from "./demo-workflow.service";
|
|
10
|
+
import { createDemoWorkflowTRPC } from "./demo-workflow.trpc";
|
|
11
|
+
|
|
12
|
+
type DemoWorkflowModuleDeps = { workflow: WorkflowModule };
|
|
13
|
+
type DemoWorkflowModuleServices = {
|
|
14
|
+
demoWorkflow: DemoWorkflowService;
|
|
15
|
+
};
|
|
16
|
+
type DemoWorkflowModuleRouters = {
|
|
17
|
+
demoWorkflow: ReturnType<typeof createDemoWorkflowTRPC>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class DemoWorkflowModule extends BaseModule<
|
|
21
|
+
DemoWorkflowModuleDeps,
|
|
22
|
+
TableMap,
|
|
23
|
+
Record<string, never>,
|
|
24
|
+
DemoWorkflowModuleServices,
|
|
25
|
+
DemoWorkflowModuleRouters
|
|
26
|
+
> {
|
|
27
|
+
readonly id = "demo-workflow";
|
|
28
|
+
override readonly dependsOn = ["workflow"] as const;
|
|
29
|
+
|
|
30
|
+
override services({
|
|
31
|
+
deps,
|
|
32
|
+
}: ModuleServicesContext<DemoWorkflowModuleDeps, Record<string, never>>) {
|
|
33
|
+
return {
|
|
34
|
+
demoWorkflow: new DemoWorkflowService({ workflow: deps.workflow.services.workflow }),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override trpc({
|
|
39
|
+
trpc,
|
|
40
|
+
services,
|
|
41
|
+
}: ModuleTRPCContext<DemoWorkflowModuleDeps, DemoWorkflowModuleServices>) {
|
|
42
|
+
return createBackendRouterMap(
|
|
43
|
+
"demoWorkflow",
|
|
44
|
+
createDemoWorkflowTRPC(trpc, services.demoWorkflow)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.service.ts.tpl
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ServerResultAsync } from "@m5kdev/backend/modules/base/base.dto";
|
|
2
|
+
import { BaseService } from "@m5kdev/backend/modules/base/base.service";
|
|
3
|
+
import type { WorkflowService } from "@m5kdev/backend/modules/workflow/workflow.service";
|
|
4
|
+
import type { FireAndForgetJobDefinition } from "@m5kdev/backend/modules/workflow/workflow.types";
|
|
5
|
+
import type { Context } from "@m5kdev/backend/utils/trpc";
|
|
6
|
+
import { workflowTriggerOutputSchema } from "@m5kdev/commons/modules/workflow/workflow.schema";
|
|
7
|
+
import { ok } from "neverthrow";
|
|
8
|
+
|
|
9
|
+
const DEMO_PING_JOB_NAME = "demo.ping";
|
|
10
|
+
|
|
11
|
+
interface DemoPingJobPayload {
|
|
12
|
+
readonly userId: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class DemoWorkflowService extends BaseService<
|
|
16
|
+
Record<string, never>,
|
|
17
|
+
{ workflow: WorkflowService },
|
|
18
|
+
Context
|
|
19
|
+
> {
|
|
20
|
+
readonly demoPingJob: FireAndForgetJobDefinition<DemoPingJobPayload>;
|
|
21
|
+
|
|
22
|
+
constructor(services: { workflow: WorkflowService }) {
|
|
23
|
+
super({}, services);
|
|
24
|
+
|
|
25
|
+
this.demoPingJob = this.service.workflow
|
|
26
|
+
.job<DemoPingJobPayload>({
|
|
27
|
+
name: DEMO_PING_JOB_NAME,
|
|
28
|
+
queue: "fast",
|
|
29
|
+
meta: (payload) => ({ userId: payload.userId }),
|
|
30
|
+
})
|
|
31
|
+
.handle(async () => {
|
|
32
|
+
// Completes immediately so the Starter webapp can observe a finished run.
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
readonly runDemo = this.procedure("runDemo")
|
|
37
|
+
.output(workflowTriggerOutputSchema)
|
|
38
|
+
.requireAuth()
|
|
39
|
+
.handle(async ({ ctx }): ServerResultAsync<{ jobId: string }> => {
|
|
40
|
+
const jobId = await this.demoPingJob.trigger({ userId: ctx.actor.userId });
|
|
41
|
+
return ok({ jobId });
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -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,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 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 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,67 +1,67 @@
|
|
|
1
|
-
import { BaseTableRepository } from "@m5kdev/backend/modules/base/base.repository";
|
|
2
|
-
|
|
3
|
-
import { and, eq, isNull, ne } from "drizzle-orm";
|
|
4
|
-
import type { LibSQLDatabase } from "drizzle-orm/libsql";
|
|
5
|
-
import { err, ok } from "neverthrow";
|
|
6
|
-
import { z } from "zod";
|
|
7
|
-
import { posts } from "./posts.db";
|
|
8
|
-
import { postSchemas } from "./posts.dto";
|
|
9
|
-
|
|
10
|
-
const schema = { posts };
|
|
11
|
-
type Schema = typeof schema;
|
|
12
|
-
type Orm = LibSQLDatabase<Schema>;
|
|
13
|
-
|
|
14
|
-
export class PostsRepository extends BaseTableRepository<
|
|
15
|
-
Orm,
|
|
16
|
-
Schema,
|
|
17
|
-
Record<string, never>,
|
|
18
|
-
Schema["posts"]
|
|
19
|
-
> {
|
|
20
|
-
findBySlug = this.query("findBySlug")
|
|
21
|
-
.input(
|
|
22
|
-
z.object({
|
|
23
|
-
slug: z.string(),
|
|
24
|
-
excludeId: z.string().optional(),
|
|
25
|
-
})
|
|
26
|
-
)
|
|
27
|
-
.output(postSchemas.output.found)
|
|
28
|
-
.handle(async ({ excludeId, slug }) => {
|
|
29
|
-
const whereClause = excludeId
|
|
30
|
-
? and(eq(this.table.slug, slug), ne(this.table.id, excludeId), isNull(this.table.deletedAt))
|
|
31
|
-
: and(eq(this.table.slug, slug), isNull(this.table.deletedAt));
|
|
32
|
-
|
|
33
|
-
const result = await this.throwableQuery(() =>
|
|
34
|
-
this.orm.select().from(this.table).where(whereClause).limit(1)
|
|
35
|
-
);
|
|
36
|
-
if (result.isErr()) return err(result.error);
|
|
37
|
-
const [row] = result.value;
|
|
38
|
-
return ok(row);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
resolveUniqueSlug = this.query("resolveUniqueSlug")
|
|
42
|
-
.input(
|
|
43
|
-
z.object({
|
|
44
|
-
candidate: z.string(),
|
|
45
|
-
excludeId: z.string().optional(),
|
|
46
|
-
})
|
|
47
|
-
)
|
|
48
|
-
.output(z.string())
|
|
49
|
-
.handle(async ({ candidate, excludeId }) => {
|
|
50
|
-
let slug = candidate;
|
|
51
|
-
let suffix = 2;
|
|
52
|
-
|
|
53
|
-
while (true) {
|
|
54
|
-
const existing = await this.findBySlug({ slug, excludeId });
|
|
55
|
-
if (existing.isErr()) {
|
|
56
|
-
return err(existing.error);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (!existing.value) {
|
|
60
|
-
return ok(slug);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
slug = `${candidate}-${suffix}`;
|
|
64
|
-
suffix += 1;
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
}
|
|
1
|
+
import { BaseTableRepository } from "@m5kdev/backend/modules/base/base.repository";
|
|
2
|
+
|
|
3
|
+
import { and, eq, isNull, ne } from "drizzle-orm";
|
|
4
|
+
import type { LibSQLDatabase } from "drizzle-orm/libsql";
|
|
5
|
+
import { err, ok } from "neverthrow";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { posts } from "./posts.db";
|
|
8
|
+
import { postSchemas } from "./posts.dto";
|
|
9
|
+
|
|
10
|
+
const schema = { posts };
|
|
11
|
+
type Schema = typeof schema;
|
|
12
|
+
type Orm = LibSQLDatabase<Schema>;
|
|
13
|
+
|
|
14
|
+
export class PostsRepository extends BaseTableRepository<
|
|
15
|
+
Orm,
|
|
16
|
+
Schema,
|
|
17
|
+
Record<string, never>,
|
|
18
|
+
Schema["posts"]
|
|
19
|
+
> {
|
|
20
|
+
findBySlug = this.query("findBySlug")
|
|
21
|
+
.input(
|
|
22
|
+
z.object({
|
|
23
|
+
slug: z.string(),
|
|
24
|
+
excludeId: z.string().optional(),
|
|
25
|
+
})
|
|
26
|
+
)
|
|
27
|
+
.output(postSchemas.output.found)
|
|
28
|
+
.handle(async ({ excludeId, slug }) => {
|
|
29
|
+
const whereClause = excludeId
|
|
30
|
+
? and(eq(this.table.slug, slug), ne(this.table.id, excludeId), isNull(this.table.deletedAt))
|
|
31
|
+
: and(eq(this.table.slug, slug), isNull(this.table.deletedAt));
|
|
32
|
+
|
|
33
|
+
const result = await this.throwableQuery(() =>
|
|
34
|
+
this.orm.select().from(this.table).where(whereClause).limit(1)
|
|
35
|
+
);
|
|
36
|
+
if (result.isErr()) return err(result.error);
|
|
37
|
+
const [row] = result.value;
|
|
38
|
+
return ok(row);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
resolveUniqueSlug = this.query("resolveUniqueSlug")
|
|
42
|
+
.input(
|
|
43
|
+
z.object({
|
|
44
|
+
candidate: z.string(),
|
|
45
|
+
excludeId: z.string().optional(),
|
|
46
|
+
})
|
|
47
|
+
)
|
|
48
|
+
.output(z.string())
|
|
49
|
+
.handle(async ({ candidate, excludeId }) => {
|
|
50
|
+
let slug = candidate;
|
|
51
|
+
let suffix = 2;
|
|
52
|
+
|
|
53
|
+
while (true) {
|
|
54
|
+
const existing = await this.findBySlug({ slug, excludeId });
|
|
55
|
+
if (existing.isErr()) {
|
|
56
|
+
return err(existing.error);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!existing.value) {
|
|
60
|
+
return ok(slug);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
slug = `${candidate}-${suffix}`;
|
|
64
|
+
suffix += 1;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|