create-m5kdev 0.41.0 → 0.43.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 (83) hide show
  1. package/package.json +1 -1
  2. package/templates/minimal-app/.cursor/rules/frontend-component-guide.mdc +95 -95
  3. package/templates/minimal-app/.cursor/rules/frontend-data-guide.mdc +103 -103
  4. package/templates/minimal-app/.cursor/rules/frontend-form-guide.mdc +80 -80
  5. package/templates/minimal-app/.cursor/rules/frontend-hook-guide.mdc +79 -79
  6. package/templates/minimal-app/.cursor/rules/frontend-i18n-guide.mdc +45 -45
  7. package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +60 -60
  8. package/templates/minimal-app/.cursor/rules/module-prompt-guide.mdc +214 -214
  9. package/templates/minimal-app/.cursor/rules/module-repository-guide.mdc +167 -167
  10. package/templates/minimal-app/.cursor/rules/module-schema-guide.mdc +53 -53
  11. package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +367 -367
  12. package/templates/minimal-app/.cursor/rules/module-trpc-guide.mdc +71 -71
  13. package/templates/minimal-app/.github/workflows/check.yml.tpl +21 -0
  14. package/templates/minimal-app/AGENTS.md.tpl +22 -31
  15. package/templates/minimal-app/CLAUDE.md.tpl +17 -3
  16. package/templates/minimal-app/GLOSSARY.md.tpl +181 -0
  17. package/templates/minimal-app/README.md.tpl +14 -6
  18. package/templates/minimal-app/apps/.gitignore.tpl +13 -13
  19. package/templates/minimal-app/apps/e2e/package.json.tpl +23 -23
  20. package/templates/minimal-app/apps/e2e/servers.ts.tpl +22 -6
  21. package/templates/minimal-app/apps/email/package.json.tpl +34 -34
  22. package/templates/minimal-app/apps/expo/.gitignore.tpl +44 -44
  23. package/templates/minimal-app/apps/expo/.prettierignore +13 -13
  24. package/templates/minimal-app/apps/expo/.prettierrc.json.tpl +9 -9
  25. package/templates/minimal-app/apps/expo/CHANGELOG.md.tpl +156 -156
  26. package/templates/minimal-app/apps/expo/README.md.tpl +44 -44
  27. package/templates/minimal-app/apps/expo/expo-env.d.ts.tpl +3 -0
  28. package/templates/minimal-app/apps/expo/package.json.tpl +63 -63
  29. package/templates/minimal-app/apps/expo/uniwind-types.d.ts.tpl +10 -0
  30. package/templates/minimal-app/apps/landing/package.json.tpl +34 -34
  31. package/templates/minimal-app/apps/server/AGENTS.md.tpl +1 -1
  32. package/templates/minimal-app/apps/server/drizzle/0000_whole_echo.sql +366 -366
  33. package/templates/minimal-app/apps/server/drizzle/0002_crazy_lilith.sql +46 -46
  34. package/templates/minimal-app/apps/server/drizzle/0003_freezing_fantastic_four.sql +3 -3
  35. package/templates/minimal-app/apps/server/drizzle/0004_slow_ezekiel_stane.sql +98 -98
  36. package/templates/minimal-app/apps/server/drizzle/0005_wonderful_the_professor.sql +1 -1
  37. package/templates/minimal-app/apps/server/drizzle/0006_lazy_blue_marvel.sql +158 -158
  38. package/templates/minimal-app/apps/server/drizzle/0007_abnormal_ultimo.sql +1 -1
  39. package/templates/minimal-app/apps/server/drizzle/0008_steady_nekra.sql +86 -86
  40. package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +2624 -2624
  41. package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +2670 -2670
  42. package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +2715 -2715
  43. package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +2687 -2687
  44. package/templates/minimal-app/apps/server/drizzle/meta/0004_snapshot.json.tpl +2497 -2497
  45. package/templates/minimal-app/apps/server/drizzle/meta/0005_snapshot.json.tpl +2483 -2483
  46. package/templates/minimal-app/apps/server/drizzle/meta/0006_snapshot.json.tpl +3602 -3602
  47. package/templates/minimal-app/apps/server/drizzle/meta/0007_snapshot.json.tpl +3619 -3619
  48. package/templates/minimal-app/apps/server/drizzle/meta/0008_snapshot.json.tpl +3619 -3619
  49. package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +76 -76
  50. package/templates/minimal-app/apps/server/drizzle-postgresql/0000_glorious_warhawk.sql +522 -0
  51. package/templates/minimal-app/apps/server/drizzle-postgresql/meta/0000_snapshot.json.tpl +3555 -0
  52. package/templates/minimal-app/apps/server/drizzle-postgresql/meta/_journal.json.tpl +13 -0
  53. package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +1 -1
  54. package/templates/minimal-app/apps/server/mastra.ts.tpl +42 -0
  55. package/templates/minimal-app/apps/server/package.json.tpl +9 -4
  56. package/templates/minimal-app/apps/server/src/app.ts.tpl +21 -16
  57. package/templates/minimal-app/apps/server/src/drizzle.schema.ts.tpl +2 -0
  58. package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl +48 -0
  59. package/templates/minimal-app/apps/server/src/modules/conversation/conversation.persistence.ts.tpl +47 -0
  60. package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +7 -6
  61. package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +2 -2
  62. package/templates/minimal-app/apps/shared/.env.example.tpl +6 -3
  63. package/templates/minimal-app/apps/shared/.env.production.example.tpl +7 -2
  64. package/templates/minimal-app/apps/shared/.env.tpl +6 -3
  65. package/templates/minimal-app/apps/shared/Dockerfile.pgbouncer +6 -0
  66. package/templates/minimal-app/apps/shared/compose.yaml.tpl +27 -0
  67. package/templates/minimal-app/apps/shared/fly.toml.tpl +0 -4
  68. package/templates/minimal-app/apps/shared/package.json.tpl +27 -27
  69. package/templates/minimal-app/apps/shared/pgbouncer.ini +13 -0
  70. package/templates/minimal-app/apps/shared/pgbouncer.users +1 -0
  71. package/templates/minimal-app/apps/webapp/AGENTS.md.tpl +24 -24
  72. package/templates/minimal-app/apps/webapp/index.html.tpl +22 -22
  73. package/templates/minimal-app/apps/webapp/package.json.tpl +53 -53
  74. package/templates/minimal-app/apps/webapp/public/logo.svg.tpl +6 -6
  75. package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +1 -1
  76. package/templates/minimal-app/biome.json.tpl +1 -0
  77. package/templates/minimal-app/docs/adr/.gitkeep +0 -0
  78. package/templates/minimal-app/docs/agents/domain.md.tpl +38 -0
  79. package/templates/minimal-app/docs/agents/issue-tracker.md.tpl +45 -0
  80. package/templates/minimal-app/docs/agents/triage-labels.md.tpl +15 -0
  81. package/templates/minimal-app/package.json.tpl +5 -0
  82. package/templates/minimal-app/pnpm-workspace.yaml.tpl +16 -13
  83. package/templates/minimal-app/template.manifest.json +21 -0
