@primstack/cli 0.0.1

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 (210) hide show
  1. package/dist/generators/crud/templates/drizzle-table.ts.template +12 -0
  2. package/dist/generators/crud/templates/handlers.ts.template +136 -0
  3. package/dist/generators/crud/templates/routes.ts.template +21 -0
  4. package/dist/generators/crud/templates/schema.ts.template +20 -0
  5. package/dist/index.js +9618 -0
  6. package/dist/integrations/analytics/providers/amplitude/templates/src/analytics/index.ts.template +79 -0
  7. package/dist/integrations/analytics/providers/amplitude/templates/src/analytics/types.ts.template +12 -0
  8. package/dist/integrations/analytics/providers/mixpanel/templates/src/analytics/index.ts.template +62 -0
  9. package/dist/integrations/analytics/providers/mixpanel/templates/src/analytics/types.ts.template +12 -0
  10. package/dist/integrations/analytics/providers/posthog/templates/src/analytics/index.ts.template +67 -0
  11. package/dist/integrations/analytics/providers/posthog/templates/src/analytics/types.ts.template +12 -0
  12. package/dist/integrations/auth/providers/authjoy/templates/api/src/middleware/auth.ts.template +89 -0
  13. package/dist/integrations/auth/providers/authjoy/templates/api/src/routes/auth.ts.template +27 -0
  14. package/dist/integrations/auth/providers/authjoy/templates/web/src/components/AuthProvider.tsx.template +40 -0
  15. package/dist/integrations/auth/providers/authjoy/templates/web/src/hooks/use-auth.ts.template +71 -0
  16. package/dist/integrations/auth/providers/authjoy/templates/web/src/lib/auth.ts.template +59 -0
  17. package/dist/integrations/auth/providers/authjoy/templates/web/src/pages/account.tsx.template +84 -0
  18. package/dist/integrations/auth/providers/authjoy/templates/web/src/pages/login.tsx.template +73 -0
  19. package/dist/integrations/cache/providers/memory/templates/src/cache/index.ts.template +43 -0
  20. package/dist/integrations/cache/providers/memory/templates/src/cache/types.ts.template +3 -0
  21. package/dist/integrations/cache/providers/redis/templates/src/cache/index.ts.template +37 -0
  22. package/dist/integrations/cache/providers/redis/templates/src/cache/types.ts.template +3 -0
  23. package/dist/integrations/cache/providers/valkey/templates/src/cache/index.ts.template +38 -0
  24. package/dist/integrations/cache/providers/valkey/templates/src/cache/types.ts.template +3 -0
  25. package/dist/integrations/db/providers/postgres/templates/drizzle.config.ts.template +10 -0
  26. package/dist/integrations/db/providers/postgres/templates/src/db/index.ts.template +13 -0
  27. package/dist/integrations/db/providers/postgres/templates/src/db/migrate.ts.template +19 -0
  28. package/dist/integrations/db/providers/postgres/templates/src/db/schema/index.ts.template +1 -0
  29. package/dist/integrations/db/providers/postgres/templates/src/db/schema/users.ts.template +12 -0
  30. package/dist/integrations/db/providers/postgres/templates/src/db/seed.ts.template +28 -0
  31. package/dist/integrations/db/providers/sqlite/templates/drizzle.config.ts.template +10 -0
  32. package/dist/integrations/db/providers/sqlite/templates/src/db/index.ts.template +10 -0
  33. package/dist/integrations/db/providers/sqlite/templates/src/db/schema/index.ts.template +1 -0
  34. package/dist/integrations/db/providers/sqlite/templates/src/db/schema/users.ts.template +12 -0
  35. package/dist/integrations/db/providers/sqlite/templates/src/db/seed.ts.template +28 -0
  36. package/dist/integrations/db/providers/supabase/templates/drizzle.config.ts.template +10 -0
  37. package/dist/integrations/db/providers/supabase/templates/src/db/index.ts.template +13 -0
  38. package/dist/integrations/db/providers/supabase/templates/src/db/migrate.ts.template +19 -0
  39. package/dist/integrations/db/providers/supabase/templates/src/db/schema/index.ts.template +1 -0
  40. package/dist/integrations/db/providers/supabase/templates/src/db/schema/users.ts.template +12 -0
  41. package/dist/integrations/db/providers/supabase/templates/src/db/seed.ts.template +28 -0
  42. package/dist/integrations/db/providers/turso/templates/drizzle.config.ts.template +11 -0
  43. package/dist/integrations/db/providers/turso/templates/src/db/index.ts.template +14 -0
  44. package/dist/integrations/db/providers/turso/templates/src/db/schema/index.ts.template +1 -0
  45. package/dist/integrations/db/providers/turso/templates/src/db/schema/users.ts.template +12 -0
  46. package/dist/integrations/db/providers/turso/templates/src/db/seed.ts.template +28 -0
  47. package/dist/integrations/email/providers/nodemailer/templates/src/email/index.ts.template +24 -0
  48. package/dist/integrations/email/providers/nodemailer/templates/src/email/templates/index.ts.template +1 -0
  49. package/dist/integrations/email/providers/nodemailer/templates/src/email/templates/welcome.ts.template +7 -0
  50. package/dist/integrations/email/providers/nodemailer/templates/src/email/types.ts.template +7 -0
  51. package/dist/integrations/email/providers/resend/templates/src/email/index.ts.template +18 -0
  52. package/dist/integrations/email/providers/resend/templates/src/email/templates/index.ts.template +1 -0
  53. package/dist/integrations/email/providers/resend/templates/src/email/templates/welcome.ts.template +7 -0
  54. package/dist/integrations/email/providers/resend/templates/src/email/types.ts.template +7 -0
  55. package/dist/integrations/email/providers/sendgrid/templates/src/email/index.ts.template +16 -0
  56. package/dist/integrations/email/providers/sendgrid/templates/src/email/templates/index.ts.template +1 -0
  57. package/dist/integrations/email/providers/sendgrid/templates/src/email/templates/welcome.ts.template +7 -0
  58. package/dist/integrations/email/providers/sendgrid/templates/src/email/types.ts.template +7 -0
  59. package/dist/integrations/flags/providers/local/templates/api/src/lib/flags.ts.template +97 -0
  60. package/dist/integrations/flags/providers/local/templates/api/src/routes/flags.ts.template +36 -0
  61. package/dist/integrations/flags/providers/local/templates/flags.json.template +8 -0
  62. package/dist/integrations/flags/providers/local/templates/web/src/hooks/use-flag.ts.template +60 -0
  63. package/dist/integrations/logging/providers/axiom/templates/src/logging/index.ts.template +56 -0
  64. package/dist/integrations/logging/providers/axiom/templates/src/logging/types.ts.template +5 -0
  65. package/dist/integrations/logging/providers/pino/templates/src/logging/index.ts.template +21 -0
  66. package/dist/integrations/logging/providers/pino/templates/src/logging/types.ts.template +5 -0
  67. package/dist/integrations/logging/providers/winston/templates/src/logging/index.ts.template +30 -0
  68. package/dist/integrations/logging/providers/winston/templates/src/logging/types.ts.template +5 -0
  69. package/dist/integrations/monitor/providers/datadog/templates/src/monitor/index.ts.template +78 -0
  70. package/dist/integrations/monitor/providers/datadog/templates/src/monitor/types.ts.template +12 -0
  71. package/dist/integrations/monitor/providers/newrelic/templates/src/monitor/index.ts.template +60 -0
  72. package/dist/integrations/monitor/providers/newrelic/templates/src/monitor/types.ts.template +12 -0
  73. package/dist/integrations/monitor/providers/sentry/templates/src/monitor/index.ts.template +70 -0
  74. package/dist/integrations/monitor/providers/sentry/templates/src/monitor/types.ts.template +12 -0
  75. package/dist/integrations/queue/providers/bullmq/templates/src/queue/index.ts.template +56 -0
  76. package/dist/integrations/queue/providers/bullmq/templates/src/queue/types.ts.template +10 -0
  77. package/dist/integrations/queue/providers/memory/templates/src/queue/index.ts.template +73 -0
  78. package/dist/integrations/queue/providers/memory/templates/src/queue/types.ts.template +10 -0
  79. package/dist/integrations/queue/providers/pgboss/templates/src/queue/index.ts.template +34 -0
  80. package/dist/integrations/queue/providers/pgboss/templates/src/queue/types.ts.template +10 -0
  81. package/dist/integrations/ratelimit/providers/memory/templates/src/ratelimit/index.ts.template +95 -0
  82. package/dist/integrations/ratelimit/providers/memory/templates/src/ratelimit/types.ts.template +12 -0
  83. package/dist/integrations/ratelimit/providers/rate-limiter-flexible/templates/src/ratelimit/index.ts.template +80 -0
  84. package/dist/integrations/ratelimit/providers/rate-limiter-flexible/templates/src/ratelimit/types.ts.template +12 -0
  85. package/dist/integrations/ratelimit/providers/upstash/templates/src/ratelimit/index.ts.template +67 -0
  86. package/dist/integrations/ratelimit/providers/upstash/templates/src/ratelimit/types.ts.template +12 -0
  87. package/dist/integrations/schedule/providers/bullmq/templates/src/schedule/index.ts.template +81 -0
  88. package/dist/integrations/schedule/providers/bullmq/templates/src/schedule/types.ts.template +10 -0
  89. package/dist/integrations/schedule/providers/croner/templates/src/schedule/index.ts.template +47 -0
  90. package/dist/integrations/schedule/providers/croner/templates/src/schedule/types.ts.template +10 -0
  91. package/dist/integrations/schedule/providers/node-cron/templates/src/schedule/index.ts.template +45 -0
  92. package/dist/integrations/schedule/providers/node-cron/templates/src/schedule/types.ts.template +10 -0
  93. package/dist/integrations/search/providers/algolia/templates/src/search/index.ts.template +52 -0
  94. package/dist/integrations/search/providers/algolia/templates/src/search/types.ts.template +18 -0
  95. package/dist/integrations/search/providers/meilisearch/templates/src/search/index.ts.template +49 -0
  96. package/dist/integrations/search/providers/meilisearch/templates/src/search/types.ts.template +18 -0
  97. package/dist/integrations/search/providers/typesense/templates/src/search/index.ts.template +71 -0
  98. package/dist/integrations/search/providers/typesense/templates/src/search/types.ts.template +35 -0
  99. package/dist/integrations/storage/providers/local/templates/src/storage/index.ts.template +69 -0
  100. package/dist/integrations/storage/providers/local/templates/src/storage/types.ts.template +12 -0
  101. package/dist/integrations/storage/providers/r2/templates/src/storage/index.ts.template +80 -0
  102. package/dist/integrations/storage/providers/r2/templates/src/storage/types.ts.template +12 -0
  103. package/dist/integrations/storage/providers/s3/templates/src/storage/index.ts.template +78 -0
  104. package/dist/integrations/storage/providers/s3/templates/src/storage/types.ts.template +12 -0
  105. package/dist/integrations/stripe/templates/api/src/lib/stripe.ts.template +259 -0
  106. package/dist/integrations/stripe/templates/api/src/routes/stripe-webhooks.ts.template +284 -0
  107. package/dist/integrations/stripe/templates/api/stripe.config.ts.template +178 -0
  108. package/dist/integrations/stripe/templates/shared/src/pricing.ts.template +117 -0
  109. package/dist/integrations/stripe/templates/shared/src/stripe-types.ts.template +133 -0
  110. package/dist/integrations/stripe/templates/web/src/components/billing-settings.tsx.template +123 -0
  111. package/dist/integrations/stripe/templates/web/src/components/pricing-cards.tsx.template +115 -0
  112. package/dist/integrations/stripe/templates/web/src/pages/pricing.tsx.template +95 -0
  113. package/dist/templates/api/fastify/.env.example.template +7 -0
  114. package/dist/templates/api/fastify/.gitignore.template +24 -0
  115. package/dist/templates/api/fastify/package.json.template +23 -0
  116. package/dist/templates/api/fastify/src/index.ts.template +52 -0
  117. package/dist/templates/api/fastify/src/lib/env.ts.template +20 -0
  118. package/dist/templates/api/fastify/src/routes/health.ts.template +12 -0
  119. package/dist/templates/api/fastify/tsconfig.json.template +18 -0
  120. package/dist/templates/api/fastify-postgres/.env.example.template +10 -0
  121. package/dist/templates/api/fastify-postgres/drizzle.config.ts.template +10 -0
  122. package/dist/templates/api/fastify-postgres/package.json.template +16 -0
  123. package/dist/templates/api/fastify-postgres/src/db/index.ts.template +9 -0
  124. package/dist/templates/api/fastify-postgres/src/db/schema/users.ts.template +12 -0
  125. package/dist/templates/api/fastify-sqlite/.env.example.template +10 -0
  126. package/dist/templates/api/fastify-sqlite/drizzle.config.ts.template +10 -0
  127. package/dist/templates/api/fastify-sqlite/package.json.template +16 -0
  128. package/dist/templates/api/fastify-sqlite/src/db/index.ts.template +6 -0
  129. package/dist/templates/api/fastify-sqlite/src/db/schema/users.ts.template +12 -0
  130. package/dist/templates/api/fastify-supabase/.env.example.template +10 -0
  131. package/dist/templates/api/fastify-supabase/drizzle.config.ts.template +10 -0
  132. package/dist/templates/api/fastify-supabase/package.json.template +15 -0
  133. package/dist/templates/api/fastify-supabase/src/db/index.ts.template +9 -0
  134. package/dist/templates/api/fastify-supabase/src/db/schema/users.ts.template +12 -0
  135. package/dist/templates/api/fastify-turso/.env.example.template +11 -0
  136. package/dist/templates/api/fastify-turso/drizzle.config.ts.template +11 -0
  137. package/dist/templates/api/fastify-turso/package.json.template +15 -0
  138. package/dist/templates/api/fastify-turso/src/db/index.ts.template +10 -0
  139. package/dist/templates/api/fastify-turso/src/db/schema/users.ts.template +12 -0
  140. package/dist/templates/fullstack/api/.env.example.template +10 -0
  141. package/dist/templates/fullstack/api/.gitignore.template +4 -0
  142. package/dist/templates/fullstack/api/drizzle.config.ts.template +14 -0
  143. package/dist/templates/fullstack/api/package.json.template +33 -0
  144. package/dist/templates/fullstack/api/src/db/index.ts.template +13 -0
  145. package/dist/templates/fullstack/api/src/db/schema/api-keys.ts.template +19 -0
  146. package/dist/templates/fullstack/api/src/db/schema/audit-logs.ts.template +23 -0
  147. package/dist/templates/fullstack/api/src/db/schema/index.ts.template +8 -0
  148. package/dist/templates/fullstack/api/src/db/schema/invites.ts.template +19 -0
  149. package/dist/templates/fullstack/api/src/db/schema/memberships.ts.template +16 -0
  150. package/dist/templates/fullstack/api/src/db/schema/organizations.ts.template +13 -0
  151. package/dist/templates/fullstack/api/src/db/schema/plans.ts.template +29 -0
  152. package/dist/templates/fullstack/api/src/db/schema/subscriptions.ts.template +38 -0
  153. package/dist/templates/fullstack/api/src/db/schema/users.ts.template +14 -0
  154. package/dist/templates/fullstack/api/src/index.ts.template +54 -0
  155. package/dist/templates/fullstack/api/src/lib/env.ts.template +22 -0
  156. package/dist/templates/fullstack/api/src/routes/health.ts.template +14 -0
  157. package/dist/templates/fullstack/api/tsconfig.json.template +15 -0
  158. package/dist/templates/fullstack/root/.gitignore.template +26 -0
  159. package/dist/templates/fullstack/root/package.json.template +15 -0
  160. package/dist/templates/fullstack/root/pnpm-workspace.yaml.template +3 -0
  161. package/dist/templates/fullstack/root/turbo.json.template +17 -0
  162. package/dist/templates/fullstack/shared/package.json.template +36 -0
  163. package/dist/templates/fullstack/shared/src/index.ts.template +8 -0
  164. package/dist/templates/fullstack/shared/src/schemas/api-key.ts.template +28 -0
  165. package/dist/templates/fullstack/shared/src/schemas/audit-log.ts.template +41 -0
  166. package/dist/templates/fullstack/shared/src/schemas/index.ts.template +8 -0
  167. package/dist/templates/fullstack/shared/src/schemas/invite.ts.template +25 -0
  168. package/dist/templates/fullstack/shared/src/schemas/membership.ts.template +20 -0
  169. package/dist/templates/fullstack/shared/src/schemas/organization.ts.template +18 -0
  170. package/dist/templates/fullstack/shared/src/schemas/plan.ts.template +38 -0
  171. package/dist/templates/fullstack/shared/src/schemas/subscription.ts.template +56 -0
  172. package/dist/templates/fullstack/shared/src/schemas/user.ts.template +21 -0
  173. package/dist/templates/fullstack/shared/src/types/index.ts.template +75 -0
  174. package/dist/templates/fullstack/shared/src/validators/index.ts.template +53 -0
  175. package/dist/templates/fullstack/shared/tsconfig.json.template +17 -0
  176. package/dist/templates/fullstack/web/.gitignore.template +3 -0
  177. package/dist/templates/fullstack/web/index.html.template +13 -0
  178. package/dist/templates/fullstack/web/package.json.template +23 -0
  179. package/dist/templates/fullstack/web/src/App.tsx.template +47 -0
  180. package/dist/templates/fullstack/web/src/index.css.template +54 -0
  181. package/dist/templates/fullstack/web/src/main.tsx.template +10 -0
  182. package/dist/templates/fullstack/web/src/vite-env.d.ts.template +1 -0
  183. package/dist/templates/fullstack/web/tsconfig.json.template +21 -0
  184. package/dist/templates/fullstack/web/tsconfig.node.json.template +11 -0
  185. package/dist/templates/fullstack/web/vite.config.ts.template +15 -0
  186. package/dist/templates/hosted/root/.env.local.template +13 -0
  187. package/dist/templates/hosted/root/.gitignore.template +32 -0
  188. package/dist/templates/hosted/root/CLAUDE.md.template +139 -0
  189. package/dist/templates/hosted/root/drizzle.config.ts.template +10 -0
  190. package/dist/templates/hosted/root/next.config.ts.template +15 -0
  191. package/dist/templates/hosted/root/package.json.template +40 -0
  192. package/dist/templates/hosted/root/postcss.config.mjs.template +9 -0
  193. package/dist/templates/hosted/root/primstack.config.json.template +5 -0
  194. package/dist/templates/hosted/root/tailwind.config.ts.template +14 -0
  195. package/dist/templates/hosted/root/tsconfig.json.template +25 -0
  196. package/dist/templates/hosted/root/wrangler.toml.template +9 -0
  197. package/dist/templates/hosted/src/app/actions/example.ts.template +50 -0
  198. package/dist/templates/hosted/src/app/api/health/route.ts.template +5 -0
  199. package/dist/templates/hosted/src/app/auth/login/page.tsx.template +32 -0
  200. package/dist/templates/hosted/src/app/globals.css.template +59 -0
  201. package/dist/templates/hosted/src/app/layout.tsx.template +24 -0
  202. package/dist/templates/hosted/src/app/page.tsx.template +34 -0
  203. package/dist/templates/hosted/src/db/migrations/0000_initial.sql.template +43 -0
  204. package/dist/templates/hosted/src/db/schema.ts.template +52 -0
  205. package/dist/templates/hosted/src/env.d.ts.template +10 -0
  206. package/dist/templates/hosted/src/instrumentation.ts.template +6 -0
  207. package/dist/templates/hosted/src/lib/auth.ts.template +35 -0
  208. package/dist/templates/hosted/src/lib/db.ts.template +17 -0
  209. package/dist/templates/hosted/src/middleware.ts.template +6 -0
  210. package/package.json +46 -0
