create-mikstack 0.1.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 (99) hide show
  1. package/README.md +54 -0
  2. package/dist/index.js +410 -0
  3. package/package.json +43 -0
  4. package/templates/adapters/cloudflare/package.json.partial +5 -0
  5. package/templates/adapters/cloudflare/svelte.config.js +19 -0
  6. package/templates/adapters/node/Dockerfile +30 -0
  7. package/templates/adapters/node/docker-compose.prod.yml +27 -0
  8. package/templates/adapters/node/package.json.partial +5 -0
  9. package/templates/adapters/node/svelte.config.js +19 -0
  10. package/templates/adapters/vercel/package.json.partial +5 -0
  11. package/templates/adapters/vercel/svelte.config.js +19 -0
  12. package/templates/base/.env.example +23 -0
  13. package/templates/base/.gitignore.append +2 -0
  14. package/templates/base/.mcp.json +9 -0
  15. package/templates/base/.prettierignore +10 -0
  16. package/templates/base/.vscode/extensions.json +3 -0
  17. package/templates/base/AGENTS.md +123 -0
  18. package/templates/base/README.md +27 -0
  19. package/templates/base/agents.md +28 -0
  20. package/templates/base/docker-compose.yml +15 -0
  21. package/templates/base/drizzle-zero.config.ts +17 -0
  22. package/templates/base/drizzle.config.ts +17 -0
  23. package/templates/base/eslint.config.ts +65 -0
  24. package/templates/base/package.json.partial +43 -0
  25. package/templates/base/prettier.config.js +6 -0
  26. package/templates/base/src/app.d.ts +12 -0
  27. package/templates/base/src/app.html +11 -0
  28. package/templates/base/src/hooks.server.ts +15 -0
  29. package/templates/base/src/lib/auth-client.ts +6 -0
  30. package/templates/base/src/lib/server/auth.ts +52 -0
  31. package/templates/base/src/lib/server/db/index.ts +19 -0
  32. package/templates/base/src/lib/server/db/schema.ts +117 -0
  33. package/templates/base/src/lib/server/db/seed.ts +21 -0
  34. package/templates/base/src/lib/server/emails/magic-link.ts +77 -0
  35. package/templates/base/src/lib/server/emails/send.ts +55 -0
  36. package/templates/base/src/lib/server/notifications/definitions.ts +12 -0
  37. package/templates/base/src/lib/server/notifications.ts +38 -0
  38. package/templates/base/src/lib/z.svelte.ts +14 -0
  39. package/templates/base/src/lib/zero/context.ts +9 -0
  40. package/templates/base/src/lib/zero/db-provider.server.ts +11 -0
  41. package/templates/base/src/lib/zero/mutators.ts +35 -0
  42. package/templates/base/src/lib/zero/queries.ts +21 -0
  43. package/templates/base/src/lib/zero/schema.ts +1 -0
  44. package/templates/base/src/routes/+layout.server.ts +5 -0
  45. package/templates/base/src/routes/+layout.svelte +7 -0
  46. package/templates/base/src/routes/+page.server.ts +7 -0
  47. package/templates/base/src/routes/+page.svelte +319 -0
  48. package/templates/base/src/routes/api/dev/emails/+server.ts +89 -0
  49. package/templates/base/src/routes/api/dev/emails/[id]/+server.ts +24 -0
  50. package/templates/base/src/routes/api/notifications/[...path]/+server.ts +10 -0
  51. package/templates/base/src/routes/api/zero/get-queries/+server.ts +29 -0
  52. package/templates/base/src/routes/api/zero/mutate/+server.ts +31 -0
  53. package/templates/base/src/routes/sign-in/+page.svelte +97 -0
  54. package/templates/base/tsconfig.json +40 -0
  55. package/templates/github-actions-bun/.github/workflows/ci.yml +22 -0
  56. package/templates/github-actions-npm/.github/workflows/ci.yml +25 -0
  57. package/templates/github-actions-pnpm/.github/workflows/ci.yml +27 -0
  58. package/templates/i18n/lingui.config.ts +16 -0
  59. package/templates/i18n/package.json.partial +14 -0
  60. package/templates/i18n/src/lib/i18n.ts +10 -0
  61. package/templates/i18n/src/locales/en.po +6 -0
  62. package/templates/i18n/src/po.d.ts +3 -0
  63. package/templates/i18n/vite.config.ts +7 -0
  64. package/templates/supply-chain-bun/bunfig.toml +3 -0
  65. package/templates/testing/package.json.partial +11 -0
  66. package/templates/testing/src/example.test.ts +7 -0
  67. package/templates/testing/src/lib/server/db/test-utils.ts +25 -0
  68. package/templates/testing/vitest.config.ts +9 -0
  69. package/templates/ui/.vscode/extensions.json +8 -0
  70. package/templates/ui/package.json.partial +13 -0
  71. package/templates/ui/src/app.css +94 -0
  72. package/templates/ui/src/routes/+layout.svelte +12 -0
  73. package/templates/ui/stylelint.config.js +7 -0
  74. package/templates/ui/vite.config.ts +6 -0
  75. package/templates/ui-dependency/package.json.partial +5 -0
  76. package/templates/ui-vendor/src/lib/components/ui/Accordion/Accordion.svelte +71 -0
  77. package/templates/ui-vendor/src/lib/components/ui/Accordion/index.ts +1 -0
  78. package/templates/ui-vendor/src/lib/components/ui/Alert/Alert.svelte +60 -0
  79. package/templates/ui-vendor/src/lib/components/ui/Alert/index.ts +1 -0
  80. package/templates/ui-vendor/src/lib/components/ui/Badge/Badge.svelte +48 -0
  81. package/templates/ui-vendor/src/lib/components/ui/Badge/index.ts +1 -0
  82. package/templates/ui-vendor/src/lib/components/ui/Button/Button.svelte +77 -0
  83. package/templates/ui-vendor/src/lib/components/ui/Button/index.ts +1 -0
  84. package/templates/ui-vendor/src/lib/components/ui/Card/Card.svelte +49 -0
  85. package/templates/ui-vendor/src/lib/components/ui/Card/index.ts +1 -0
  86. package/templates/ui-vendor/src/lib/components/ui/Dialog/Dialog.svelte +70 -0
  87. package/templates/ui-vendor/src/lib/components/ui/Dialog/index.ts +1 -0
  88. package/templates/ui-vendor/src/lib/components/ui/FormField/FormField.svelte +53 -0
  89. package/templates/ui-vendor/src/lib/components/ui/FormField/index.ts +1 -0
  90. package/templates/ui-vendor/src/lib/components/ui/Input/Input.svelte +27 -0
  91. package/templates/ui-vendor/src/lib/components/ui/Input/index.ts +1 -0
  92. package/templates/ui-vendor/src/lib/components/ui/Separator/Separator.svelte +26 -0
  93. package/templates/ui-vendor/src/lib/components/ui/Separator/index.ts +1 -0
  94. package/templates/ui-vendor/src/lib/components/ui/Skeleton/Skeleton.svelte +40 -0
  95. package/templates/ui-vendor/src/lib/components/ui/Skeleton/index.ts +1 -0
  96. package/templates/ui-vendor/src/lib/components/ui/Switch/Switch.svelte +86 -0
  97. package/templates/ui-vendor/src/lib/components/ui/Switch/index.ts +1 -0
  98. package/templates/ui-vendor/src/lib/components/ui/Textarea/Textarea.svelte +29 -0
  99. package/templates/ui-vendor/src/lib/components/ui/Textarea/index.ts +1 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ "recommendations": ["svelte.svelte-vscode", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
3
+ }
@@ -0,0 +1,123 @@
1
+ # {{projectName}}
2
+
3
+ SvelteKit app with Drizzle ORM, Zero (real-time sync), and better-auth.
4
+
5
+ ## Stack
6
+
7
+ - **Framework**: SvelteKit with TypeScript
8
+ - **Database**: PostgreSQL via Drizzle ORM
9
+ - **Real-time**: Zero (@rocicorp/zero) for client-side sync
10
+ - **Auth**: better-auth with magic link
11
+ - **Styling**: See `src/app.css`
12
+ <!-- {{#if:i18n}} -->
13
+ - **i18n**: @mikstack/svelte-lingui (Lingui-based)
14
+ <!-- {{/if:i18n}} -->
15
+
16
+ ## Key Patterns
17
+
18
+ ### Zero Queries & Mutations
19
+
20
+ Queries: `src/lib/zero/queries.ts` — define with `defineQueries`
21
+ Mutators: `src/lib/zero/mutators.ts` — define with `defineMutators`
22
+ Client usage: `get_z()` from `$lib/z.svelte` returns the Zero instance
23
+
24
+ ```typescript
25
+ const z = get_z();
26
+ const query = z.q(queries.myQuery());
27
+ const data = $derived(query.data);
28
+ await z.mutate(mutators.myMutation(args));
29
+ ```
30
+
31
+ ### Auth
32
+
33
+ Server: `src/lib/server/auth.ts` (better-auth config)
34
+ Client: `src/lib/auth-client.ts` (magic link + client helpers)
35
+ Session is available in `locals.user` and `locals.session` via `src/hooks.server.ts`.
36
+
37
+ ### Forms (@mikstack/form)
38
+
39
+ Use `createForm` from `@mikstack/form` for client-side forms with Valibot validation.
40
+ Ideal for Zero mutators where forms don't use SvelteKit form actions.
41
+
42
+ ```svelte
43
+ <script lang="ts">
44
+ import { createForm } from "@mikstack/form";
45
+ import * as v from "valibot";
46
+
47
+ const form = createForm({
48
+ schema: v.object({
49
+ title: v.pipe(v.string(), v.minLength(1, "Required")),
50
+ content: v.string(),
51
+ }),
52
+ initialValues: { title: "", content: "" },
53
+ async onSubmit(data) {
54
+ await z.mutate(mutators.note.create({ id: crypto.randomUUID(), ...data }));
55
+ form.reset();
56
+ },
57
+ });
58
+ </script>
59
+
60
+ <form id={form.id} onsubmit={form.onsubmit}>
61
+ <input {...form.fields.title.as("text")} />
62
+ <button type="submit" disabled={form.pending}>Create</button>
63
+ </form>
64
+ ```
65
+
66
+ Key APIs: `form.fields.<name>.as(type)` for input props, `.issues()` for errors,
67
+ `form.pending` / `form.error` / `form.result` for submit state, `form.reset()` to clear.
68
+
69
+ ### Notifications (@mikstack/notifications)
70
+
71
+ Config: `src/lib/server/notifications.ts` (lazy-initialized via Proxy)
72
+ Definitions: `src/lib/server/notifications/definitions.ts`
73
+ API routes: `src/routes/api/notifications/` (mark-read, preferences)
74
+
75
+ Send a notification:
76
+
77
+ ```typescript
78
+ import { notif } from "$lib/server/notifications";
79
+ await notif.send({ type: "welcome", userId: user.id, data: { userName: user.name } });
80
+ ```
81
+
82
+ Define new notification types in `definitions.ts` using `defineNotification()`.
83
+ In-app notifications sync to the client via Zero (`inAppNotification` table).
84
+ Email delivery tracking and retries are handled automatically.
85
+
86
+ ### Database
87
+
88
+ Schema: `src/lib/server/db/schema.ts`
89
+ Connection: `src/lib/server/db/index.ts` (lazy-initialized via Proxy)
90
+
91
+ <!-- {{#if:i18n}} -->
92
+ ### i18n
93
+
94
+ Setup: `src/lib/i18n.ts` — initialized in root layout
95
+ Config: `lingui.config.ts`
96
+ Catalogs: `src/locales/{locale}.po`
97
+
98
+ Use `useLingui()` for translations, `<T>` component for rich text:
99
+
100
+ ```svelte
101
+ <script lang="ts">
102
+ import { useLingui } from "@mikstack/svelte-lingui";
103
+ const { t } = useLingui();
104
+ </script>
105
+
106
+ <h1>{t`Hello world`}</h1>
107
+ ```
108
+
109
+ Run `{{pmRun}} i18n:extract` to extract messages into `.po` catalogs.
110
+ <!-- {{/if:i18n}} -->
111
+
112
+ ## Commands
113
+
114
+ - `{{pmRun}} dev` — start dev server
115
+ - `{{pmRun}} build` — production build
116
+ - `{{pmRun}} db:push` — push schema to database
117
+ - `{{pmRun}} db:seed` — seed database
118
+ - `{{pmRun}} zero:generate` — generate Zero schema from Drizzle
119
+ - `{{pmRun}} lint` — run ESLint
120
+ - `{{pmRun}} format` — format with Prettier
121
+ <!-- {{#if:i18n}} -->
122
+ - `{{pmRun}} i18n:extract` — extract messages to .po catalogs
123
+ <!-- {{/if:i18n}} -->
@@ -0,0 +1,27 @@
1
+ # {{projectName}}
2
+
3
+ Built with [mikstack](https://github.com/mikaelsiidorow/mikstack).
4
+
5
+ ## Getting Started
6
+
7
+ ```bash
8
+ {{pmRun}} db:start
9
+ {{pmRun}} db:push
10
+ {{pmRun}} db:seed
11
+ {{pmRun}} dev
12
+ ```
13
+
14
+ ## Scripts
15
+
16
+ - `{{pmRun}} dev` — Start dev server
17
+ - `{{pmRun}} build` — Build for production
18
+ - `{{pmRun}} preview` — Preview production build
19
+ - `{{pmRun}} check` — Run svelte-check
20
+ - `{{pmRun}} lint` — Lint with ESLint
21
+ - `{{pmRun}} format` — Format with Prettier
22
+ - `{{pmRun}} db:start` — Start Postgres (Docker)
23
+ - `{{pmRun}} db:generate` — Generate Drizzle migrations
24
+ - `{{pmRun}} db:migrate` — Run Drizzle migrations
25
+ - `{{pmRun}} db:push` — Push schema to database
26
+ - `{{pmRun}} db:studio` — Open Drizzle Studio
27
+ - `{{pmRun}} db:seed` — Seed the database
@@ -0,0 +1,28 @@
1
+ # Agent Guidelines for {{projectName}}
2
+
3
+ ## Architecture Overview
4
+
5
+ This is a SvelteKit application using:
6
+
7
+ - **Drizzle ORM** for database schema and migrations
8
+ - **Zero** (@rocicorp/zero) for real-time client-side data sync
9
+ - **better-auth** for authentication (magic link flow)
10
+
11
+ ## File Conventions
12
+
13
+ - Server-only code: `src/lib/server/` (enforced by SvelteKit)
14
+ - Zero schema/queries/mutators: `src/lib/zero/`
15
+ - Auth client: `src/lib/auth-client.ts`
16
+ - Zero client context: `src/lib/z.svelte.ts`
17
+ - API routes for Zero: `src/routes/api/zero/`
18
+
19
+ ## When Modifying the Schema
20
+
21
+ 1. Update Drizzle schema in `src/lib/server/db/schema.ts`
22
+ 2. Run `{{pmRun}} db:push` (dev) or `{{pmRun}} db:generate` + `{{pmRun}} db:migrate` (production)
23
+ 3. Run `{{pmRun}} zero:generate` to regenerate Zero schema
24
+ 4. Update queries/mutators in `src/lib/zero/` as needed
25
+
26
+ ## Testing
27
+
28
+ Run `{{pmRun}} check` for type checking and `{{pmRun}} lint` for linting before committing.
@@ -0,0 +1,15 @@
1
+ services:
2
+ db:
3
+ image: postgres
4
+ restart: always
5
+ ports:
6
+ - 5432:5432
7
+ environment:
8
+ POSTGRES_USER: root
9
+ POSTGRES_PASSWORD: mysecretpassword
10
+ POSTGRES_DB: local
11
+ command: ["postgres", "-c", "wal_level=logical"]
12
+ volumes:
13
+ - pgdata:/var/lib/postgresql/data
14
+ volumes:
15
+ pgdata:
@@ -0,0 +1,17 @@
1
+ import { drizzleZeroConfig } from "drizzle-zero";
2
+ import * as schema from "./src/lib/server/db/schema";
3
+
4
+ export default drizzleZeroConfig(schema, {
5
+ tables: {
6
+ user: true,
7
+ note: true,
8
+ inAppNotification: true,
9
+ // Exclude auth-internal and server-only tables from client sync
10
+ session: false,
11
+ account: false,
12
+ verification: false,
13
+ notificationDelivery: false,
14
+ notificationPreference: false,
15
+ },
16
+ casing: "snake_case",
17
+ });
@@ -0,0 +1,17 @@
1
+ import { defineConfig } from "drizzle-kit";
2
+ import { existsSync } from "fs";
3
+
4
+ if (existsSync(".env")) {
5
+ process.loadEnvFile();
6
+ }
7
+
8
+ const DATABASE_URL = process.env.DATABASE_URL || "postgresql://placeholder";
9
+
10
+ export default defineConfig({
11
+ schema: "./src/lib/server/db/schema.ts",
12
+ dialect: "postgresql",
13
+ dbCredentials: { url: DATABASE_URL },
14
+ verbose: true,
15
+ strict: true,
16
+ casing: "snake_case",
17
+ });
@@ -0,0 +1,65 @@
1
+ import prettier from "eslint-config-prettier";
2
+ import { fileURLToPath } from "node:url";
3
+ import { includeIgnoreFile } from "@eslint/compat";
4
+ import js from "@eslint/js";
5
+ import svelte from "eslint-plugin-svelte";
6
+ import { defineConfig } from "eslint/config";
7
+ import globals from "globals";
8
+ import ts from "typescript-eslint";
9
+ import svelteConfig from "./svelte.config.js";
10
+
11
+ const gitignorePath = fileURLToPath(new URL("./.gitignore", import.meta.url));
12
+
13
+ const extraFileExtensions = [".svelte"];
14
+
15
+ export default defineConfig(
16
+ includeIgnoreFile(gitignorePath),
17
+ { ignores: ["!*.gen.ts"] },
18
+ js.configs.recommended,
19
+ ...ts.configs.recommendedTypeChecked,
20
+ ...svelte.configs.recommended,
21
+ prettier,
22
+ ...svelte.configs.prettier,
23
+ {
24
+ languageOptions: {
25
+ globals: { ...globals.browser, ...globals.node },
26
+ parserOptions: {
27
+ projectService: true,
28
+ extraFileExtensions,
29
+ },
30
+ },
31
+ rules: {
32
+ "no-undef": "off",
33
+ "no-restricted-imports": [
34
+ "error",
35
+ {
36
+ paths: [
37
+ {
38
+ name: "$app/stores",
39
+ message: "Use $app/state instead of $app/stores.",
40
+ },
41
+ ],
42
+ },
43
+ ],
44
+ },
45
+ },
46
+ {
47
+ files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"],
48
+ languageOptions: {
49
+ parserOptions: {
50
+ projectService: true,
51
+ parser: ts.parser,
52
+ svelteConfig,
53
+ extraFileExtensions,
54
+ },
55
+ },
56
+ rules: {
57
+ "@typescript-eslint/no-unsafe-argument": "off",
58
+ "@typescript-eslint/no-unsafe-assignment": "off",
59
+ "@typescript-eslint/no-unsafe-call": "off",
60
+ "@typescript-eslint/no-unsafe-member-access": "off",
61
+ "@typescript-eslint/no-unsafe-return": "off",
62
+ "@typescript-eslint/no-unnecessary-type-assertion": "off",
63
+ },
64
+ },
65
+ );
@@ -0,0 +1,43 @@
1
+ {
2
+ "scripts": {
3
+ "check": "drizzle-zero generate --output src/lib/zero/zero-schema.gen.ts && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
4
+ "check:watch": "drizzle-zero generate --output src/lib/zero/zero-schema.gen.ts && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
5
+ "lint": "drizzle-zero generate --output src/lib/zero/zero-schema.gen.ts && eslint .",
6
+ "format": "prettier --write .",
7
+ "format:check": "prettier --check .",
8
+ "db:start": "docker compose up -d",
9
+ "db:generate": "drizzle-kit generate",
10
+ "db:migrate": "drizzle-kit migrate",
11
+ "db:push": "drizzle-kit push",
12
+ "db:studio": "drizzle-kit studio",
13
+ "db:seed": "{{seedRunner}} src/lib/server/db/seed.ts",
14
+ "zero:generate": "drizzle-zero generate --output src/lib/zero/zero-schema.gen.ts",
15
+ "zero:start": "zero-cache-server"
16
+ },
17
+ "dependencies": {
18
+ "@mikstack/email": "^0.1.0",
19
+ "@mikstack/form": "^0.1.0",
20
+ "@mikstack/notifications": "^0.1.0",
21
+ "@rocicorp/zero": "^0.25.12",
22
+ "better-auth": "^1.4.18",
23
+ "drizzle-orm": "^0.45.1",
24
+ "nodemailer": "^8.0.1",
25
+ "postgres": "^3.4.8",
26
+ "valibot": "^1.2.0",
27
+ "zero-svelte": "^1.2.2"
28
+ },
29
+ "devDependencies": {
30
+ "@eslint/compat": "^1.4.1",
31
+ "@eslint/js": "^9.39.2",
32
+ "@types/nodemailer": "^7.0.9",
33
+ "drizzle-kit": "^0.31.8",
34
+ "drizzle-zero": "^0.17.3",
35
+ "eslint": "^9.39.2",
36
+ "eslint-config-prettier": "^10.1.8",
37
+ "eslint-plugin-svelte": "^3.14.0",
38
+ "globals": "^17.3.0",
39
+ "prettier": "^3.8.1",
40
+ "prettier-plugin-svelte": "^3.4.1",
41
+ "typescript-eslint": "^8.54.0"
42
+ }
43
+ }
@@ -0,0 +1,6 @@
1
+ /** @type {import("prettier").Config} */
2
+ export default {
3
+ printWidth: 100,
4
+ plugins: ["prettier-plugin-svelte"],
5
+ overrides: [{ files: "*.svelte", options: { parser: "svelte" } }],
6
+ };
@@ -0,0 +1,12 @@
1
+ import type { Session, User } from "$lib/server/auth";
2
+
3
+ declare global {
4
+ namespace App {
5
+ interface Locals {
6
+ user: User | null;
7
+ session: Session | null;
8
+ }
9
+ }
10
+ }
11
+
12
+ export {};
@@ -0,0 +1,11 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ %sveltekit.head%
7
+ </head>
8
+ <body data-sveltekit-preload-data="hover">
9
+ <div style="display: contents">%sveltekit.body%</div>
10
+ </body>
11
+ </html>
@@ -0,0 +1,15 @@
1
+ import type { Handle } from "@sveltejs/kit";
2
+ import { auth } from "$lib/server/auth";
3
+ import { svelteKitHandler } from "better-auth/svelte-kit";
4
+ import { building } from "$app/environment";
5
+
6
+ export const handle: Handle = async ({ event, resolve }) => {
7
+ const session = await auth.api.getSession({
8
+ headers: event.request.headers,
9
+ });
10
+
11
+ event.locals.user = session?.user ?? null;
12
+ event.locals.session = session?.session ?? null;
13
+
14
+ return svelteKitHandler({ event, resolve, auth, building });
15
+ };
@@ -0,0 +1,6 @@
1
+ import { createAuthClient } from "better-auth/client";
2
+ import { magicLinkClient } from "better-auth/client/plugins";
3
+
4
+ export const authClient = createAuthClient({
5
+ plugins: [magicLinkClient()],
6
+ });
@@ -0,0 +1,52 @@
1
+ import { betterAuth } from "better-auth";
2
+ import { drizzleAdapter } from "better-auth/adapters/drizzle";
3
+ import { magicLink } from "better-auth/plugins";
4
+ import { sveltekitCookies } from "better-auth/svelte-kit";
5
+ import { getRequestEvent } from "$app/server";
6
+ import { building } from "$app/environment";
7
+ import { env } from "$env/dynamic/private";
8
+ import { db } from "./db";
9
+ import * as schema from "./db/schema";
10
+ import { notif } from "./notifications";
11
+
12
+ let _auth: Auth | undefined;
13
+
14
+ function createAuth() {
15
+ return betterAuth({
16
+ baseURL: env.BETTER_AUTH_URL,
17
+ database: drizzleAdapter(db, {
18
+ provider: "pg",
19
+ schema,
20
+ }),
21
+ plugins: [
22
+ magicLink({
23
+ sendMagicLink: async ({ email, url }) => {
24
+ await notif.send({
25
+ type: "magic-link",
26
+ userId: email, // before user exists, use email as userId
27
+ recipientEmail: email,
28
+ data: { url },
29
+ });
30
+ },
31
+ }),
32
+ sveltekitCookies(getRequestEvent),
33
+ ],
34
+ });
35
+ }
36
+
37
+ type Auth = ReturnType<typeof createAuth>;
38
+
39
+ export const auth: Auth = new Proxy({} as Auth, {
40
+ get(_, prop) {
41
+ if (building) {
42
+ throw new Error("Cannot access auth during build");
43
+ }
44
+ if (!_auth) {
45
+ _auth = createAuth();
46
+ }
47
+ return (_auth as unknown as Record<string | symbol, unknown>)[prop];
48
+ },
49
+ });
50
+
51
+ export type Session = Auth["$Infer"]["Session"]["session"];
52
+ export type User = Auth["$Infer"]["Session"]["user"];
@@ -0,0 +1,19 @@
1
+ import { drizzle } from "drizzle-orm/postgres-js";
2
+ import postgres from "postgres";
3
+ import * as schema from "./schema";
4
+ import { env } from "$env/dynamic/private";
5
+
6
+ export type DrizzleDB = ReturnType<typeof drizzle<typeof schema>>;
7
+
8
+ let _db: DrizzleDB | undefined;
9
+
10
+ export const db = new Proxy({} as DrizzleDB, {
11
+ get(_, prop) {
12
+ if (!_db) {
13
+ if (!env.DATABASE_URL) throw new Error("DATABASE_URL is not set");
14
+ const client = postgres(env.DATABASE_URL);
15
+ _db = drizzle(client, { schema, casing: "snake_case" });
16
+ }
17
+ return _db[prop as keyof typeof _db];
18
+ },
19
+ });
@@ -0,0 +1,117 @@
1
+ import { boolean, integer, jsonb, pgTable, text, timestamp } from "drizzle-orm/pg-core";
2
+
3
+ // better-auth tables (managed by better-auth, do not insert/update directly)
4
+ export const user = pgTable("user", {
5
+ id: text("id").primaryKey(),
6
+ name: text("name").notNull(),
7
+ email: text("email").notNull().unique(),
8
+ emailVerified: timestamp("email_verified"),
9
+ image: text("image"),
10
+ createdAt: timestamp("created_at").notNull().defaultNow(),
11
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
12
+ });
13
+
14
+ export const session = pgTable("session", {
15
+ id: text("id").primaryKey(),
16
+ expiresAt: timestamp("expires_at").notNull(),
17
+ token: text("token").notNull().unique(),
18
+ ipAddress: text("ip_address"),
19
+ userAgent: text("user_agent"),
20
+ userId: text("user_id")
21
+ .notNull()
22
+ .references(() => user.id),
23
+ createdAt: timestamp("created_at").notNull().defaultNow(),
24
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
25
+ });
26
+
27
+ export const account = pgTable("account", {
28
+ id: text("id").primaryKey(),
29
+ accountId: text("account_id").notNull(),
30
+ providerId: text("provider_id").notNull(),
31
+ userId: text("user_id")
32
+ .notNull()
33
+ .references(() => user.id),
34
+ accessToken: text("access_token"),
35
+ refreshToken: text("refresh_token"),
36
+ idToken: text("id_token"),
37
+ accessTokenExpiresAt: timestamp("access_token_expires_at"),
38
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
39
+ scope: text("scope"),
40
+ password: text("password"),
41
+ createdAt: timestamp("created_at").notNull().defaultNow(),
42
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
43
+ });
44
+
45
+ export const verification = pgTable("verification", {
46
+ id: text("id").primaryKey(),
47
+ identifier: text("identifier").notNull(),
48
+ value: text("value").notNull(),
49
+ expiresAt: timestamp("expires_at").notNull(),
50
+ createdAt: timestamp("created_at"),
51
+ updatedAt: timestamp("updated_at"),
52
+ });
53
+
54
+ // Notification tables (managed by @mikstack/notifications)
55
+ export const notificationDelivery = pgTable("notification_delivery", {
56
+ id: text("id")
57
+ .primaryKey()
58
+ .$defaultFn(() => crypto.randomUUID()),
59
+ userId: text("user_id")
60
+ .notNull()
61
+ .references(() => user.id),
62
+ type: text("type").notNull(),
63
+ channel: text("channel").notNull(),
64
+ status: text("status", { enum: ["pending", "sent", "delivered", "failed"] })
65
+ .notNull()
66
+ .default("pending"),
67
+ content: jsonb("content"),
68
+ error: text("error"),
69
+ retryOf: text("retry_of"),
70
+ retriesLeft: integer("retries_left").notNull().default(0),
71
+ recipientEmail: text("recipient_email"),
72
+ externalId: text("external_id"),
73
+ createdAt: timestamp("created_at").notNull().defaultNow(),
74
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
75
+ });
76
+
77
+ export const inAppNotification = pgTable("in_app_notification", {
78
+ id: text("id")
79
+ .primaryKey()
80
+ .$defaultFn(() => crypto.randomUUID()),
81
+ userId: text("user_id")
82
+ .notNull()
83
+ .references(() => user.id),
84
+ type: text("type").notNull(),
85
+ title: text("title").notNull(),
86
+ body: text("body"),
87
+ url: text("url"),
88
+ icon: text("icon"),
89
+ read: boolean("read").notNull().default(false),
90
+ createdAt: timestamp("created_at").notNull().defaultNow(),
91
+ });
92
+
93
+ export const notificationPreference = pgTable("notification_preference", {
94
+ id: text("id")
95
+ .primaryKey()
96
+ .$defaultFn(() => crypto.randomUUID()),
97
+ userId: text("user_id")
98
+ .notNull()
99
+ .references(() => user.id),
100
+ notificationType: text("notification_type").notNull(),
101
+ channel: text("channel").notNull(),
102
+ enabled: boolean("enabled").notNull(),
103
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
104
+ });
105
+
106
+ // Application tables
107
+
108
+ export const note = pgTable("note", {
109
+ id: text("id").primaryKey(),
110
+ title: text("title").notNull(),
111
+ content: text("content").notNull().default(""),
112
+ userId: text("user_id")
113
+ .notNull()
114
+ .references(() => user.id),
115
+ createdAt: timestamp("created_at").notNull(),
116
+ updatedAt: timestamp("updated_at").notNull(),
117
+ });
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Database seed script.
3
+ *
4
+ * Run with: {{pmRun}} db:seed
5
+ */
6
+
7
+ // import { db } from './index';
8
+
9
+ // eslint-disable-next-line @typescript-eslint/require-await
10
+ async function seed() {
11
+ console.log("Seeding database...");
12
+
13
+ // Add your seed data here
14
+
15
+ console.log("Seeding complete.");
16
+ }
17
+
18
+ seed().catch((err) => {
19
+ console.error("Seed failed:", err);
20
+ process.exit(1);
21
+ });