@@ -1,71 +1,71 @@
1
- ---
2
- globs: apps/**/server/src/modules/**/*.trpc.ts
3
- alwaysApply: false
4
- ---
5
-
6
- # Module tRPC File Style Guide
7
-
8
- Conventions for tRPC router files in app modules. Routers are thin wiring:
9
- they attach the module DTO schemas and delegate to the service. The router is
10
- also the type source for the client — the webapp infers all row/list types
11
- from `AppRouter`, so accurate `.input()`/`.output()` here is what makes
12
- end-to-end types work.
13
-
14
- ## File Location
15
-
16
- ```
17
- apps/<app>/server/src/modules/<module>/<module>.trpc.ts
18
- ```
19
-
20
- ## Conventions
21
-
22
- - Export a factory function named `create<Module>TRPC`.
23
- - Import the module DTO object (`postSchemas`) from `./<module>.dto` — not
24
- shared schemas, not hand-written types.
25
- - Use `handleTRPCResult` from `@m5kdev/backend/utils/trpc` to unwrap `Result`s.
26
- - Keep resolvers one-liners that delegate to the service; no business logic or
27
- DB access here.
28
- - Always pass `ctx` to service methods as the second argument. The first
29
- argument (`input`) can be `undefined` for optional-input queries.
30
-
31
- ## Factory Pattern
32
-
33
- ```typescript
34
- import { handleTRPCResult, type TRPCMethods } from "@m5kdev/backend/utils/trpc";
35
- import { postSchemas } from "./posts.dto";
36
- import type { PostsService } from "./posts.service";
37
-
38
- export function createPostsTRPC(
39
- { router, privateProcedure: procedure }: TRPCMethods,
40
- postsService: PostsService
41
- ) {
42
- return router({
43
- list: procedure
44
- .input(postSchemas.input.list)
45
- .output(postSchemas.output.list)
46
- .query(async ({ ctx, input }) => handleTRPCResult(await postsService.list(input ?? {}, ctx))),
47
-
48
- create: procedure
49
- .input(postSchemas.input.create)
50
- .output(postSchemas.output.single)
51
- .mutation(async ({ ctx, input }) => handleTRPCResult(await postsService.create(input, ctx))),
52
-
53
- softDelete: procedure
54
- .input(postSchemas.input.delete)
55
- .output(postSchemas.output.uuid)
56
- .mutation(async ({ ctx, input }) =>
57
- handleTRPCResult(await postsService.softDelete(input, ctx))
58
- ),
59
- });
60
- }
61
- ```
62
-
63
- ## Procedure Selection
64
-
65
- - `privateProcedure` — authenticated user (most module routes).
66
- - `organizationProcedure` — requires an active organization.
67
- - `adminProcedure` — admin role.
68
- - `publicProcedure` — unauthenticated; use sparingly.
69
-
70
- The service's own `.requireAuth()`/`.access()` still run — the tRPC procedure
71
- choice is transport-level gating, not a replacement for service checks.
1
+ ---
2
+ globs: apps/**/server/src/modules/**/*.trpc.ts
3
+ alwaysApply: false
4
+ ---
5
+
6
+ # Module tRPC File Style Guide
7
+
8
+ Conventions for tRPC router files in app modules. Routers are thin wiring:
9
+ they attach the module DTO schemas and delegate to the service. The router is
10
+ also the type source for the client — the webapp infers all row/list types
11
+ from `AppRouter`, so accurate `.input()`/`.output()` here is what makes
12
+ end-to-end types work.
13
+
14
+ ## File Location
15
+
16
+ ```
17
+ apps/<app>/server/src/modules/<module>/<module>.trpc.ts
18
+ ```
19
+
20
+ ## Conventions
21
+
22
+ - Export a factory function named `create<Module>TRPC`.
23
+ - Import the module DTO object (`postSchemas`) from `./<module>.dto` — not
24
+ shared schemas, not hand-written types.
25
+ - Use `handleTRPCResult` from `@m5kdev/backend/utils/trpc` to unwrap `Result`s.
26
+ - Keep resolvers one-liners that delegate to the service; no business logic or
27
+ DB access here.
28
+ - Always pass `ctx` to service methods as the second argument. The first
29
+ argument (`input`) can be `undefined` for optional-input queries.
30
+
31
+ ## Factory Pattern
32
+
33
+ ```typescript
34
+ import { handleTRPCResult, type TRPCMethods } from "@m5kdev/backend/utils/trpc";
35
+ import { postSchemas } from "./posts.dto";
36
+ import type { PostsService } from "./posts.service";
37
+
38
+ export function createPostsTRPC(
39
+ { router, privateProcedure: procedure }: TRPCMethods,
40
+ postsService: PostsService
41
+ ) {
42
+ return router({
43
+ list: procedure
44
+ .input(postSchemas.input.list)
45
+ .output(postSchemas.output.list)
46
+ .query(async ({ ctx, input }) => handleTRPCResult(await postsService.list(input ?? {}, ctx))),
47
+
48
+ create: procedure
49
+ .input(postSchemas.input.create)
50
+ .output(postSchemas.output.single)
51
+ .mutation(async ({ ctx, input }) => handleTRPCResult(await postsService.create(input, ctx))),
52
+
53
+ softDelete: procedure
54
+ .input(postSchemas.input.delete)
55
+ .output(postSchemas.output.uuid)
56
+ .mutation(async ({ ctx, input }) =>
57
+ handleTRPCResult(await postsService.softDelete(input, ctx))
58
+ ),
59
+ });
60
+ }
61
+ ```
62
+
63
+ ## Procedure Selection
64
+
65
+ - `privateProcedure` — authenticated user (most module routes).
66
+ - `organizationProcedure` — requires an active organization.
67
+ - `adminProcedure` — admin role.
68
+ - `publicProcedure` — unauthenticated; use sparingly.
69
+
70
+ The service's own `.requireAuth()`/`.access()` still run — the tRPC procedure
71
+ choice is transport-level gating, not a replacement for service checks.
@@ -0,0 +1,21 @@
1
+ name: check
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ check:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: pnpm/action-setup@v4
13
+ with:
14
+ version: 10.13.1
15
+ - uses: actions/setup-node@v4
16
+ with:
17
+ node-version: 24
18
+ cache: pnpm
19
+ - run: pnpm install
20
+ - run: pnpm lint
21
+ - run: pnpm check-types
@@ -22,38 +22,29 @@
22
22
  // m5k:expo:end