@@ -0,0 +1,12 @@
1
+ import { pgTable, uuid, varchar, timestamp } from 'drizzle-orm/pg-core';
2
+
3
+ export const users = pgTable('users', {
4
+ id: uuid('id').primaryKey().defaultRandom(),
5
+ email: varchar('email', { length: 255 }).notNull().unique(),
6
+ name: varchar('name', { length: 255 }),
7
+ createdAt: timestamp('created_at').defaultNow().notNull(),
8
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
9
+ });
10
+
11
+ export type User = typeof users.$inferSelect;
12
+ export type NewUser = typeof users.$inferInsert;
@@ -0,0 +1,11 @@
1
+ # Server configuration
2
+ PORT={{PORT}}
3
+ HOST=0.0.0.0
4
+ NODE_ENV=development
5
+
6
+ # CORS (optional, defaults to allow all in development)
7
+ # CORS_ORIGIN=https://your-frontend.com
8
+
9
+ # Turso Database
10
+ TURSO_DATABASE_URL=libsql://your-database.turso.io
11
+ TURSO_AUTH_TOKEN=your-auth-token
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'drizzle-kit';
2
+
3
+ export default defineConfig({
4
+ schema: './src/db/schema',
5
+ out: './src/db/migrations',
6
+ dialect: 'turso',
7
+ dbCredentials: {
8
+ url: process.env.TURSO_DATABASE_URL!,
9
+ authToken: process.env.TURSO_AUTH_TOKEN,
10
+ },
11
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "scripts": {
3
+ "db:generate": "drizzle-kit generate",
4
+ "db:migrate": "drizzle-kit migrate",
5
+ "db:push": "drizzle-kit push",
6
+ "db:studio": "drizzle-kit studio"
7
+ },
8
+ "dependencies": {
9
+ "drizzle-orm": "^0.36.0",
10
+ "@libsql/client": "^0.14.0"
11
+ },
12
+ "devDependencies": {
13
+ "drizzle-kit": "^0.28.0"
14
+ }
15
+ }
@@ -0,0 +1,10 @@
1
+ import { drizzle } from 'drizzle-orm/libsql';
2
+ import { createClient } from '@libsql/client';
3
+ import * as schema from './schema/users.js';
4
+
5
+ const client = createClient({
6
+ url: process.env.TURSO_DATABASE_URL!,
7
+ authToken: process.env.TURSO_AUTH_TOKEN!,
8
+ });
9
+
10
+ export const db = drizzle(client, { schema });
@@ -0,0 +1,12 @@
1
+ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
2
+
3
+ export const users = sqliteTable('users', {
4
+ id: integer('id').primaryKey({ autoIncrement: true }),
5
+ email: text('email').notNull().unique(),
6
+ name: text('name'),
7
+ createdAt: text('created_at').notNull().default('CURRENT_TIMESTAMP'),
8
+ updatedAt: text('updated_at').notNull().default('CURRENT_TIMESTAMP'),
9
+ });
10
+
11
+ export type User = typeof users.$inferSelect;
12
+ export type NewUser = typeof users.$inferInsert;
@@ -0,0 +1,10 @@
1
+ # Server configuration
2
+ PORT={{PORT}}
3
+ HOST=0.0.0.0
4
+ NODE_ENV=development
5
+
6
+ # CORS (optional, defaults to allow all in development)
7
+ # CORS_ORIGIN=https://your-frontend.com
8
+
9
+ # Database (update with your credentials if needed)
10
+ DATABASE_URL=postgresql://localhost:5432/{{PROJECT_NAME}}
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ dist/
3
+ .env
4
+ *.log
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from 'drizzle-kit';
2
+
3
+ if (!process.env.DATABASE_URL) {
4
+ throw new Error('DATABASE_URL environment variable is required');
5
+ }
6
+
7
+ export default defineConfig({
8
+ schema: './src/db/schema',
9
+ out: './src/db/migrations',
10
+ dialect: 'postgresql',
11
+ dbCredentials: {
12
+ url: process.env.DATABASE_URL,
13
+ },
14
+ });
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@{{PROJECT_NAME}}/api",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "tsx watch src/index.ts",
7
+ "build": "tsc",
8
+ "start": "node dist/index.js",
9
+ "typecheck": "tsc --noEmit",
10
+ "db:generate": "drizzle-kit generate",
11
+ "db:migrate": "drizzle-kit migrate",
12
+ "db:push": "drizzle-kit push",
13
+ "db:studio": "drizzle-kit studio"
14
+ },
15
+ "dependencies": {
16
+ "@{{PROJECT_NAME}}/shared": "workspace:*",
17
+ "@fastify/cors": "^10.0.0",
18
+ "@fastify/helmet": "^13.0.0",
19
+ "@fastify/sensible": "^6.0.0",
20
+ "dotenv": "^16.4.0",
21
+ "fastify": "^5.0.0",
22
+ "drizzle-orm": "^0.36.0",
23
+ "pg": "^8.13.0",
24
+ "zod": "^3.23.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^20.0.0",
28
+ "@types/pg": "^8.11.0",
29
+ "drizzle-kit": "^0.28.0",
30
+ "tsx": "^4.0.0",
31
+ "typescript": "^5.7.0"
32
+ }
33
+ }
@@ -0,0 +1,13 @@
1
+ import { drizzle } from 'drizzle-orm/node-postgres';
2
+ import pg from 'pg';
3
+ import * as schema from './schema/index.js';
4
+
5
+ export const pool = new pg.Pool({
6
+ connectionString: process.env.DATABASE_URL,
7
+ });
8
+
9
+ export const db = drizzle(pool, { schema });
10
+
11
+ export async function closeDatabase(): Promise<void> {
12
+ await pool.end();
13
+ }
@@ -0,0 +1,19 @@
1
+ import { pgTable, uuid, varchar, timestamp, index, uniqueIndex } from 'drizzle-orm/pg-core';
2
+ import { organizations } from './organizations.js';
3
+
4
+ export const apiKeys = pgTable('api_keys', {
5
+ id: uuid('id').primaryKey().defaultRandom(),
6
+ organizationId: uuid('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),
7
+ name: varchar('name', { length: 255 }).notNull(),
8
+ keyHash: varchar('key_hash', { length: 255 }).notNull(),
9
+ keyPrefix: varchar('key_prefix', { length: 8 }).notNull(),
10
+ lastUsedAt: timestamp('last_used_at'),
11
+ expiresAt: timestamp('expires_at'),
12
+ createdAt: timestamp('created_at').defaultNow().notNull(),
13
+ }, (table) => [
14
+ uniqueIndex('api_keys_key_hash_idx').on(table.keyHash),
15
+ index('api_keys_org_idx').on(table.organizationId),
16
+ ]);
17
+
18
+ export type ApiKey = typeof apiKeys.$inferSelect;
19
+ export type NewApiKey = typeof apiKeys.$inferInsert;
@@ -0,0 +1,23 @@
1
+ import { pgTable, uuid, varchar, timestamp, json, index } from 'drizzle-orm/pg-core';
2
+ import { organizations } from './organizations.js';
3
+ import { users } from './users.js';
4
+
5
+ export const auditLogs = pgTable('audit_logs', {
6
+ id: uuid('id').primaryKey().defaultRandom(),
7
+ organizationId: uuid('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),
8
+ userId: uuid('user_id').references(() => users.id, { onDelete: 'set null' }),
9
+ action: varchar('action', { length: 255 }).notNull(),
10
+ resourceType: varchar('resource_type', { length: 100 }).notNull(),
11
+ resourceId: varchar('resource_id', { length: 255 }),
12
+ metadata: json('metadata').$type<Record<string, unknown>>().notNull().default({}),
13
+ ipAddress: varchar('ip_address', { length: 45 }),
14
+ userAgent: varchar('user_agent', { length: 500 }),
15
+ createdAt: timestamp('created_at').defaultNow().notNull(),
16
+ }, (table) => [
17
+ index('audit_logs_org_idx').on(table.organizationId),
18
+ index('audit_logs_action_idx').on(table.action),
19
+ index('audit_logs_created_idx').on(table.createdAt),
20
+ ]);
21
+
22
+ export type AuditLog = typeof auditLogs.$inferSelect;
23
+ export type NewAuditLog = typeof auditLogs.$inferInsert;
@@ -0,0 +1,8 @@
1
+ export * from './users.js';
2
+ export * from './organizations.js';
3
+ export * from './memberships.js';
4
+ export * from './plans.js';
5
+ export * from './subscriptions.js';
6
+ export * from './invites.js';
7
+ export * from './api-keys.js';
8
+ export * from './audit-logs.js';
@@ -0,0 +1,19 @@
1
+ import { pgTable, uuid, varchar, timestamp, index } from 'drizzle-orm/pg-core';
2
+ import { organizations } from './organizations.js';
3
+
4
+ export const invites = pgTable('invites', {
5
+ id: uuid('id').primaryKey().defaultRandom(),
6
+ email: varchar('email', { length: 255 }).notNull(),
7
+ organizationId: uuid('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),
8
+ role: varchar('role', { length: 50 }).notNull().$type<'admin' | 'member'>(),
9
+ token: varchar('token', { length: 255 }).notNull().unique(),
10
+ expiresAt: timestamp('expires_at').notNull(),
11
+ acceptedAt: timestamp('accepted_at'),
12
+ createdAt: timestamp('created_at').defaultNow().notNull(),
13
+ }, (table) => [
14
+ index('invites_email_idx').on(table.email),
15
+ index('invites_token_idx').on(table.token),
16
+ ]);
17
+
18
+ export type Invite = typeof invites.$inferSelect;
19
+ export type NewInvite = typeof invites.$inferInsert;
@@ -0,0 +1,16 @@
1
+ import { pgTable, uuid, varchar, timestamp, uniqueIndex } from 'drizzle-orm/pg-core';
2
+ import { users } from './users.js';
3
+ import { organizations } from './organizations.js';
4
+
5
+ export const memberships = pgTable('memberships', {
6
+ id: uuid('id').primaryKey().defaultRandom(),
7
+ userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
8
+ organizationId: uuid('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),
9
+ role: varchar('role', { length: 50 }).notNull().$type<'owner' | 'admin' | 'member'>(),
10
+ createdAt: timestamp('created_at').defaultNow().notNull(),
11
+ }, (table) => [
12
+ uniqueIndex('memberships_user_org_idx').on(table.userId, table.organizationId),
13
+ ]);
14
+
15
+ export type Membership = typeof memberships.$inferSelect;
16
+ export type NewMembership = typeof memberships.$inferInsert;
@@ -0,0 +1,13 @@
1
+ import { pgTable, uuid, varchar, timestamp } from 'drizzle-orm/pg-core';
2
+
3
+ export const organizations = pgTable('organizations', {
4
+ id: uuid('id').primaryKey().defaultRandom(),
5
+ name: varchar('name', { length: 255 }).notNull(),
6
+ slug: varchar('slug', { length: 255 }).notNull().unique(),
7
+ logoUrl: varchar('logo_url', { length: 500 }),
8
+ createdAt: timestamp('created_at').defaultNow().notNull(),
9
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
10
+ });
11
+
12
+ export type Organization = typeof organizations.$inferSelect;
13
+ export type NewOrganization = typeof organizations.$inferInsert;
@@ -0,0 +1,29 @@
1
+ import { pgTable, uuid, varchar, integer, boolean, timestamp, json } from 'drizzle-orm/pg-core';
2
+
3
+ export const plans = pgTable('plans', {
4
+ id: uuid('id').primaryKey().defaultRandom(),
5
+ name: varchar('name', { length: 255 }).notNull(),
6
+ slug: varchar('slug', { length: 255 }).notNull().unique(),
7
+ description: varchar('description', { length: 1000 }),
8
+ priceMonthly: integer('price_monthly').notNull(), // cents
9
+ priceYearly: integer('price_yearly').notNull(), // cents
10
+ features: json('features').$type<string[]>().notNull().default([]),
11
+ limits: json('limits').$type<{
12
+ seats: number | null;
13
+ storage: number | null;
14
+ apiCalls: number | null;
15
+ }>().notNull().default({ seats: null, storage: null, apiCalls: null }),
16
+ isActive: boolean('is_active').default(true).notNull(),
17
+
18
+ // Stripe integration fields
19
+ stripeProductIdTest: varchar('stripe_product_id_test', { length: 255 }),
20
+ stripeProductIdLive: varchar('stripe_product_id_live', { length: 255 }),
21
+ currentPriceLookupKeyMonthly: varchar('current_price_lookup_key_monthly', { length: 255 }), // e.g., 'pro_monthly_v2'
22
+ currentPriceLookupKeyYearly: varchar('current_price_lookup_key_yearly', { length: 255 }),
23
+
24
+ createdAt: timestamp('created_at').defaultNow().notNull(),
25
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
26
+ });
27
+
28
+ export type Plan = typeof plans.$inferSelect;
29
+ export type NewPlan = typeof plans.$inferInsert;
@@ -0,0 +1,38 @@
1
+ import { pgTable, uuid, varchar, boolean, timestamp, integer, index } from 'drizzle-orm/pg-core';
2
+ import { organizations } from './organizations.js';
3
+ import { plans } from './plans.js';
4
+
5
+ export const subscriptions = pgTable('subscriptions', {
6
+ id: uuid('id').primaryKey().defaultRandom(),
7
+ organizationId: uuid('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),
8
+ planId: uuid('plan_id').notNull().references(() => plans.id),
9
+ status: varchar('status', { length: 50 }).notNull().$type<'active' | 'canceled' | 'past_due' | 'trialing' | 'incomplete'>(),
10
+ currentPeriodStart: timestamp('current_period_start').notNull(),
11
+ currentPeriodEnd: timestamp('current_period_end').notNull(),
12
+ cancelAtPeriodEnd: boolean('cancel_at_period_end').default(false).notNull(),
13
+
14
+ // Stripe integration fields
15
+ stripeSubscriptionId: varchar('stripe_subscription_id', { length: 255 }),
16
+ stripeCustomerId: varchar('stripe_customer_id', { length: 255 }),
17
+
18
+ // Price versioning (locks in the price at subscription time)
19
+ billingInterval: varchar('billing_interval', { length: 20 }).$type<'monthly' | 'yearly'>(),
20
+ priceLookupKey: varchar('price_lookup_key', { length: 255 }), // e.g., 'pro_monthly_v1'
21
+ priceVersion: integer('price_version'),
22
+ priceAmountCents: integer('price_amount_cents'), // Locked-in price
23
+
24
+ // Trial support
25
+ trialStart: timestamp('trial_start'),
26
+ trialEnd: timestamp('trial_end'),
27
+
28
+ canceledAt: timestamp('canceled_at'),
29
+ createdAt: timestamp('created_at').defaultNow().notNull(),
30
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
31
+ }, (table) => [
32
+ index('subscriptions_org_idx').on(table.organizationId),
33
+ index('subscriptions_stripe_id_idx').on(table.stripeSubscriptionId),
34
+ index('subscriptions_customer_idx').on(table.stripeCustomerId),
35
+ ]);
36
+
37
+ export type Subscription = typeof subscriptions.$inferSelect;
38
+ export type NewSubscription = typeof subscriptions.$inferInsert;
@@ -0,0 +1,14 @@
1
+ import { pgTable, uuid, varchar, boolean, timestamp } from 'drizzle-orm/pg-core';
2
+
3
+ export const users = pgTable('users', {
4
+ id: uuid('id').primaryKey().defaultRandom(),
5
+ email: varchar('email', { length: 255 }).notNull().unique(),
6
+ name: varchar('name', { length: 255 }),
7
+ avatarUrl: varchar('avatar_url', { length: 500 }),
8
+ emailVerified: boolean('email_verified').default(false).notNull(),
9
+ createdAt: timestamp('created_at').defaultNow().notNull(),
10
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
11
+ });
12
+
13
+ export type User = typeof users.$inferSelect;
14
+ export type NewUser = typeof users.$inferInsert;
@@ -0,0 +1,54 @@
1
+ import Fastify from 'fastify';
2
+ import cors from '@fastify/cors';
3
+ import helmet from '@fastify/helmet';
4
+ import sensible from '@fastify/sensible';
5
+ import { env } from './lib/env.js';
6
+ import { closeDatabase } from './db/index.js';
7
+ import healthRoutes from './routes/health.js';
8
+
9
+ const app = Fastify({
10
+ logger: {
11
+ level: env.NODE_ENV === 'development' ? 'info' : 'warn',
12
+ },
13
+ });
14
+
15
+ // Plugins
16
+ app.register(helmet);
17
+ app.register(cors, {
18
+ origin: env.NODE_ENV === 'development'
19
+ ? true
20
+ : (env.CORS_ORIGIN?.split(',').map((o) => o.trim()).filter(Boolean) ?? false),
21
+ });
22
+ app.register(sensible);
23
+
24
+ // Routes
25
+ app.register(healthRoutes, { prefix: '/health' });
26
+
27
+ // Graceful shutdown
28
+ const shutdown = async (signal: string) => {
29
+ app.log.info(`Received ${signal}, shutting down gracefully...`);
30
+ try {
31
+ await app.close();
32
+ await closeDatabase();
33
+ process.exit(0);
34
+ } catch (err) {
35
+ app.log.error('Error during shutdown:', err);
36
+ process.exit(1);
37
+ }
38
+ };
39
+
40
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
41
+ process.on('SIGINT', () => shutdown('SIGINT'));
42
+
43
+ // Start server
44
+ const start = async () => {
45
+ try {
46
+ await app.listen({ port: env.PORT, host: env.HOST });
47
+ app.log.info(`Server running at http://${env.HOST}:${env.PORT}`);
48
+ } catch (err) {
49
+ app.log.error(err);
50
+ process.exit(1);
51
+ }
52
+ };
53
+
54
+ start();
@@ -0,0 +1,22 @@
1
+ import 'dotenv/config';
2
+ import { z } from 'zod';
3
+
4
+ const envSchema = z.object({
5
+ PORT: z.coerce.number().default({{PORT}}),
6
+ HOST: z.string().default('0.0.0.0'),
7
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
8
+ CORS_ORIGIN: z.string().min(1).optional(),
9
+ DATABASE_URL: z.string().min(1),
10
+ });
11
+
12
+ export const env = envSchema
13
+ .superRefine((val, ctx) => {
14
+ if (val.NODE_ENV === 'production' && !val.CORS_ORIGIN) {
15
+ ctx.addIssue({
16
+ code: z.ZodIssueCode.custom,
17
+ message: 'CORS_ORIGIN is required when NODE_ENV=production',
18
+ path: ['CORS_ORIGIN'],
19
+ });
20
+ }
21
+ })
22
+ .parse(process.env);
@@ -0,0 +1,14 @@
1
+ import { FastifyPluginAsync } from 'fastify';
2
+
3
+ const healthRoutes: FastifyPluginAsync = async (fastify) => {
4
+ fastify.get('/', async () => {
5
+ return { status: 'ok', timestamp: new Date().toISOString() };
6
+ });
7
+
8
+ fastify.get('/ready', async () => {
9
+ // Add database connectivity check here if needed
10
+ return { status: 'ready', timestamp: new Date().toISOString() };
11
+ });
12
+ };
13
+
14
+ export default healthRoutes;
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src"
12
+ },
13
+ "include": ["src/**/*"],
14
+ "exclude": ["node_modules", "dist"]
15
+ }
@@ -0,0 +1,26 @@
1
+ # Dependencies
2
+ node_modules/
3
+
4
+ # Build outputs
5
+ dist/
6
+ .turbo/
7
+
8
+ # Environment
9
+ .env
10
+ .env.local
11
+ .env.*.local
12
+
13
+ # IDE
14
+ .idea/
15
+ .vscode/
16
+ *.swp
17
+ *.swo
18
+
19
+ # OS
20
+ .DS_Store
21
+ Thumbs.db
22
+
23
+ # Logs
24
+ *.log
25
+ npm-debug.log*
26
+ pnpm-debug.log*
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "turbo dev",
7
+ "build": "turbo build",
8
+ "typecheck": "turbo typecheck",
9
+ "lint": "turbo lint"
10
+ },
11
+ "devDependencies": {
12
+ "turbo": "^2.0.0"
13
+ },
14
+ "packageManager": "pnpm@9.0.0"
15
+ }
@@ -0,0 +1,3 @@
1
+ packages:
2
+ - "apps/*"
3
+ - "packages/*"
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://turbo.build/schema.json",
3
+ "tasks": {
4
+ "build": {
5
+ "dependsOn": ["^build"],
6
+ "outputs": ["dist/**"]
7
+ },
8
+ "dev": {
9
+ "cache": false,
10
+ "persistent": true
11
+ },
12
+ "typecheck": {
13
+ "dependsOn": ["^build"]
14
+ },
15
+ "lint": {}
16
+ }
17
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@{{PROJECT_NAME}}/shared",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ },
12
+ "./schemas": {
13
+ "types": "./dist/schemas/index.d.ts",
14
+ "import": "./dist/schemas/index.js"
15
+ },
16
+ "./types": {
17
+ "types": "./dist/types/index.d.ts",
18
+ "import": "./dist/types/index.js"
19
+ },
20
+ "./validators": {
21
+ "types": "./dist/validators/index.d.ts",
22
+ "import": "./dist/validators/index.js"
23
+ }
24
+ },
25
+ "scripts": {
26
+ "build": "tsc",
27
+ "dev": "tsc --watch",
28
+ "typecheck": "tsc --noEmit"
29
+ },
30
+ "dependencies": {
31
+ "zod": "^3.23.0"
32
+ },
33
+ "devDependencies": {
34
+ "typescript": "^5.7.0"
35
+ }
36
+ }
@@ -0,0 +1,8 @@
1
+ // Re-export all schemas
2
+ export * from './schemas/index.js';
3
+
4
+ // Re-export all types
5
+ export * from './types/index.js';
6
+
7
+ // Re-export validators
8
+ export * from './validators/index.js';
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+
3
+ export const apiKeySchema = z.object({
4
+ id: z.string().uuid(),
5
+ organizationId: z.string().uuid(),
6
+ name: z.string().min(1).max(255),
7
+ keyHash: z.string(), // hashed, never store plain
8
+ keyPrefix: z.string().length(8), // first 8 chars for identification
9
+ lastUsedAt: z.coerce.date().nullable(),
10
+ expiresAt: z.coerce.date().nullable(),
11
+ createdAt: z.coerce.date(),
12
+ });
13
+
14
+ export const createApiKeySchema = z.object({
15
+ organizationId: z.string().uuid(),
16
+ name: z.string().min(1).max(255),
17
+ expiresAt: z.coerce.date().nullable().optional(),
18
+ });
19
+
20
+ // What's returned when creating a new key (includes unhashed key once)
21
+ export const apiKeyWithSecretSchema = apiKeySchema.extend({
22
+ key: z.string(), // full key, only shown once at creation
23
+ });
24
+
25
+ export const updateApiKeySchema = z.object({
26
+ name: z.string().min(1).max(255).optional(),
27
+ expiresAt: z.coerce.date().nullable().optional(),
28
+ });
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+
3
+ export const auditLogSchema = z.object({
4
+ id: z.string().uuid(),
5
+ organizationId: z.string().uuid(),
6
+ userId: z.string().uuid().nullable(),
7
+ action: z.string(), // e.g., 'user.created', 'subscription.upgraded'
8
+ resourceType: z.string(), // e.g., 'user', 'subscription'
9
+ resourceId: z.string().nullable(),
10
+ metadata: z.record(z.unknown()),
11
+ ipAddress: z.string().nullable(),
12
+ userAgent: z.string().nullable(),
13
+ createdAt: z.coerce.date(),
14
+ });
15
+
16
+ export const createAuditLogSchema = auditLogSchema.omit({
17
+ id: true,
18
+ createdAt: true,
19
+ }).extend({
20
+ metadata: z.record(z.unknown()).optional(),
21
+ });
22
+
23
+ // Common audit actions
24
+ export const AuditAction = {
25
+ USER_CREATED: 'user.created',
26
+ USER_UPDATED: 'user.updated',
27
+ USER_DELETED: 'user.deleted',
28
+ ORG_CREATED: 'organization.created',
29
+ ORG_UPDATED: 'organization.updated',
30
+ MEMBER_ADDED: 'member.added',
31
+ MEMBER_REMOVED: 'member.removed',
32
+ MEMBER_ROLE_CHANGED: 'member.role_changed',
33
+ SUBSCRIPTION_CREATED: 'subscription.created',
34
+ SUBSCRIPTION_UPGRADED: 'subscription.upgraded',
35
+ SUBSCRIPTION_DOWNGRADED: 'subscription.downgraded',
36
+ SUBSCRIPTION_CANCELED: 'subscription.canceled',
37
+ API_KEY_CREATED: 'api_key.created',
38
+ API_KEY_REVOKED: 'api_key.revoked',
39
+ INVITE_SENT: 'invite.sent',
40
+ INVITE_ACCEPTED: 'invite.accepted',
41
+ } as const;