create-m5kdev 0.41.1 → 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 (29) hide show
  1. package/package.json +1 -1
  2. package/templates/minimal-app/CLAUDE.md.tpl +2 -2
  3. package/templates/minimal-app/README.md.tpl +12 -6
  4. package/templates/minimal-app/apps/e2e/servers.ts.tpl +22 -6
  5. package/templates/minimal-app/apps/server/AGENTS.md.tpl +1 -1
  6. package/templates/minimal-app/apps/server/drizzle-postgresql/0000_glorious_warhawk.sql +522 -0
  7. package/templates/minimal-app/apps/server/drizzle-postgresql/meta/0000_snapshot.json.tpl +3555 -0
  8. package/templates/minimal-app/apps/server/drizzle-postgresql/meta/_journal.json.tpl +13 -0
  9. package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +1 -1
  10. package/templates/minimal-app/apps/server/mastra.ts.tpl +42 -0
  11. package/templates/minimal-app/apps/server/package.json.tpl +62 -57
  12. package/templates/minimal-app/apps/server/src/app.ts.tpl +20 -16
  13. package/templates/minimal-app/apps/server/src/drizzle.schema.ts.tpl +2 -0
  14. package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl +48 -0
  15. package/templates/minimal-app/apps/server/src/modules/conversation/conversation.persistence.ts.tpl +47 -0
  16. package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +7 -6
  17. package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +2 -2
  18. package/templates/minimal-app/apps/shared/.env.example.tpl +6 -3
  19. package/templates/minimal-app/apps/shared/.env.production.example.tpl +7 -2
  20. package/templates/minimal-app/apps/shared/.env.tpl +6 -3
  21. package/templates/minimal-app/apps/shared/Dockerfile.pgbouncer +6 -0
  22. package/templates/minimal-app/apps/shared/compose.yaml.tpl +27 -0
  23. package/templates/minimal-app/apps/shared/fly.toml.tpl +0 -4
  24. package/templates/minimal-app/apps/shared/pgbouncer.ini +13 -0
  25. package/templates/minimal-app/apps/shared/pgbouncer.users +1 -0
  26. package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +1 -1
  27. package/templates/minimal-app/package.json.tpl +5 -0
  28. package/templates/minimal-app/pnpm-workspace.yaml.tpl +16 -13
  29. package/templates/minimal-app/template.manifest.json +9 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-m5kdev",
3
- "version": "0.41.1",
3
+ "version": "0.43.0",
4
4
  "license": "GPL-3.0-only",
5
5
  "engines": {
6
6
  "node": ">=24"
@@ -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
@@ -32,8 +32,11 @@ 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
 
@@ -83,7 +86,6 @@ cp apps/shared/.env.production.example apps/shared/.env.production
83
86
  # fill in URLs, BETTER_AUTH_SECRET, REDIS_URL, …
84
87
 
85
88
  fly apps create {{APP_SLUG}}-app
86
- fly volumes create libsql_data --app {{APP_SLUG}}-app --region iad --size 1
87
89
  pnpm app:secrets
88
90
  pnpm app:deploy
89
91
  // m5k:server:end
@@ -97,7 +99,11 @@ pnpm landing:deploy
97
99
  `m5kdev-fly-secrets`). A missing env file errors with a copy-the-example
98
100
  message.
99
101
  // m5k:server:start
100
- `app:deploy` does the same for the product image. Product data lives on the
101
- `libsql_data` volume at `/app/data` (`DATABASE_URL=file:/app/data/local.db`).
102
- 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.
103
109
  // m5k:server:end
@@ -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(" && "),
@@ -35,7 +35,7 @@ apps/server/src/modules/<module>/
35
35
  // m5k:ai:start
36
36
  - `AIModule` is registered in `app.ts` with an app-owned Mastra Agent (`assistant`). Conversation HTTP lives on `/ai`. Set `OPENROUTER_API_KEY` for live replies. Do not add a Starter service wrapper around `AIService` chat.
37
37
  // m5k:ai:end
38
- - Database commands live in server `db.ts` (`pnpm drizzle:reset`, `drizzle:sync`, `drizzle:seed`) and call Kernel `runDb` — they must not import `app.ts`.
38
+ - Database commands live in server `db.ts` and call Kernel `runDb` through `DATABASE_DIRECT_URL`, without importing `app.ts`. Runtime uses `DATABASE_URL` through PgBouncer. Reset requires a disposable environment and preserves Mastra/vector data.
39
39
 
