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.
- package/package.json +1 -1
- package/templates/minimal-app/CLAUDE.md.tpl +2 -2
- package/templates/minimal-app/README.md.tpl +12 -6
- package/templates/minimal-app/apps/e2e/servers.ts.tpl +22 -6
- package/templates/minimal-app/apps/server/AGENTS.md.tpl +1 -1
- package/templates/minimal-app/apps/server/drizzle-postgresql/0000_glorious_warhawk.sql +522 -0
- package/templates/minimal-app/apps/server/drizzle-postgresql/meta/0000_snapshot.json.tpl +3555 -0
- package/templates/minimal-app/apps/server/drizzle-postgresql/meta/_journal.json.tpl +13 -0
- package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +1 -1
- package/templates/minimal-app/apps/server/mastra.ts.tpl +42 -0
- package/templates/minimal-app/apps/server/package.json.tpl +62 -57
- package/templates/minimal-app/apps/server/src/app.ts.tpl +20 -16
- package/templates/minimal-app/apps/server/src/drizzle.schema.ts.tpl +2 -0
- package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl +48 -0
- package/templates/minimal-app/apps/server/src/modules/conversation/conversation.persistence.ts.tpl +47 -0
- package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +7 -6
- package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +2 -2
- package/templates/minimal-app/apps/shared/.env.example.tpl +6 -3
- package/templates/minimal-app/apps/shared/.env.production.example.tpl +7 -2
- package/templates/minimal-app/apps/shared/.env.tpl +6 -3
- package/templates/minimal-app/apps/shared/Dockerfile.pgbouncer +6 -0
- package/templates/minimal-app/apps/shared/compose.yaml.tpl +27 -0
- package/templates/minimal-app/apps/shared/fly.toml.tpl +0 -4
- package/templates/minimal-app/apps/shared/pgbouncer.ini +13 -0
- package/templates/minimal-app/apps/shared/pgbouncer.users +1 -0
- package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +1 -1
- package/templates/minimal-app/package.json.tpl +5 -0
- package/templates/minimal-app/pnpm-workspace.yaml.tpl +16 -13
- package/templates/minimal-app/template.manifest.json +9 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { PgVector } from "@mastra/pg";
|
|
2
|
+
import {
|
|
3
|
+
conversationEmbeddingDimensions,
|
|
4
|
+
conversationEmbeddingIndex,
|
|
5
|
+
conversationEmbeddingMetric,
|
|
6
|
+
createConversationStorage,
|
|
7
|
+
createConversationVectorProvisioner,
|
|
8
|
+
} from "./src/modules/conversation/conversation.persistence";
|
|
9
|
+
|
|
10
|
+
async function initialize(): Promise<void> {
|
|
11
|
+
const storage = createConversationStorage(true);
|
|
12
|
+
let vector: PgVector | undefined;
|
|
13
|
+
try {
|
|
14
|
+
vector = createConversationVectorProvisioner();
|
|
15
|
+
const extension = await vector.pool.query<{ schema: string }>(
|
|
16
|
+
"SELECT n.nspname AS schema FROM pg_extension e JOIN pg_namespace n ON n.oid = e.extnamespace WHERE e.extname = 'vector'"
|
|
17
|
+
);
|
|
18
|
+
if (extension.rows[0]?.schema !== "public") {
|
|
19
|
+
throw new Error(
|
|
20
|
+
"Provision pgvector in the public schema before Mastra initialization (required for transaction pooling)"
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
await storage.init();
|
|
24
|
+
await vector.createIndex({
|
|
25
|
+
indexName: conversationEmbeddingIndex,
|
|
26
|
+
dimension: conversationEmbeddingDimensions,
|
|
27
|
+
metric: conversationEmbeddingMetric,
|
|
28
|
+
buildIndex: false,
|
|
29
|
+
});
|
|
30
|
+
} finally {
|
|
31
|
+
try {
|
|
32
|
+
await vector?.disconnect();
|
|
33
|
+
} finally {
|
|
34
|
+
await storage.close();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
void initialize().catch((error) => {
|
|
40
|
+
console.error(error);
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
});
|
|
@@ -1,57 +1,62 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "{{PACKAGE_SCOPE}}/server",
|
|
3
|
-
"version": "0.0.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "tsx watch --env-file=../shared/.env src/index.ts",
|
|
8
|
-
"build": "tsup",
|
|
9
|
-
"start": "node dist/index.js",
|
|
10
|
-
"lint": "biome check .",
|
|
11
|
-
"lint:fix": "biome check . --write",
|
|
12
|
-
"check-types": "tsc --noEmit",
|
|
13
|
-
"drizzle:generate": "drizzle-kit generate",
|
|
14
|
-
"drizzle:migrate": "
|
|
15
|
-
"drizzle:studio": "drizzle-kit studio",
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"@m5kdev/
|
|
26
|
-
"@
|
|
27
|
-
"@
|
|
28
|
-
"@
|
|
29
|
-
"@
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"@
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PACKAGE_SCOPE}}/server",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "tsx watch --env-file=../shared/.env src/index.ts",
|
|
8
|
+
"build": "tsup",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"lint": "biome check .",
|
|
11
|
+
"lint:fix": "biome check . --write",
|
|
12
|
+
"check-types": "tsc --noEmit",
|
|
13
|
+
"drizzle:generate": "drizzle-kit generate",
|
|
14
|
+
"drizzle:migrate": "tsx --env-file=../shared/.env db.ts migrate",
|
|
15
|
+
"drizzle:studio": "drizzle-kit studio",
|
|
16
|
+
|
|
17
|
+
"mastra:init": "tsx --env-file=../shared/.env mastra.ts",
|
|
18
|
+
// m5k:ai:end
|
|
19
|
+
"drizzle:seed": "tsx --env-file=../shared/.env db.ts seed",
|
|
20
|
+
"drizzle:reset": "tsx --env-file=../shared/.env db.ts reset"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"{{PACKAGE_SCOPE}}/email": "workspace:*",
|
|
24
|
+
"{{PACKAGE_SCOPE}}/shared": "workspace:*",
|
|
25
|
+
"@m5kdev/backend": "catalog:m5kdev",
|
|
26
|
+
"@m5kdev/commons": "catalog:m5kdev",
|
|
27
|
+
"@mastra/core": "catalog:m5kdev",
|
|
28
|
+
"@mastra/pg": "catalog:m5kdev",
|
|
29
|
+
"@mastra/memory": "catalog:m5kdev",
|
|
30
|
+
"pg": "catalog:m5kdev",
|
|
31
|
+
"@openrouter/ai-sdk-provider": "catalog:m5kdev",
|
|
32
|
+
"@opentelemetry/api": "catalog:m5kdev",
|
|
33
|
+
"@trpc/server": "catalog:m5kdev",
|
|
34
|
+
"better-auth": "catalog:m5kdev",
|
|
35
|
+
"dotenv": "catalog:m5kdev",
|
|
36
|
+
"drizzle-orm": "catalog:m5kdev",
|
|
37
|
+
"drizzle-zod": "catalog:m5kdev",
|
|
38
|
+
"express": "catalog:m5kdev",
|
|
39
|
+
"ioredis": "catalog:m5kdev",
|
|
40
|
+
"neverthrow": "catalog:m5kdev",
|
|
41
|
+
"react": "catalog:m5kdev",
|
|
42
|
+
"react-dom": "catalog:m5kdev",
|
|
43
|
+
"uuid": "catalog:m5kdev",
|
|
44
|
+
"zod": "catalog:m5kdev"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@m5kdev/config": "catalog:m5kdev",
|
|
48
|
+
"@types/express": "catalog:m5kdev",
|
|
49
|
+
"@types/node": "catalog:m5kdev",
|
|
50
|
+
"@types/pg": "catalog:m5kdev",
|
|
51
|
+
"@types/react": "catalog:m5kdev",
|
|
52
|
+
"@types/react-dom": "catalog:m5kdev",
|
|
53
|
+
"drizzle-kit": "catalog:m5kdev",
|
|
54
|
+
"tslib": "catalog:m5kdev",
|
|
55
|
+
"tsup": "catalog:m5kdev",
|
|
56
|
+
"tsx": "catalog:m5kdev",
|
|
57
|
+
"typescript": "catalog:m5kdev"
|
|
58
|
+
},
|
|
59
|
+
"exports": {
|
|
60
|
+
"./types": "./src/types.ts"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { createBackendApp } from "@m5kdev/backend/app";
|
|
3
3
|
import { shutdownTelemetry } from "@m5kdev/backend/lib/otel";
|
|
4
4
|
// m5k:ai:start
|
|
5
|
+
import { defineBackendModule } from "@m5kdev/backend/app";
|
|
5
6
|
import { AIModule } from "@m5kdev/backend/modules/ai/ai.module";
|
|
6
7
|
// m5k:ai:end
|
|
7
8
|
import { createBetterAuth } from "@m5kdev/backend/modules/auth/auth.lib";
|
|
@@ -31,7 +32,13 @@ import {
|
|
|
31
32
|
import { NOTIFICATION_KINDS } from "{{PACKAGE_SCOPE}}/shared/modules/notification/notification.constants";
|
|
32
33
|
// m5k:notifications:end
|
|
33
34
|
// m5k:ai:start
|
|
34
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
conversationStorage,
|
|
37
|
+
mastra,
|
|
38
|
+
openrouter,
|
|
39
|
+
verifyConversationPersistence,
|
|
40
|
+
} from "./modules/conversation/conversation.mastra";
|
|
41
|
+
import { conversationVectorSchema } from "./modules/conversation/conversation.persistence";
|
|
35
42
|
// m5k:ai:end
|
|
36
43
|
// m5k:workflows:start
|
|
37
44
|
import { DemoWorkflowModule } from "./modules/demo-workflow/demo-workflow.module";
|
|
@@ -44,27 +51,15 @@ import * as schema from "./schema";
|
|
|
44
51
|
|
|
45
52
|
const appUrl = process.env.VITE_APP_URL ?? "http://localhost:5173";
|
|
46
53
|
const serverUrl = process.env.VITE_SERVER_URL ?? "http://localhost:8080";
|
|
47
|
-
const databaseUrl = process.env.DATABASE_URL
|
|
48
|
-
|
|
49
|
-
const authToken = process.env.TURSO_AUTH_TOKEN;
|
|
54
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
55
|
+
if (!databaseUrl) throw new Error("DATABASE_URL must point to the runtime PgBouncer connection");
|
|
50
56
|
// m5k:workflows:start
|
|
51
57
|
const redisUrl = process.env.REDIS_URL ?? "redis://127.0.0.1:6379";
|
|
52
58
|
// m5k:workflows:end
|
|
53
59
|
const resendApiKey = process.env.RESEND_API_KEY;
|
|
54
60
|
const enableWaitlist = process.env.VITE_ENABLE_WAITLIST === "true";
|
|
55
61
|
|
|
56
|
-
const connection =
|
|
57
|
-
syncUrl && authToken
|
|
58
|
-
? {
|
|
59
|
-
url: databaseUrl,
|
|
60
|
-
syncUrl,
|
|
61
|
-
authToken,
|
|
62
|
-
syncInterval: 60,
|
|
63
|
-
readYourWrites: true,
|
|
64
|
-
}
|
|
65
|
-
: {
|
|
66
|
-
url: databaseUrl,
|
|
67
|
-
};
|
|
62
|
+
const connection = { connectionString: databaseUrl };
|
|
68
63
|
|
|
69
64
|
export const builtBackendApp = createBackendApp(
|
|
70
65
|
{
|
|
@@ -129,6 +124,15 @@ export const builtBackendApp = createBackendApp(
|
|
|
129
124
|
// m5k:ai:start
|
|
130
125
|
new AIModule({
|
|
131
126
|
libs: { mastra, openrouter },
|
|
127
|
+
vectorStore: {
|
|
128
|
+
connectionString: process.env.VECTOR_DATABASE_URL || databaseUrl,
|
|
129
|
+
schemaName: conversationVectorSchema,
|
|
130
|
+
},
|
|
131
|
+
}),
|
|
132
|
+
defineBackendModule({
|
|
133
|
+
id: "conversation-persistence",
|
|
134
|
+
startup: verifyConversationPersistence,
|
|
135
|
+
shutdown: () => conversationStorage.close(),
|
|
132
136
|
}),
|
|
133
137
|
// m5k:ai:end
|
|
134
138
|
// m5k:files:start
|
package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl
CHANGED
|
@@ -1,7 +1,53 @@
|
|
|
1
1
|
import { Mastra } from "@mastra/core";
|
|
2
2
|
import { Agent } from "@mastra/core/agent";
|
|
3
|
+
import { Memory } from "@mastra/memory";
|
|
3
4
|
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
4
5
|
import { STARTER_ASSISTANT_AGENT_ID } from "{{PACKAGE_SCOPE}}/shared/modules/conversation/conversation.constants";
|
|
6
|
+
import {
|
|
7
|
+
conversationEmbeddingDimensions,
|
|
8
|
+
conversationEmbeddingIndex,
|
|
9
|
+
conversationEmbeddingMetric,
|
|
10
|
+
conversationStorageSchema,
|
|
11
|
+
conversationVectorSchema,
|
|
12
|
+
createConversationStorage,
|
|
13
|
+
} from "./conversation.persistence";
|
|
14
|
+
|
|
15
|
+
export const conversationStorage = createConversationStorage();
|
|
16
|
+
|
|
17
|
+
export async function verifyConversationPersistence(): Promise<void> {
|
|
18
|
+
const tables = await conversationStorage.pool.query<{ ready: boolean }>(
|
|
19
|
+
"SELECT to_regclass(format('%I.%I', $1::text, 'mastra_threads')) IS NOT NULL AND to_regclass(format('%I.%I', $1::text, 'mastra_messages')) IS NOT NULL AS ready",
|
|
20
|
+
[conversationStorageSchema]
|
|
21
|
+
);
|
|
22
|
+
if (!tables.rows[0]?.ready)
|
|
23
|
+
throw new Error(
|
|
24
|
+
"Mastra storage is not ready; run mastra:init using the direct connection before startup"
|
|
25
|
+
);
|
|
26
|
+
const { PgVector } = await import("@mastra/pg");
|
|
27
|
+
const vector = new PgVector({
|
|
28
|
+
id: "starter-vector-readiness",
|
|
29
|
+
connectionString: process.env.VECTOR_DATABASE_URL || process.env.DATABASE_URL!,
|
|
30
|
+
schemaName: conversationVectorSchema,
|
|
31
|
+
disableInit: true,
|
|
32
|
+
});
|
|
33
|
+
try {
|
|
34
|
+
const extension = await vector.pool.query<{ ready: boolean }>(
|
|
35
|
+
"SELECT EXISTS(SELECT 1 FROM pg_extension e JOIN pg_namespace n ON n.oid = e.extnamespace WHERE e.extname = 'vector' AND n.nspname = 'public') AS ready"
|
|
36
|
+
);
|
|
37
|
+
if (!extension.rows[0]?.ready)
|
|
38
|
+
throw new Error(
|
|
39
|
+
"Provision pgvector in public before startup (transaction pooling requires this)"
|
|
40
|
+
);
|
|
41
|
+
const index = await vector.describeIndex({ indexName: conversationEmbeddingIndex });
|
|
42
|
+
if (
|
|
43
|
+
index.dimension !== conversationEmbeddingDimensions ||
|
|
44
|
+
index.metric !== conversationEmbeddingMetric
|
|
45
|
+
)
|
|
46
|
+
throw new Error("Starter vector configuration differs from the provisioned index");
|
|
47
|
+
} finally {
|
|
48
|
+
await vector.disconnect();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
5
51
|
|
|
6
52
|
/** Cheap OpenRouter chat model for the starter Conversation example. */
|
|
7
53
|
const STARTER_CHAT_MODEL = "google/gemini-2.5-flash";
|
|
@@ -14,12 +60,14 @@ const assistant = new Agent({
|
|
|
14
60
|
id: STARTER_ASSISTANT_AGENT_ID,
|
|
15
61
|
name: "Assistant",
|
|
16
62
|
instructions: "You are a helpful assistant for this starter app. Keep answers concise.",
|
|
63
|
+
memory: new Memory({ storage: conversationStorage, options: { semanticRecall: false } }),
|
|
17
64
|
model: openrouter.chat(STARTER_CHAT_MODEL, {
|
|
18
65
|
usage: { include: true },
|
|
19
66
|
}),
|
|
20
67
|
});
|
|
21
68
|
|
|
22
69
|
export const mastra = new Mastra({
|
|
70
|
+
storage: conversationStorage,
|
|
23
71
|
agents: {
|
|
24
72
|
[STARTER_ASSISTANT_AGENT_ID]: assistant,
|
|
25
73
|
},
|
package/templates/minimal-app/apps/server/src/modules/conversation/conversation.persistence.ts.tpl
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { PgVector, PostgresStore } from "@mastra/pg";
|
|
2
|
+
|
|
3
|
+
export const conversationStorageSchema = process.env.MASTRA_DATABASE_SCHEMA ?? "mastra";
|
|
4
|
+
export const conversationVectorSchema = process.env.VECTOR_DATABASE_SCHEMA ?? "vectors";
|
|
5
|
+
export const conversationEmbeddingIndex = "mastra_embeddings";
|
|
6
|
+
export const conversationEmbeddingModel = "openai/text-embedding-3-small";
|
|
7
|
+
export const conversationEmbeddingDimensions = 1536;
|
|
8
|
+
export const conversationEmbeddingMetric = "cosine" as const;
|
|
9
|
+
|
|
10
|
+
export function createConversationStorage(initialize = false): PostgresStore {
|
|
11
|
+
const appSchema = process.env.DATABASE_SCHEMA ?? "app";
|
|
12
|
+
if (
|
|
13
|
+
conversationStorageSchema === appSchema ||
|
|
14
|
+
conversationVectorSchema === appSchema ||
|
|
15
|
+
conversationStorageSchema === conversationVectorSchema
|
|
16
|
+
) {
|
|
17
|
+
throw new Error("App, Mastra storage, and vectors must use separately owned schemas");
|
|
18
|
+
}
|
|
19
|
+
const connectionString = initialize
|
|
20
|
+
? process.env.MASTRA_DATABASE_DIRECT_URL || process.env.DATABASE_DIRECT_URL
|
|
21
|
+
: process.env.MASTRA_DATABASE_URL || process.env.DATABASE_URL;
|
|
22
|
+
if (!connectionString) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
initialize
|
|
25
|
+
? "DATABASE_DIRECT_URL is required for Mastra initialization"
|
|
26
|
+
: "DATABASE_URL is required for Mastra runtime storage"
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return new PostgresStore({
|
|
30
|
+
id: "starter-mastra",
|
|
31
|
+
connectionString,
|
|
32
|
+
schemaName: conversationStorageSchema,
|
|
33
|
+
disableInit: !initialize,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createConversationVectorProvisioner(): PgVector {
|
|
38
|
+
const connectionString =
|
|
39
|
+
process.env.VECTOR_DATABASE_DIRECT_URL || process.env.DATABASE_DIRECT_URL;
|
|
40
|
+
if (!connectionString)
|
|
41
|
+
throw new Error("DATABASE_DIRECT_URL is required for vector initialization");
|
|
42
|
+
return new PgVector({
|
|
43
|
+
id: "starter-vector-init",
|
|
44
|
+
connectionString,
|
|
45
|
+
schemaName: conversationVectorSchema,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { appDatabaseSchema } from "@m5kdev/backend/base/database.schema";
|
|
1
2
|
import { members, organizations, users } from "@m5kdev/backend/modules/auth/auth.db";
|
|
2
|
-
import {
|
|
3
|
+
import { text, timestamp } from "drizzle-orm/pg-core";
|
|
3
4
|
import { v4 as uuidv4 } from "uuid";
|
|
4
5
|
|
|
5
|
-
export const posts = table("posts", {
|
|
6
|
+
export const posts = appDatabaseSchema.table("posts", {
|
|
6
7
|
id: text("id").primaryKey().$default(uuidv4),
|
|
7
8
|
authorUserId: text("author_user_id").references(() => users.id, {
|
|
8
9
|
onDelete: "set null",
|
|
@@ -19,10 +20,10 @@ export const posts = table("posts", {
|
|
|
19
20
|
excerpt: text("excerpt"),
|
|
20
21
|
content: text("content").notNull(),
|
|
21
22
|
status: text("status").$type<"draft" | "published">().notNull().default("draft"),
|
|
22
|
-
publishedAt:
|
|
23
|
-
createdAt:
|
|
23
|
+
publishedAt: timestamp("published_at", { withTimezone: true, mode: "date" }),
|
|
24
|
+
createdAt: timestamp("created_at", { withTimezone: true, mode: "date" })
|
|
24
25
|
.notNull()
|
|
25
26
|
.$default(() => new Date()),
|
|
26
|
-
updatedAt:
|
|
27
|
-
deletedAt:
|
|
27
|
+
updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }),
|
|
28
|
+
deletedAt: timestamp("deleted_at", { withTimezone: true, mode: "date" }),
|
|
28
29
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BaseTableRepository } from "@m5kdev/backend/modules/base/base.repository";
|
|
2
2
|
|
|
3
3
|
import { and, eq, isNull, ne } from "drizzle-orm";
|
|
4
|
-
import type {
|
|
4
|
+
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
5
5
|
import { err, ok } from "neverthrow";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { posts } from "./posts.db";
|
|
@@ -9,7 +9,7 @@ import { postSchemas } from "./posts.dto";
|
|
|
9
9
|
|
|
10
10
|
const schema = { posts };
|
|
11
11
|
type Schema = typeof schema;
|
|
12
|
-
type Orm =
|
|
12
|
+
type Orm = NodePgDatabase<Schema>;
|
|
13
13
|
|
|
14
14
|
export class PostsRepository extends BaseTableRepository<
|
|
15
15
|
Orm,
|
|
@@ -8,9 +8,12 @@ VITE_SERVER_URL=http://localhost:8080
|
|
|
8
8
|
VITE_ENABLE_WAITLIST=false
|
|
9
9
|
|
|
10
10
|
# Database
|
|
11
|
-
DATABASE_URL=
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
DATABASE_URL=postgresql://m5kdev:m5kdev@127.0.0.1:6432/m5kdev
|
|
12
|
+
DATABASE_DIRECT_URL=postgresql://m5kdev:m5kdev@127.0.0.1:5432/m5kdev
|
|
13
|
+
DATABASE_SCHEMA=app
|
|
14
|
+
DATABASE_DISPOSABLE=false
|
|
15
|
+
MASTRA_DATABASE_SCHEMA=mastra
|
|
16
|
+
VECTOR_DATABASE_SCHEMA=vectors
|
|
14
17
|
|
|
15
18
|
# Redis (BullMQ / workflow workers)
|
|
16
19
|
REDIS_URL=redis://127.0.0.1:6379
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
# image (.dockerignore excludes .env*). app:deploy forwards every key as a Fly
|
|
3
3
|
# build secret; app:secrets imports the same file as runtime secrets.
|
|
4
4
|
|
|
5
|
-
#
|
|
6
|
-
DATABASE_URL=
|
|
5
|
+
# PostgreSQL: runtime transaction pool and direct migration/admin connection
|
|
6
|
+
DATABASE_URL=
|
|
7
|
+
DATABASE_DIRECT_URL=
|
|
8
|
+
DATABASE_SCHEMA=app
|
|
9
|
+
DATABASE_DISPOSABLE=false
|
|
10
|
+
MASTRA_DATABASE_SCHEMA=mastra
|
|
11
|
+
VECTOR_DATABASE_SCHEMA=vectors
|
|
7
12
|
|
|
8
13
|
# Redis — set as a Fly runtime secret (do not bake into the image)
|
|
9
14
|
REDIS_URL=
|
|
@@ -8,9 +8,12 @@ VITE_SERVER_URL=http://localhost:8080
|
|
|
8
8
|
VITE_ENABLE_WAITLIST=false
|
|
9
9
|
|
|
10
10
|
# Database
|
|
11
|
-
DATABASE_URL=
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
DATABASE_URL=postgresql://m5kdev:m5kdev@127.0.0.1:6432/m5kdev
|
|
12
|
+
DATABASE_DIRECT_URL=postgresql://m5kdev:m5kdev@127.0.0.1:5432/m5kdev
|
|
13
|
+
DATABASE_SCHEMA=app
|
|
14
|
+
DATABASE_DISPOSABLE=false
|
|
15
|
+
MASTRA_DATABASE_SCHEMA=mastra
|
|
16
|
+
VECTOR_DATABASE_SCHEMA=vectors
|
|
14
17
|
|
|
15
18
|
# m5k:ai:start
|
|
16
19
|
# Optional OpenRouter (AI Conversation). Required to get an assistant reply.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
FROM debian:bookworm-slim
|
|
2
|
+
RUN apt-get update && apt-get install -y --no-install-recommends pgbouncer && rm -rf /var/lib/apt/lists/*
|
|
3
|
+
COPY pgbouncer.ini /etc/pgbouncer/pgbouncer.ini
|
|
4
|
+
COPY pgbouncer.users /etc/pgbouncer/users.txt
|
|
5
|
+
USER nobody
|
|
6
|
+
CMD ["pgbouncer", "/etc/pgbouncer/pgbouncer.ini"]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: pgvector/pgvector:pg17
|
|
4
|
+
environment:
|
|
5
|
+
POSTGRES_DB: m5kdev
|
|
6
|
+
POSTGRES_USER: m5kdev
|
|
7
|
+
POSTGRES_PASSWORD: m5kdev
|
|
8
|
+
ports:
|
|
9
|
+
- "127.0.0.1:5432:5432"
|
|
10
|
+
volumes:
|
|
11
|
+
- postgres_data:/var/lib/postgresql/data
|
|
12
|
+
healthcheck:
|
|
13
|
+
test: ["CMD-SHELL", "pg_isready -U m5kdev -d m5kdev"]
|
|
14
|
+
interval: 2s
|
|
15
|
+
timeout: 2s
|
|
16
|
+
retries: 30
|
|
17
|
+
pgbouncer:
|
|
18
|
+
build:
|
|
19
|
+
context: .
|
|
20
|
+
dockerfile: Dockerfile.pgbouncer
|
|
21
|
+
depends_on:
|
|
22
|
+
postgres:
|
|
23
|
+
condition: service_healthy
|
|
24
|
+
ports:
|
|
25
|
+
- "127.0.0.1:6432:6432"
|
|
26
|
+
volumes:
|
|
27
|
+
postgres_data:
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[databases]
|
|
2
|
+
m5kdev = host=postgres port=5432 dbname=m5kdev
|
|
3
|
+
|
|
4
|
+
[pgbouncer]
|
|
5
|
+
listen_addr = 0.0.0.0
|
|
6
|
+
listen_port = 6432
|
|
7
|
+
unix_socket_dir = /tmp
|
|
8
|
+
auth_type = plain
|
|
9
|
+
auth_file = /etc/pgbouncer/users.txt
|
|
10
|
+
pool_mode = transaction
|
|
11
|
+
default_pool_size = 10
|
|
12
|
+
max_client_conn = 100
|
|
13
|
+
pidfile = /tmp/pgbouncer.pid
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"m5kdev" "m5kdev"
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"eyebrow": "Included by Default",
|
|
9
9
|
"title": "Auth, modules, and one honest feature.",
|
|
10
10
|
"body": "This starter keeps the stack visible: Better Auth, Drizzle, tRPC, React Router, HeroUI, and a single editorial posts module.",
|
|
11
|
-
"local": "
|
|
11
|
+
"local": "PostgreSQL",
|
|
12
12
|
"auth": "Better Auth"
|
|
13
13
|
},
|
|
14
14
|
"navigation": {
|
|
@@ -6,20 +6,21 @@ catalogs:
|
|
|
6
6
|
"@biomejs/biome": 2.5.9
|
|
7
7
|
"@heroui/react": 3.0.3
|
|
8
8
|
"@heroui/styles": 3.0.3
|
|
9
|
-
"@
|
|
10
|
-
"@m5kdev/
|
|
11
|
-
"@m5kdev/
|
|
12
|
-
"@m5kdev/
|
|
13
|
-
"@m5kdev/
|
|
14
|
-
"@m5kdev/
|
|
15
|
-
"@m5kdev/
|
|
16
|
-
"@m5kdev/module-
|
|
17
|
-
"@m5kdev/module-
|
|
18
|
-
"@m5kdev/module-
|
|
19
|
-
"@m5kdev/module-
|
|
20
|
-
"@m5kdev/
|
|
21
|
-
"@m5kdev/web-ui": 0.41.1
|
|
9
|
+
"@m5kdev/backend": 0.43.0
|
|
10
|
+
"@m5kdev/commons": 0.43.0
|
|
11
|
+
"@m5kdev/config": 0.43.0
|
|
12
|
+
"@m5kdev/email": 0.43.0
|
|
13
|
+
"@m5kdev/expo": 0.43.0
|
|
14
|
+
"@m5kdev/frontend": 0.43.0
|
|
15
|
+
"@m5kdev/module-clay": 0.43.0
|
|
16
|
+
"@m5kdev/module-docx": 0.43.0
|
|
17
|
+
"@m5kdev/module-pdf": 0.43.0
|
|
18
|
+
"@m5kdev/module-social": 0.43.0
|
|
19
|
+
"@m5kdev/module-video": 0.43.0
|
|
20
|
+
"@m5kdev/web-ui": 0.43.0
|
|
22
21
|
"@mastra/core": 1.51.0
|
|
22
|
+
"@mastra/memory": 1.9.0
|
|
23
|
+
"@mastra/pg": 1.16.0
|
|
23
24
|
"@openrouter/ai-sdk-provider": 3.0.0
|
|
24
25
|
"@opentelemetry/api": 1.9.1
|
|
25
26
|
"@playwright/test": 1.57.0
|
|
@@ -31,6 +32,7 @@ catalogs:
|
|
|
31
32
|
"@trpc/tanstack-react-query": 11.4.3
|
|
32
33
|
"@types/express": 5.0.6
|
|
33
34
|
"@types/node": 20.19.11
|
|
35
|
+
"@types/pg": 8.23.1
|
|
34
36
|
"@types/react": 19.2.17
|
|
35
37
|
"@types/react-dom": 19.2.3
|
|
36
38
|
"@vitejs/plugin-react": 4.2.0
|
|
@@ -46,6 +48,7 @@ catalogs:
|
|
|
46
48
|
lucide-react: 0.488.0
|
|
47
49
|
neverthrow: 8.2.0
|
|
48
50
|
nuqs: 2.4.3
|
|
51
|
+
pg: 8.23.1
|
|
49
52
|
posthog-js: 1.258.2
|
|
50
53
|
react: 19.2.3
|
|
51
54
|
react-dom: 19.2.3
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"paths": [
|
|
59
59
|
"apps/webapp/src/modules/conversation/",
|
|
60
60
|
"apps/server/src/modules/conversation/",
|
|
61
|
+
"apps/server/mastra.ts",
|
|
61
62
|
"apps/shared/src/modules/conversation/",
|
|
62
63
|
"apps/e2e/tests/conversation.spec.ts"
|
|
63
64
|
]
|
|
@@ -137,6 +138,10 @@
|
|
|
137
138
|
"pattern": "**/drizzle/meta/**",
|
|
138
139
|
"policy": "ignore"
|
|
139
140
|
},
|
|
141
|
+
{
|
|
142
|
+
"pattern": "**/drizzle-postgresql/**",
|
|
143
|
+
"policy": "ignore"
|
|
144
|
+
},
|
|
140
145
|
{
|
|
141
146
|
"pattern": "apps/server/db.ts",
|
|
142
147
|
"policy": "ensure"
|
|
@@ -149,6 +154,10 @@
|
|
|
149
154
|
"pattern": "apps/server/drizzle.config.ts",
|
|
150
155
|
"policy": "ensure"
|
|
151
156
|
},
|
|
157
|
+
{
|
|
158
|
+
"pattern": "apps/server/src/drizzle.schema.ts",
|
|
159
|
+
"policy": "ensure"
|
|
160
|
+
},
|
|
152
161
|
{
|
|
153
162
|
"pattern": "GLOSSARY.md",
|
|
154
163
|
"policy": "ensure"
|