23
23
 
24
24
  // m5k:server:start
25
- ## Backend Conventions
26
-
27
- - Keep repository, service, and transport layers separate inside each module.
28
- - Register modules in `apps/server/src/app.ts` via `createBackendApp(config, [modules])`.
29
- - Import Kernel infrastructure (`BaseModule`, Grants, DTOs) from `@m5kdev/backend/base/*`. The `modules/base` path still re-exports.
30
- - Clay, Docx, Pdf, Social, and Video are Optional Backend Module packages (`@m5kdev/module-<name>`). They are not scaffolded; add them only when the product needs them.
31
- - Use `BaseModule` subclasses for app modules; the kernel wires repositories, services, and tRPC.
32
- - Keep tRPC files focused on input/output wiring and delegate logic to services.
33
- - Do not add service methods that merely call another service. Call the owning service directly unless the method adds a business rule, authorization, validation, orchestration, a transaction boundary, or meaningful domain translation.
34
- - Renaming a dependency method, repackaging arguments, constructing a prompt, or forwarding actor/context data alone does not justify a service wrapper.
35
- - Do not create Drizzle migrations by hand. Use the scaffolded config and your project migration workflow later if you need generated migrations.
36
- - Server event: emit through Auth from Workflow job handlers (and similar background work) when the UI must refresh a change the triggering mutation did not return. See `.cursor/rules/server-event-emit.mdc`.
37
- // m5k:server:end
25
+ ## Modules
26
+
27
+ `posts` is the reference module. Mirror its layout when adding a feature:
38
28
 