40
40
  ## Avoid Trivial Service Delegation
41
41
 
@@ -0,0 +1,522 @@
1
+ CREATE SCHEMA "app";
2
+ --> statement-breakpoint
3
+ CREATE TABLE "app"."account_claim_magic_links" (
4
+ "id" text PRIMARY KEY NOT NULL,
5
+ "claim_id" text NOT NULL,
6
+ "user_id" text NOT NULL,
7
+ "email" text NOT NULL,
8
+ "token" text NOT NULL,
9
+ "url" text NOT NULL,
10
+ "expires_at" timestamp with time zone,
11
+ "created_at" timestamp with time zone NOT NULL
12
+ );
13
+ --> statement-breakpoint
14
+ CREATE TABLE "app"."account_claims" (
15
+ "id" text PRIMARY KEY NOT NULL,
16
+ "claim_user_id" text NOT NULL,
17
+ "status" text NOT NULL,
18
+ "code" text,
19
+ "expires_at" timestamp with time zone,
20
+ "claimed_at" timestamp with time zone,
21
+ "claimed_email" text,
22
+ "created_at" timestamp with time zone NOT NULL,
23
+ "updated_at" timestamp with time zone
24
+ );
25
+ --> statement-breakpoint
26
+ CREATE TABLE "app"."accounts" (
27
+ "id" text PRIMARY KEY NOT NULL,
28
+ "account_id" text NOT NULL,
29
+ "issuer" text DEFAULT 'local:credential' NOT NULL,
30
+ "provider_id" text NOT NULL,
31
+ "user_id" text NOT NULL,
32
+ "access_token" text,
33
+ "refresh_token" text,
34
+ "id_token" text,
35
+ "access_token_expires_at" timestamp with time zone,
36
+ "refresh_token_expires_at" timestamp with time zone,
37
+ "scope" text,
38
+ "password" text,
39
+ "created_at" timestamp with time zone NOT NULL,
40
+ "updated_at" timestamp with time zone NOT NULL
41
+ );
42
+ --> statement-breakpoint
43
+ CREATE TABLE "app"."ai_usage" (
44
+ "id" text PRIMARY KEY NOT NULL,
45
+ "user_id" text,
46
+ "member_id" text,
47
+ "team_id" text,
48
+ "organization_id" text,
49
+ "feature" text NOT NULL,
50
+ "provider" text NOT NULL,
51
+ "model" text NOT NULL,
52
+ "input_tokens" integer,
53
+ "output_tokens" integer,
54
+ "total_tokens" integer,
55
+ "cost" double precision,
56
+ "trace_id" text,
57
+ "created_at" timestamp with time zone NOT NULL,
58
+ "metadata" jsonb
59
+ );
60
+ --> statement-breakpoint
61
+ CREATE TABLE "app"."apikeys" (
62
+ "id" text PRIMARY KEY NOT NULL,
63
+ "name" text,
64
+ "start" text,
65
+ "prefix" text,
66
+ "key" text NOT NULL,
67
+ "config_id" text DEFAULT 'default' NOT NULL,
68
+ "reference_id" text NOT NULL,
69
+ "user_id" text,
70
+ "refill_interval" integer,
71
+ "refill_amount" integer,
72
+ "last_refill_at" timestamp with time zone,
73
+ "enabled" boolean NOT NULL,
74
+ "rate_limit_enabled" boolean NOT NULL,
75
+ "rate_limit_time_window" integer,
76
+ "rate_limit_max" integer,
77
+ "request_count" integer NOT NULL,
78
+ "remaining" integer,
79
+ "last_request" timestamp with time zone,
80
+ "expires_at" timestamp with time zone,
81
+ "created_at" timestamp with time zone NOT NULL,
82
+ "updated_at" timestamp with time zone NOT NULL,
83
+ "permissions" text,
84
+ "metadata" text
85
+ );
86
+ --> statement-breakpoint
87
+ CREATE TABLE "app"."chats" (
88
+ "id" text PRIMARY KEY NOT NULL,
89
+ "user_id" text NOT NULL,
90
+ "title" text,
91
+ "type" text,
92
+ "conversation" jsonb,
93
+ "created_at" timestamp with time zone,
94
+ "updated_at" timestamp with time zone NOT NULL
95
+ );
96
+ --> statement-breakpoint
97
+ CREATE TABLE "app"."files" (
98
+ "id" text PRIMARY KEY NOT NULL,
99
+ "created_at" timestamp with time zone NOT NULL,
100
+ "updated_at" timestamp with time zone,
101
+ "deleted_at" timestamp with time zone,
102
+ "user_id" text,
103
+ "member_id" text,
104
+ "organization_id" text,
105
+ "bucket" text NOT NULL,
106
+ "key" text NOT NULL,
107
+ "original_name" text NOT NULL,
108
+ "original_extension" text,
109
+ "content_type" text NOT NULL,
110
+ "size_bytes" bigint,
111
+ "etag" text,
112
+ "checksum_sha256" text,
113
+ "metadata" jsonb,
114
+ "status" text NOT NULL,
115
+ "uploaded_at" timestamp with time zone
116
+ );
117
+ --> statement-breakpoint
118
+ CREATE TABLE "app"."invitations" (
119
+ "id" text PRIMARY KEY NOT NULL,
120
+ "organization_id" text NOT NULL,
121
+ "member_id" text,
122
+ "email" text NOT NULL,
123
+ "role" text,
124
+ "status" text NOT NULL,
125
+ "created_at" timestamp with time zone NOT NULL,
126
+ "expires_at" timestamp with time zone NOT NULL,
127
+ "inviter_id" text NOT NULL
128
+ );
129
+ --> statement-breakpoint
130
+ CREATE TABLE "app"."jwks" (
131
+ "id" text PRIMARY KEY NOT NULL,
132
+ "public_key" text NOT NULL,
133
+ "private_key" text NOT NULL,
134
+ "created_at" timestamp with time zone NOT NULL,
135
+ "expires_at" timestamp with time zone,
136
+ "alg" text,
137
+ "crv" text
138
+ );
139
+ --> statement-breakpoint
140
+ CREATE TABLE "app"."mcp_allowlist_entries" (
141
+ "id" text PRIMARY KEY NOT NULL,
142
+ "oauth_client_id" text NOT NULL,
143
+ "user_id" text NOT NULL,
144
+ "organization_id" text NOT NULL,
145
+ "created_at" timestamp with time zone NOT NULL
146
+ );
147
+ --> statement-breakpoint
148
+ CREATE TABLE "app"."members" (
149
+ "id" text PRIMARY KEY NOT NULL,
150
+ "organization_id" text NOT NULL,
151
+ "user_id" text,
152
+ "email" text,
153
+ "name" text DEFAULT '' NOT NULL,
154
+ "image" text,
155
+ "role" text NOT NULL,
156
+ "created_at" timestamp with time zone NOT NULL,
157
+ "deleted_at" timestamp with time zone,
158
+ "preferences" jsonb DEFAULT '{}'::jsonb,
159
+ "metadata" jsonb DEFAULT '{}'::jsonb,
160
+ "onboarding" integer,
161
+ "flags" text[] DEFAULT '{}'
162
+ );
163
+ --> statement-breakpoint
164
+ CREATE TABLE "app"."notification_devices" (
165
+ "id" text PRIMARY KEY NOT NULL,
166
+ "user_id" text NOT NULL,
167
+ "platform" text NOT NULL,
168
+ "endpoint" text,
169
+ "subscription" jsonb,
170
+ "token" text,
171
+ "label" text,
172
+ "enabled" boolean DEFAULT true NOT NULL,
173
+ "created_at" timestamp with time zone NOT NULL,
174
+ "updated_at" timestamp with time zone NOT NULL,
175
+ CONSTRAINT "notification_devices_endpoint_unique" UNIQUE("endpoint"),
176
+ CONSTRAINT "notification_devices_token_unique" UNIQUE("token")
177
+ );
178
+ --> statement-breakpoint
179
+ CREATE TABLE "app"."notification_preferences" (
180
+ "id" text PRIMARY KEY NOT NULL,
181
+ "member_id" text NOT NULL,
182
+ "kind" text NOT NULL,
183
+ "channel" text NOT NULL,
184
+ "created_at" timestamp with time zone NOT NULL,
185
+ "updated_at" timestamp with time zone NOT NULL
186
+ );
187
+ --> statement-breakpoint
188
+ CREATE TABLE "app"."notification_send_logs" (
189
+ "id" text PRIMARY KEY NOT NULL,
190
+ "batch_id" text NOT NULL,
191
+ "notification_id" text NOT NULL,
192
+ "user_id" text NOT NULL,
193
+ "device_id" text,
194
+ "channel" text NOT NULL,
195
+ "provider" text,
196
+ "title" text NOT NULL,
197
+ "body" text NOT NULL,
198
+ "data" jsonb,
199
+ "status" text NOT NULL,
200
+ "error" text,
201
+ "job_id" text,
202
+ "created_at" timestamp with time zone NOT NULL,
203
+ "updated_at" timestamp with time zone NOT NULL
204
+ );
205
+ --> statement-breakpoint
206
+ CREATE TABLE "app"."notifications" (
207
+ "id" text PRIMARY KEY NOT NULL,
208
+ "member_id" text NOT NULL,
209
+ "user_id" text NOT NULL,
210
+ "kind" text NOT NULL,
211
+ "title" text NOT NULL,
212
+ "body" text NOT NULL,
213
+ "data" jsonb,
214
+ "visible_in_inbox" boolean DEFAULT false NOT NULL,
215
+ "armed_channels" text[] DEFAULT '{}' NOT NULL,
216
+ "web_pushed_at" timestamp with time zone,
217
+ "mobile_pushed_at" timestamp with time zone,
218
+ "emailed_at" timestamp with time zone,
219
+ "read_at" timestamp with time zone,
220
+ "created_at" timestamp with time zone NOT NULL,
221
+ "updated_at" timestamp with time zone NOT NULL
222
+ );
223
+ --> statement-breakpoint
224
+ CREATE TABLE "app"."oauth_access_tokens" (
225
+ "id" text PRIMARY KEY NOT NULL,
226
+ "token" text,
227
+ "client_id" text NOT NULL,
228
+ "session_id" text,
229
+ "user_id" text,
230
+ "reference_id" text,
231
+ "authorization_code_id" text,
232
+ "resources" text[],
233
+ "requested_user_info_claims" text[],
234
+ "refresh_id" text,
235
+ "expires_at" timestamp with time zone NOT NULL,
236
+ "created_at" timestamp with time zone NOT NULL,
237
+ "revoked" timestamp with time zone,
238
+ "confirmation" jsonb,
239
+ "scopes" text[] NOT NULL,
240
+ CONSTRAINT "oauth_access_tokens_token_unique" UNIQUE("token")
241
+ );
242
+ --> statement-breakpoint
243
+ CREATE TABLE "app"."oauth_client_assertions" (
244
+ "id" text PRIMARY KEY NOT NULL,
245
+ "expires_at" timestamp with time zone NOT NULL
246
+ );
247
+ --> statement-breakpoint
248
+ CREATE TABLE "app"."oauth_client_resources" (
249
+ "id" text PRIMARY KEY NOT NULL,
250
+ "client_id" text NOT NULL,
251
+ "resource_id" text NOT NULL,
252
+ "metadata" jsonb,
253
+ "created_at" timestamp with time zone
254
+ );
255
+ --> statement-breakpoint
256
+ CREATE TABLE "app"."oauth_clients" (
257
+ "id" text PRIMARY KEY NOT NULL,
258
+ "client_id" text NOT NULL,
259
+ "client_secret" text,
260
+ "client_discovery_id" text,
261
+ "disabled" boolean DEFAULT false,
262
+ "skip_consent" boolean,
263
+ "enable_end_session" boolean,
264
+ "subject_type" text,
265
+ "scopes" text[],
266
+ "client_credentials_scopes" text[] DEFAULT '{}',
267
+ "user_id" text,
268
+ "created_at" timestamp with time zone,
269
+ "updated_at" timestamp with time zone,
270
+ "name" text,
271
+ "uri" text,
272
+ "icon" text,
273
+ "contacts" text[],
274
+ "tos" text,
275
+ "policy" text,
276
+ "software_id" text,
277
+ "software_version" text,
278
+ "software_statement" text,
279
+ "redirect_uris" text[] NOT NULL,
280
+ "post_logout_redirect_uris" text[],
281
+ "backchannel_logout_uri" text,
282
+ "backchannel_logout_session_required" boolean,
283
+ "token_endpoint_auth_method" text,
284
+ "application_type" text,
285
+ "jwks" text,
286
+ "jwks_uri" text,
287
+ "grant_types" text[],
288
+ "response_types" text[],
289
+ "require_pkce" boolean,
290
+ "dpop_bound_access_tokens" boolean DEFAULT false,
291
+ "reference_id" text,
292
+ "metadata" jsonb,
293
+ CONSTRAINT "oauth_clients_client_id_unique" UNIQUE("client_id")
294
+ );
295
+ --> statement-breakpoint
296
+ CREATE TABLE "app"."oauth_consents" (
297
+ "id" text PRIMARY KEY NOT NULL,
298
+ "client_id" text NOT NULL,
299
+ "user_id" text,
300
+ "reference_id" text,
301
+ "resources" text[],
302
+ "requested_user_info_claims" text[],
303
+ "scopes" text[] NOT NULL,
304
+ "created_at" timestamp with time zone NOT NULL,
305
+ "updated_at" timestamp with time zone NOT NULL
306
+ );
307
+ --> statement-breakpoint
308
+ CREATE TABLE "app"."oauth_refresh_tokens" (
309
+ "id" text PRIMARY KEY NOT NULL,
310
+ "token" text NOT NULL,
311
+ "client_id" text NOT NULL,
312
+ "session_id" text,
313
+ "user_id" text NOT NULL,
314
+ "reference_id" text,
315
+ "authorization_code_id" text,
316
+ "resources" text[],
317
+ "requested_user_info_claims" text[],
318
+ "expires_at" timestamp with time zone NOT NULL,
319
+ "created_at" timestamp with time zone NOT NULL,
320
+ "revoked" timestamp with time zone,
321
+ "rotated_at" timestamp with time zone,
322
+ "rotation_replay_response" text,
323
+ "rotation_replay_expires_at" timestamp with time zone,
324
+ "auth_time" timestamp with time zone,
325
+ "confirmation" jsonb,
326
+ "scopes" text[] NOT NULL,
327
+ CONSTRAINT "oauth_refresh_tokens_token_unique" UNIQUE("token")
328
+ );
329
+ --> statement-breakpoint
330
+ CREATE TABLE "app"."oauth_resources" (
331
+ "id" text PRIMARY KEY NOT NULL,
332
+ "identifier" text NOT NULL,
333
+ "name" text NOT NULL,
334
+ "access_token_ttl" integer,
335
+ "refresh_token_ttl" integer,
336
+ "signing_algorithm" text,
337
+ "signing_key_id" text,
338
+ "allowed_scopes" text[],
339
+ "custom_claims" jsonb,
340
+ "dpop_bound_access_tokens_required" boolean DEFAULT false,
341
+ "disabled" boolean DEFAULT false,
342
+ "created_at" timestamp with time zone,
343
+ "updated_at" timestamp with time zone,
344
+ "policy_version" integer DEFAULT 1,
345
+ "metadata" jsonb,
346
+ CONSTRAINT "oauth_resources_identifier_unique" UNIQUE("identifier")
347
+ );
348
+ --> statement-breakpoint
349
+ CREATE TABLE "app"."organizations" (
350
+ "id" text PRIMARY KEY NOT NULL,
351
+ "name" text NOT NULL,
352
+ "slug" text,
353
+ "logo" text,
354
+ "type" text,
355
+ "parent_id" text,
356
+ "created_at" timestamp with time zone NOT NULL,
357
+ "onboarding" integer,
358
+ "preferences" jsonb DEFAULT '{}'::jsonb,
359
+ "metadata" jsonb DEFAULT '{}'::jsonb,
360
+ "flags" text[] DEFAULT '{}',
361
+ "locale" text,
362
+ "currency" text,
363
+ "stripe_customer_id" text,
364
+ "stripe_sandbox_customer_id" text,
365
+ "billing_exempt" boolean DEFAULT false NOT NULL,
366
+ "allow_cardless_trial" boolean DEFAULT false NOT NULL,
367
+ "cardless_trial_consumed" jsonb DEFAULT '{}'::jsonb,
368
+ "closed_at" timestamp with time zone,
369
+ CONSTRAINT "organizations_slug_unique" UNIQUE("slug"),
370
+ CONSTRAINT "organizations_stripe_customer_id_unique" UNIQUE("stripe_customer_id"),
371
+ CONSTRAINT "organizations_stripe_sandbox_customer_id_unique" UNIQUE("stripe_sandbox_customer_id")
372
+ );
373
+ --> statement-breakpoint
374
+ CREATE TABLE "app"."posts" (
375
+ "id" text PRIMARY KEY NOT NULL,
376
+ "author_user_id" text,
377
+ "member_id" text,
378
+ "organization_id" text,
379
+ "team_id" text,
380
+ "title" text NOT NULL,
381
+ "slug" text NOT NULL,
382
+ "excerpt" text,
383
+ "content" text NOT NULL,
384
+ "status" text DEFAULT 'draft' NOT NULL,
385
+ "published_at" timestamp with time zone,
386
+ "created_at" timestamp with time zone NOT NULL,
387
+ "updated_at" timestamp with time zone,
388
+ "deleted_at" timestamp with time zone,
389
+ CONSTRAINT "posts_slug_unique" UNIQUE("slug")
390
+ );
391
+ --> statement-breakpoint
392
+ CREATE TABLE "app"."sessions" (
393
+ "id" text PRIMARY KEY NOT NULL,
394
+ "expires_at" timestamp with time zone NOT NULL,
395
+ "token" text NOT NULL,
396
+ "created_at" timestamp with time zone NOT NULL,
397
+ "updated_at" timestamp with time zone NOT NULL,
398
+ "ip_address" text,
399
+ "user_agent" text,
400
+ "user_id" text NOT NULL,
401
+ "impersonated_by" text,
402
+ "active_organization_id" text,
403
+ "active_organization_member_id" text,
404
+ "active_organization_role" text,
405
+ "active_organization_type" text,
406
+ CONSTRAINT "sessions_token_unique" UNIQUE("token")
407
+ );
408
+ --> statement-breakpoint
409
+ CREATE TABLE "app"."users" (
410
+ "id" text PRIMARY KEY NOT NULL,
411
+ "name" text NOT NULL,
412
+ "email" text NOT NULL,
413
+ "email_verified" boolean NOT NULL,
414
+ "image" text,
415
+ "created_at" timestamp with time zone NOT NULL,
416
+ "updated_at" timestamp with time zone NOT NULL,
417
+ "role" text,
418
+ "banned" boolean,
419
+ "ban_reason" text,
420
+ "ban_expires" timestamp with time zone,
421
+ "preferences" jsonb DEFAULT '{}'::jsonb,
422
+ "metadata" jsonb DEFAULT '{}'::jsonb,
423
+ "onboarding" integer,
424
+ "flags" text[] DEFAULT '{}',
425
+ "locale" text,
426
+ "closed_at" timestamp with time zone,
427
+ CONSTRAINT "users_email_unique" UNIQUE("email")
428
+ );
429
+ --> statement-breakpoint
430
+ CREATE TABLE "app"."verifications" (
431
+ "id" text PRIMARY KEY NOT NULL,
432
+ "identifier" text NOT NULL,
433
+ "value" text NOT NULL,
434
+ "expires_at" timestamp with time zone NOT NULL,
435
+ "created_at" timestamp with time zone,
436
+ "updated_at" timestamp with time zone
437
+ );
438
+ --> statement-breakpoint
439
+ CREATE TABLE "app"."waitlist" (
440
+ "id" text PRIMARY KEY NOT NULL,
441
+ "name" text,
442
+ "email" text,
443
+ "created_at" timestamp with time zone NOT NULL,
444
+ "updated_at" timestamp with time zone,
445
+ "status" text DEFAULT 'WAITLIST' NOT NULL,
446
+ "code" text,
447
+ "expires_at" timestamp with time zone,
448
+ "user_id" text
449
+ );
450
+ --> statement-breakpoint
451
+ CREATE TABLE "app"."workflows" (
452
+ "id" text PRIMARY KEY NOT NULL,
453
+ "user_id" text,
454
+ "job_id" text NOT NULL,
455
+ "job_name" text NOT NULL,
456
+ "queue_name" text NOT NULL,
457
+ "timeout" integer,
458
+ "tags" text[],
459
+ "input" jsonb,
460
+ "output" jsonb,
461
+ "status" text NOT NULL,
462
+ "error" text,
463
+ "retries" integer DEFAULT 0 NOT NULL,
464
+ "finished_at" timestamp with time zone,
465
+ "processed_at" timestamp with time zone,
466
+ "created_at" timestamp with time zone NOT NULL,
467
+ "updated_at" timestamp with time zone NOT NULL,
468
+ CONSTRAINT "workflows_job_id_unique" UNIQUE("job_id")
469
+ );
470
+ --> statement-breakpoint
471
+ ALTER TABLE "app"."account_claim_magic_links" ADD CONSTRAINT "account_claim_magic_links_claim_id_account_claims_id_fk" FOREIGN KEY ("claim_id") REFERENCES "app"."account_claims"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
472
+ ALTER TABLE "app"."account_claim_magic_links" ADD CONSTRAINT "account_claim_magic_links_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
473
+ ALTER TABLE "app"."account_claims" ADD CONSTRAINT "account_claims_claim_user_id_users_id_fk" FOREIGN KEY ("claim_user_id") REFERENCES "app"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
474
+ ALTER TABLE "app"."accounts" ADD CONSTRAINT "accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
475
+ ALTER TABLE "app"."ai_usage" ADD CONSTRAINT "ai_usage_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
476
+ ALTER TABLE "app"."ai_usage" ADD CONSTRAINT "ai_usage_member_id_members_id_fk" FOREIGN KEY ("member_id") REFERENCES "app"."members"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
477
+ ALTER TABLE "app"."ai_usage" ADD CONSTRAINT "ai_usage_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "app"."organizations"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
478
+ ALTER TABLE "app"."apikeys" ADD CONSTRAINT "apikeys_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
479
+ ALTER TABLE "app"."chats" ADD CONSTRAINT "chats_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
480
+ ALTER TABLE "app"."files" ADD CONSTRAINT "files_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
481
+ ALTER TABLE "app"."files" ADD CONSTRAINT "files_member_id_members_id_fk" FOREIGN KEY ("member_id") REFERENCES "app"."members"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
482
+ ALTER TABLE "app"."files" ADD CONSTRAINT "files_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "app"."organizations"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
483
+ ALTER TABLE "app"."invitations" ADD CONSTRAINT "invitations_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "app"."organizations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
484
+ ALTER TABLE "app"."invitations" ADD CONSTRAINT "invitations_member_id_members_id_fk" FOREIGN KEY ("member_id") REFERENCES "app"."members"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
485
+ ALTER TABLE "app"."invitations" ADD CONSTRAINT "invitations_inviter_id_users_id_fk" FOREIGN KEY ("inviter_id") REFERENCES "app"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
486
+ ALTER TABLE "app"."mcp_allowlist_entries" ADD CONSTRAINT "mcp_allowlist_entries_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
487
+ ALTER TABLE "app"."mcp_allowlist_entries" ADD CONSTRAINT "mcp_allowlist_entries_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "app"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
488
+ ALTER TABLE "app"."members" ADD CONSTRAINT "members_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "app"."organizations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
489
+ ALTER TABLE "app"."members" ADD CONSTRAINT "members_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
490
+ ALTER TABLE "app"."notification_devices" ADD CONSTRAINT "notification_devices_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
491
+ ALTER TABLE "app"."notification_preferences" ADD CONSTRAINT "notification_preferences_member_id_members_id_fk" FOREIGN KEY ("member_id") REFERENCES "app"."members"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
492
+ ALTER TABLE "app"."notification_send_logs" ADD CONSTRAINT "notification_send_logs_notification_id_notifications_id_fk" FOREIGN KEY ("notification_id") REFERENCES "app"."notifications"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
493
+ ALTER TABLE "app"."notification_send_logs" ADD CONSTRAINT "notification_send_logs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
494
+ ALTER TABLE "app"."notification_send_logs" ADD CONSTRAINT "notification_send_logs_device_id_notification_devices_id_fk" FOREIGN KEY ("device_id") REFERENCES "app"."notification_devices"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
495
+ ALTER TABLE "app"."notifications" ADD CONSTRAINT "notifications_member_id_members_id_fk" FOREIGN KEY ("member_id") REFERENCES "app"."members"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
496
+ ALTER TABLE "app"."notifications" ADD CONSTRAINT "notifications_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
497
+ ALTER TABLE "app"."oauth_access_tokens" ADD CONSTRAINT "oauth_access_tokens_client_id_oauth_clients_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "app"."oauth_clients"("client_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
498
+ ALTER TABLE "app"."oauth_access_tokens" ADD CONSTRAINT "oauth_access_tokens_session_id_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "app"."sessions"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
499
+ ALTER TABLE "app"."oauth_access_tokens" ADD CONSTRAINT "oauth_access_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
500
+ ALTER TABLE "app"."oauth_access_tokens" ADD CONSTRAINT "oauth_access_tokens_refresh_id_oauth_refresh_tokens_id_fk" FOREIGN KEY ("refresh_id") REFERENCES "app"."oauth_refresh_tokens"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
501
+ ALTER TABLE "app"."oauth_client_resources" ADD CONSTRAINT "oauth_client_resources_client_id_oauth_clients_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "app"."oauth_clients"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
502
+ ALTER TABLE "app"."oauth_client_resources" ADD CONSTRAINT "oauth_client_resources_resource_id_oauth_resources_identifier_fk" FOREIGN KEY ("resource_id") REFERENCES "app"."oauth_resources"("identifier") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
503
+ ALTER TABLE "app"."oauth_clients" ADD CONSTRAINT "oauth_clients_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
504
+ ALTER TABLE "app"."oauth_consents" ADD CONSTRAINT "oauth_consents_client_id_oauth_clients_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "app"."oauth_clients"("client_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
505
+ ALTER TABLE "app"."oauth_consents" ADD CONSTRAINT "oauth_consents_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
506
+ ALTER TABLE "app"."oauth_refresh_tokens" ADD CONSTRAINT "oauth_refresh_tokens_client_id_oauth_clients_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "app"."oauth_clients"("client_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
507
+ ALTER TABLE "app"."oauth_refresh_tokens" ADD CONSTRAINT "oauth_refresh_tokens_session_id_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "app"."sessions"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
508
+ ALTER TABLE "app"."oauth_refresh_tokens" ADD CONSTRAINT "oauth_refresh_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
509
+ ALTER TABLE "app"."organizations" ADD CONSTRAINT "organizations_parent_id_organizations_id_fk" FOREIGN KEY ("parent_id") REFERENCES "app"."organizations"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
510
+ ALTER TABLE "app"."posts" ADD CONSTRAINT "posts_author_user_id_users_id_fk" FOREIGN KEY ("author_user_id") REFERENCES "app"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
511
+ ALTER TABLE "app"."posts" ADD CONSTRAINT "posts_member_id_members_id_fk" FOREIGN KEY ("member_id") REFERENCES "app"."members"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
512
+ ALTER TABLE "app"."posts" ADD CONSTRAINT "posts_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "app"."organizations"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
513
+ ALTER TABLE "app"."sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
514
+ ALTER TABLE "app"."waitlist" ADD CONSTRAINT "waitlist_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
515
+ ALTER TABLE "app"."workflows" ADD CONSTRAINT "workflows_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "app"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
516
+ CREATE UNIQUE INDEX "accounts_issuer_account_id_unique" ON "app"."accounts" USING btree ("issuer","account_id");--> statement-breakpoint
517
+ CREATE UNIQUE INDEX "files_bucket_key_unique" ON "app"."files" USING btree ("bucket","key");--> statement-breakpoint
518
+ CREATE UNIQUE INDEX "mcp_allowlist_client_user_org_unique" ON "app"."mcp_allowlist_entries" USING btree ("oauth_client_id","user_id","organization_id");--> statement-breakpoint
519
+ CREATE UNIQUE INDEX "members_attached_user_organization_unique" ON "app"."members" USING btree ("user_id","organization_id") WHERE "app"."members"."user_id" is not null;--> statement-breakpoint
520
+ CREATE UNIQUE INDEX "members_live_pending_email_organization_unique" ON "app"."members" USING btree ("email","organization_id") WHERE "app"."members"."user_id" is null and "app"."members"."deleted_at" is null;--> statement-breakpoint
521
+ CREATE UNIQUE INDEX "notification_preferences_member_kind_channel_unique" ON "app"."notification_preferences" USING btree ("member_id","kind","channel");--> statement-breakpoint
522
+ CREATE UNIQUE INDEX "oauth_client_resources_client_resource_unique" ON "app"."oauth_client_resources" USING btree ("client_id","resource_id");