29
+ - `apps/shared/src/modules/posts/`
30
+ - `apps/server/src/modules/posts/`
39
31
  // m5k:webapp:start
40
- ## Frontend Conventions
41
-
42
- - Keep the app shell shape: `NuqsAdapter` + `BrowserRouter` + `Providers`.
43
- - Compose global providers only in `apps/webapp/src/Providers.tsx`.
44
- - Use `AppConfigProvider` and `AppTrpcQueryProvider` from `@m5kdev/frontend`; use `@m5kdev/web-ui` for web-only UI.
45
- - Define app roles in `apps/shared/src/modules/app/roles.constants.ts` and pass `roles: APP_ROLES_CONFIG` through `AppConfigProvider` and `createBackendApp` metadata.
46
- - Add role display labels in `apps/webapp/translations/app.json` using keys like `organization.role.editor` (resolved as `app:organization.role.editor`).
47
- - When adding custom roles, also extend module grants (for example `*.grants.ts`) so permissions match the new role keys.
48
- - For upgrading existing apps, see the docs site guide `apps/docs/docs/guides/custom-app-roles-migration.md`.
49
- - Use `nuqs` for URL state, React Router for routing, HeroUI for UI primitives, and Tailwind v4 for styling.
50
- - Prefer shared framework utilities from `@m5kdev/frontend` and `@m5kdev/web-ui` before adding local duplicates.
51
- - React 19 idioms: no `forwardRef` (pass `ref` as a prop), no `defaultProps`, use `startTransition`/`useDeferredValue` for low-priority updates.
52
- - Prefer HeroUI v3 components over hand-rolled HTML + Tailwind whenever an equivalent exists; Tailwind is for layout and styling on top of them.
53
- - Keep components display-focused: data fetching, mutations, URL state, and effect chains live in custom hooks under `modules/<feature>/hooks/` (small focused hooks composed into one screen-level hook, e.g. `usePostsRoute`). Ephemeral UI state (modal open, selected row) may stay in the component. Extract reusable tRPC actions (delete, publish) into their own hooks.
54
- - Fetch all server data with TanStack Query over tRPC (`useTRPC()` + `queryOptions`/`mutationOptions`); invalidate with the matching `queryFilter()` after mutations. No `fetch`/axios.
55
- - Forms are uncontrolled HeroUI `Form` + `TextField`/`TextArea` with native HTML validation (`isRequired`, `type`, `minLength`) and `FieldError`; read values from `FormData` on submit. Never add form libraries (react-hook-form, formik). Validation that HTML cannot express (e.g. confirm-password) happens in the submit handler.
56
- - All user-facing copy goes through i18next (`useTranslation`); keys live in `apps/webapp/translations/`.
57
- - The `posts` feature (`apps/webapp/src/modules/posts/`) is the reference implementation for these conventions; see `.cursor/rules/frontend-*.mdc` for the detailed guides.
32
+ - `apps/webapp/src/modules/posts/`
58
33
  // m5k:webapp:end
59
34
 
35
+ Read the matching `.cursor/rules` guide before editing a module file.
36
+ // m5k:server:end
37
+
38
+ ## Agent skills
39
+
40
+ ### Issue tracker
41
+
42
+ Issues live in GitHub Issues. Use the `gh` CLI. See `docs/agents/issue-tracker.md`.
43
+
44
+ ### Triage labels
45
+
46
+ Default role labels: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`. See `docs/agents/triage-labels.md`.
47
+
48
+ ### Domain docs
49
+
50
+ Single-context: one root `GLOSSARY.md` and `docs/adr/`. See `docs/agents/domain.md`.
@@ -8,7 +8,7 @@ Guidance for Claude Code when working in this repository.
8
8
 
9
9
  This is a pnpm + Turborepo monorepo built on the **m5kdev framework**:
10
10
 
11
- - `@m5kdev/backend` — backend kernel: Express, Better Auth, Drizzle (LibSQL), and tRPC. Apps compose it via `createBackendApp(config, [modules])`.
11
+ - `@m5kdev/backend` — backend kernel: Express, Better Auth, Drizzle (PostgreSQL), and tRPC. Apps compose it via `createBackendApp(config, [modules])`.
12
12
  - `@m5kdev/commons` — shared runtime utilities and contracts.
13
13
  - `@m5kdev/config` — configuration loading and validation.
14
14
  // m5k:server:start
@@ -68,7 +68,7 @@ First-time setup: `pnpm install`, then `drizzle:migrate` and `drizzle:seed`, the
68
68
  End-to-end tests: `pnpm --filter ./apps/e2e test:e2e` (Playwright).
69
69
  // m5k:test-harness:end
70
70
 
71
- The local database is a LibSQL file; local auth emails are written to `apps/server/.emails`. Demo login: `admin@{{APP_SLUG}}.local` / `password1234`.
71
+ Runtime uses PostgreSQL through PgBouncer (`DATABASE_URL`); migrations and initialization require `DATABASE_DIRECT_URL`. Keep app, Mastra, and vector schemas separately owned. Start the local PostgreSQL/PgBouncer Compose stack and provision pgvector before applying reviewed migrations. Agents must not generate or hand-author migration SQL or metadata. Local auth emails are written to `apps/server/.emails`. Demo login: `admin@{{APP_SLUG}}.local` / `password1234`.
72
72
  // m5k:server:end
73
73
 
74
74
  // m5k:server:start
@@ -139,6 +139,20 @@ The framework's detailed conventions live in `.cursor/rules/*.mdc`. Cursor appli
139
139
  | `.cursor/rules/frontend-i18n-guide.mdc` | webapp user-facing copy |
140
140
  // m5k:webapp:end
141
141
 
142
- `AGENTS.md` (repo root and per-app) carries a compact summary of the same conventions for other agents; this file plus the guides above are the authoritative version for Claude.
142
+ `AGENTS.md` (repo root and per-app) points at this file and `.cursor/rules`. This file plus the guides above are the authoritative conventions.
143
143
  // m5k:server:end
144
144
 
145
+ ## Agent skills
146
+
147
+ ### Issue tracker
148
+
149
+ Issues live in GitHub Issues. Use the `gh` CLI. See `docs/agents/issue-tracker.md`.
150
+
151
+ ### Triage labels
152
+
153
+ Default role labels: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`. See `docs/agents/triage-labels.md`.
154
+
155
+ ### Domain docs
156
+
157
+ Single-context: one root `GLOSSARY.md` and `docs/adr/`. See `docs/agents/domain.md`.
158
+
@@ -0,0 +1,181 @@
1
+ # {{APP_NAME}}
2
+
3
+ {{APP_DESCRIPTION}}
4
+
5
+ ## Language
6
+ # m5k:server:start
7
+
8
+ ### Tenancy
9
+
10
+ **Organization**:
11
+ The default tenancy unit. Signup creates one; a Closed Organization cannot be used.
12
+ _Avoid_: Workspace, tenant, account, company, Team
13
+
14
+ **Membership**:
15
+ A durable seat in an Organization. Invite can create it before a User exists; leave, cancel, and expiry soft-delete it; rejoin revives the same MemberId. An Organization has exactly one Owner.
16
+ _Avoid_: OrgUser; treating Invitation as the Membership; Seat (that is Seat billing)
17
+
18
+ **Member**:
19
+ The Membership principal used to attribute and authorize org-scoped assets, including invited Memberships that do not yet have a User.
20
+ _Avoid_: User (when you mean the membership), Invitation
21
+
22
+ **MemberId**:
23
+ The Membership id stamped on org-scoped rows. In organization context, `"own"` grants compare this, not UserId.
24
+ _Avoid_: UserId as the org ownership key
25
+
26
+ **User**:
27
+ The global sign-in identity. Owns personal resources that are not org tenancy. A Closed User cannot sign in.
28
+ _Avoid_: Account, Customer, Client, Member
29
+
30
+ **UserId**:
31
+ The User id. Correct key for personal resources. Not the org-scoped ownership key.
32
+ _Avoid_: MemberId
33
+
34
+ **Owner**:
35
+ The Organization Role `owner`. Exactly one per Organization. Invite and org self-service cannot grant or transfer it.
36
+ _Avoid_: multiple Owners as a product feature; inviting Owner
37
+
38
+ **Invitation**:
39
+ The accept token that attaches a User to an invited Membership. Not a person and not assignable.
40
+ _Avoid_: Waitlist code, pending Member
41
+
42
+ **Closed**:
43
+ A User or Organization in the restorable lockout state.
44
+ _Avoid_: soft-delete (that is Membership leave), deactivated, suspended, banned
45
+
46
+ **Restore**:
47
+ Returning a Closed User or Organization to usable state before Purge. Admin panel only.
48
+ _Avoid_: undelete, reactivate
49
+
50
+ **Purge**:
51
+ Permanent removal of a Closed User or Organization after the grace window.
52
+ _Avoid_: hard-delete as the User-facing noun
53
+
54
+ ### Identity and access
55
+
56
+ **Actor**:
57
+ Who a server call is made on behalf of: User, Organization, or Admin. Organization scope requires a live Membership and an Organization that is not Closed. Invited Members are not Actors.
58
+ _Avoid_: Session, Principal, Request, TeamActor
59
+
60
+ **Grant**:
61
+ A permission tuple: resource, level (`user` | `organization`), role, Action, Access.
62
+ _Avoid_: Permission, Policy, ACL
63
+
64
+ **Action**:
65
+ Canonical Grant verbs: `read`, `write`, `delete`, `publish`.
66
+ _Avoid_: update, create, patch, remove
67
+
68
+ **Access**:
69
+ How wide a Grant is: `all`, `own`, `org`, `none`.
70
+ _Avoid_: Scope, Role
71
+
72
+ **Role**:
73
+ A named key at User or Organization scope. Defaults: User `user`/`admin`; Organization `member`/`admin`/`owner`.
74
+ _Avoid_: Grant, Access, permission
75
+
76
+ ### Product surfaces
77
+
78
+ **Admin panel**:
79
+ The User-role admin UI at `/admin`. Organization Role `admin` does not see it.
80
+ _Avoid_: org Role admin, Billing Portal
81
+ # m5k:files:start
82
+
83
+ **File**:
84
+ An inventoried object in storage: a row plus the bytes. Personal Files stamp UserId; org-scoped Files stamp MemberId.
85
+ _Avoid_: Upload, Asset, Attachment, Blob
86
+ # m5k:files:end
87
+ # m5k:billing:start
88
+
89
+ **Plan**:
90
+ Named commercial offering in app code. Not a Stripe Product.
91
+ _Avoid_: Product, Tier, Subscription
92
+
93
+ **Subscription**:
94
+ The billed relationship for an Organization, not a User.
95
+ _Avoid_: Plan, personal User subscription
96
+
97
+ **Checkout**:
98
+ Hosted start of a Subscription when the Organization has none.
99
+ _Avoid_: Billing Portal
100
+
101
+ **Billing Portal**:
102
+ Hosted management of an existing Subscription.
103
+ _Avoid_: Checkout
104
+
105
+ **Seat billing**:
106
+ When on, billable quantity is the count of Memberships. Off (default): quantity is one.
107
+ _Avoid_: Seat as a synonym for Membership
108
+ # m5k:billing:end
109
+ # m5k:ai:start
110
+
111
+ **Conversation**:
112
+ Ordered UI transcript of AI turns.
113
+ _Avoid_: Chat, Thread (that is the store)
114
+
115
+ **Thread**:
116
+ The persisted AI memory thread behind a Conversation.
117
+ _Avoid_: using Thread for the UI surface
118
+
119
+ **Agent**:
120
+ A named AI agent the app registers. A Conversation selects which Agent answers.
121
+ _Avoid_: Assistant, Bot, MCP client
122
+ # m5k:ai:end
123
+ # m5k:mcp:start
124
+
125
+ **MCP client**:
126
+ A remote Model Context Protocol client that calls the app on behalf of a User.
127
+ _Avoid_: Agent, Connection
128
+
129
+ **MCP call**:
130
+ A tRPC procedure opted into the MCP catalog. Grant checks always run.
131
+ _Avoid_: a second tool catalog, Workflow job
132
+
133
+ **MCP allowlist**:
134
+ The Organizations a given OAuth client may pass as organizationId for a User.
135
+ _Avoid_: treating allowlist as permission to write
136
+ # m5k:mcp:end
137
+ # m5k:notifications:start
138
+
139
+ **Notification**:
140
+ A persisted inbox instance owned by a Member, classified by a Notification kind.
141
+ _Avoid_: UserId as the ownership key
142
+
143
+ **Notification kind**:
144
+ A developer-declared class of Notifications. Mute applies to the kind.
145
+ _Avoid_: Notification (that is the instance)
146
+
147
+ **Channel**:
148
+ A delivery means for a Notification: in-app, web push, mobile push, or email.
149
+ _Avoid_: Server event, Device
150
+ # m5k:notifications:end
151
+ # m5k:workflows:start
152
+
153
+ **Workflow**:
154
+ A persisted background run. Org-scoped runs stamp MemberId.
155
+ _Avoid_: Job, Queue, Task, Recurrence
156
+ # m5k:workflows:end
157
+ # m5k:tags:start
158
+
159
+ **Tag**:
160
+ A polymorphic label. Ownership is UserId, MemberId, or organizationId.
161
+ _Avoid_: Label, Category
162
+ # m5k:tags:end
163
+ # m5k:connect:start
164
+
165
+ **Connection**:
166
+ A linked third-party API account. Personal; keyed by UserId. The module id is `connect`.
167
+ _Avoid_: Connect as the product noun; login OAuth
168
+ # m5k:connect:end
169
+ # m5k:webhook:start
170
+
171
+ **Inbound callback**:
172
+ A one-shot inbound callback with an awaitable payload. Unattributed. The module id is `webhook`.
173
+ _Avoid_: Webhook (when you mean this vs Stripe Subscription sync); Connection
174
+ # m5k:webhook:end
175
+ # m5k:recurrence:start
176
+
177
+ **Recurrence**:
178
+ A repeating calendar pattern stored as a row. Not a Workflow.
179
+ _Avoid_: Workflow, cron
180
+ # m5k:recurrence:end
181
+ # m5k:server:end
@@ -32,13 +32,18 @@ pnpm --filter ./apps/server drizzle:migrate
32
32
  pnpm --filter ./apps/server drizzle:seed
33
33
  ```
34
34
 
35
- The starter uses a local LibSQL file by default and writes local auth emails to `apps/server/.emails`.
36
- Database tables are registered by hand in `apps/server/src/schema.ts`; after changing any `*.db.ts`, run `drizzle:generate` and `drizzle:migrate`.
35
+ The starter uses PostgreSQL through PgBouncer and writes local auth emails to `apps/server/.emails`. Start `docker compose -f apps/shared/compose.yaml up -d --build`, configure runtime `DATABASE_URL` and direct `DATABASE_DIRECT_URL`, and provision pgvector in `public` before migration/initialization.
36
+ Database tables are registered in `apps/server/src/schema.ts`; `src/drizzle.schema.ts` adds schema metadata for migration generation. After changing any `*.db.ts`, the maintainer runs `drizzle:generate`, reviews the output, then runs `drizzle:migrate`.
37
+ // m5k:ai:start
38
+ Run `pnpm --filter ./apps/server mastra:init` after migrations and before seed or runtime startup. App migrations own `app`; Mastra owns `mastra` and the embedding adapter owns `vectors`.
39
+ // m5k:ai:end
37
40
 
38
41
  ## Demo Credentials
39
42
 
40
43
  - Email: `admin@{{APP_SLUG}}.local`
41
44
  - Password: `password1234`
45
+
46
+ After sign-in, the index route (`/`) is the Posts list.
42
47
  // m5k:server:end
43
48
 
44
49
  ## Typical Commands
@@ -81,7 +86,6 @@ cp apps/shared/.env.production.example apps/shared/.env.production
81
86
  # fill in URLs, BETTER_AUTH_SECRET, REDIS_URL, …
82
87
 
83
88
  fly apps create {{APP_SLUG}}-app
84
- fly volumes create libsql_data --app {{APP_SLUG}}-app --region iad --size 1
85
89
  pnpm app:secrets
86
90
  pnpm app:deploy
87
91
  // m5k:server:end
@@ -95,7 +99,11 @@ pnpm landing:deploy
95
99
  `m5kdev-fly-secrets`). A missing env file errors with a copy-the-example
96
100
  message.
97
101
  // m5k:server:start
98
- `app:deploy` does the same for the product image. Product data lives on the
99
- `libsql_data` volume at `/app/data` (`DATABASE_URL=file:/app/data/local.db`).
100
- Redis is a runtime secret, not an image layer.
102
+ `app:deploy` does the same for the product image. Provision provider-neutral
103
+ PostgreSQL with pgvector in `public` and a transaction-mode PgBouncer endpoint.
104
+ Set `DATABASE_URL` to the runtime pool and `DATABASE_DIRECT_URL` to the direct
105
+ database endpoint. App migrations own `app`; Mastra owns `mastra` and vectors
106
+ use `vectors`. Run reviewed app migrations and, when AI is selected, `mastra:init`
107
+ through direct connections before starting the server. Redis and database URLs
108
+ are server secrets, not image layers. No local database volume is required.
101
109
  // m5k:server:end
@@ -1,13 +1,13 @@
1
- # runtime artifacts
2
- server/local.db*
3
- server/.e2e/
4
- server/.emails/
5
- server/uploads/
6
-
7
- # e2e output
8
- e2e/test-results/
9
- e2e/playwright-report/
10
-
11
- # expo
12
- expo/.expo/
13
- expo/dist/
1
+ # runtime artifacts
2
+ server/local.db*
3
+ server/.e2e/
4
+ server/.emails/
5
+ server/uploads/
6
+
7
+ # e2e output
8
+ e2e/test-results/
9
+ e2e/playwright-report/
10
+
11
+ # expo
12
+ expo/.expo/
13
+ expo/dist/
@@ -1,23 +1,23 @@
1
- {
2
- "name": "{{PACKAGE_SCOPE}}/e2e",
3
- "version": "0.0.0",
4
- "private": true,
5
- "scripts": {
6
- "build": "tsc --noEmit",
7
- "lint": "biome check .",
8
- "lint:fix": "biome check . --write",
9
- "check-types": "tsc --noEmit",
10
- "test:e2e": "playwright test",
11
- "test:e2e:expo": "playwright test --config playwright.expo.config.ts --headed"
12
- },
13
- "devDependencies": {
14
- "@m5kdev/backend": "catalog:m5kdev",
15
- "@m5kdev/commons": "catalog:m5kdev",
16
- "@m5kdev/config": "catalog:m5kdev",
17
- "@playwright/test": "catalog:m5kdev",
18
- "{{PACKAGE_SCOPE}}/shared": "workspace:*",
19
- "@trpc/client": "catalog:m5kdev",
20
- "@types/node": "catalog:m5kdev",
21
- "typescript": "catalog:m5kdev"
22
- }
23
- }
1
+ {
2
+ "name": "{{PACKAGE_SCOPE}}/e2e",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "build": "tsc --noEmit",
7
+ "lint": "biome check .",
8
+ "lint:fix": "biome check . --write",
9
+ "check-types": "tsc --noEmit",
10
+ "test:e2e": "playwright test",
11
+ "test:e2e:expo": "playwright test --config playwright.expo.config.ts --headed"
12
+ },
13
+ "devDependencies": {
14
+ "@m5kdev/backend": "catalog:m5kdev",
15
+ "@m5kdev/commons": "catalog:m5kdev",
16
+ "@m5kdev/config": "catalog:m5kdev",
17
+ "@playwright/test": "catalog:m5kdev",
18
+ "{{PACKAGE_SCOPE}}/shared": "workspace:*",
19
+ "@trpc/client": "catalog:m5kdev",
20
+ "@types/node": "catalog:m5kdev",
21
+ "typescript": "catalog:m5kdev"
22
+ }
23
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Shared webServer definitions for the Playwright configs. Each profile runs
3
- * an isolated stack: own port pair, own SQLite file, own email output dir, and
3
+ * an isolated stack: own port pair, own PostgreSQL database, own email output dir, and
4
4
  * own Redis DB index (queue names are identical across profiles — sharing a
5
5
  * Redis DB would let one profile's workers consume the other's jobs).
6
6
  */
@@ -48,11 +48,27 @@ function serverEnv(profile: E2EProfile, expo: boolean): Record<string, string> {
48
48
  const serverPort = expo ? ports.expoServer : ports.server;
49
49
  const webPort = expo ? ports.expoWeb : ports.web;
50
50
  const suffix = expo ? `${profile}-expo` : profile;
51
+ const databaseConnection = (key: string): string => {
52
+ const value = process.env[key];
53
+ if (!value) throw new Error(`${key} is required for PostgreSQL E2E tests`);
54
+ const url = new URL(value);
55
+ url.pathname = `${url.pathname}_e2e_${suffix.replaceAll("-", "_")}`;
56
+ return url.toString();
57
+ };
51
58
  return {
52
59
  PORT: String(serverPort),
53
60
  VITE_SERVER_URL: `http://127.0.0.1:${serverPort}`,
54
61
  VITE_APP_URL: `http://127.0.0.1:${webPort}`,
55
- DATABASE_URL: `file:./.e2e/${suffix}.db`,
62
+ DATABASE_URL: databaseConnection("E2E_DATABASE_URL"),
63
+ DATABASE_DIRECT_URL: databaseConnection("E2E_DATABASE_DIRECT_URL"),
64
+ DATABASE_SCHEMA: "app",
65
+ DATABASE_DISPOSABLE: process.env.E2E_DATABASE_DISPOSABLE ?? "false",
66
+ MASTRA_DATABASE_URL: "",
67
+ MASTRA_DATABASE_DIRECT_URL: "",
68
+ VECTOR_DATABASE_URL: "",
69
+ VECTOR_DATABASE_DIRECT_URL: "",
70
+ MASTRA_DATABASE_SCHEMA: "mastra",
71
+ VECTOR_DATABASE_SCHEMA: "vectors",
56
72
  EMAIL_OUTPUT_DIRECTORY: `.e2e/emails-${suffix}`,
57
73
  PROVISIONED_EMAIL_DOMAIN: "provisioned.auth-e2e.local",
58
74
  REDIS_URL: `redis://127.0.0.1:6379/${expo ? PORTS[profile].expoRedisDb : PORTS[profile].redisDb}`,
@@ -62,9 +78,6 @@ function serverEnv(profile: E2EProfile, expo: boolean): Record<string, string> {
62
78
  BETTER_AUTH_SECRET: "auth-e2e-local-secret-auth-e2e-local-secret",
63
79
  SYSTEM_NOTIFICATION_EMAIL: "ops@auth-e2e.local",
64
80
  NODE_ENV: "development",
65
- // hermetic: shared/.env Turso credentials must not leak into drizzle-kit
66
- TURSO_DATABASE_URL: "",
67
- TURSO_AUTH_TOKEN: "",
68
81
  // hermetic: a real key in the invoking shell would flip email mode to
69
82
  // "send" and the stored-email assertions would find nothing
70
83
  RESEND_API_KEY: "",
@@ -84,7 +97,10 @@ export function serverWebServer(profile: E2EProfile, { expo = false } = {}) {
84
97
  return {
85
98
  command: [
86
99
  `${SERVER_FILTER} tsx db.ts reset`,
87
- `${SERVER_FILTER} drizzle-kit migrate`,
100
+ `${SERVER_FILTER} tsx db.ts migrate`,
101
+ // m5k:ai:start
102
+ `${SERVER_FILTER} tsx mastra.ts`,
103
+ // m5k:ai:end
88
104
  `${SERVER_FILTER} tsx db.e2e.ts seed`,
89
105
  `${SERVER_FILTER} tsx src/index.ts`,
90
106
  ].join(" && "),