@techstream/quark-create-app 1.2.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 (73) hide show
  1. package/README.md +38 -0
  2. package/package.json +34 -0
  3. package/src/index.js +611 -0
  4. package/templates/base-project/README.md +35 -0
  5. package/templates/base-project/apps/web/next.config.js +6 -0
  6. package/templates/base-project/apps/web/package.json +32 -0
  7. package/templates/base-project/apps/web/postcss.config.mjs +7 -0
  8. package/templates/base-project/apps/web/public/file.svg +1 -0
  9. package/templates/base-project/apps/web/public/globe.svg +1 -0
  10. package/templates/base-project/apps/web/public/next.svg +1 -0
  11. package/templates/base-project/apps/web/public/vercel.svg +1 -0
  12. package/templates/base-project/apps/web/public/window.svg +1 -0
  13. package/templates/base-project/apps/web/src/app/api/auth/[...nextauth]/route.js +4 -0
  14. package/templates/base-project/apps/web/src/app/api/auth/register/route.js +39 -0
  15. package/templates/base-project/apps/web/src/app/api/csrf/route.js +42 -0
  16. package/templates/base-project/apps/web/src/app/api/error-handler.js +21 -0
  17. package/templates/base-project/apps/web/src/app/api/health/route.js +78 -0
  18. package/templates/base-project/apps/web/src/app/api/posts/[id]/route.js +61 -0
  19. package/templates/base-project/apps/web/src/app/api/posts/route.js +34 -0
  20. package/templates/base-project/apps/web/src/app/api/users/[id]/route.js +54 -0
  21. package/templates/base-project/apps/web/src/app/api/users/route.js +36 -0
  22. package/templates/base-project/apps/web/src/app/favicon.ico +0 -0
  23. package/templates/base-project/apps/web/src/app/globals.css +26 -0
  24. package/templates/base-project/apps/web/src/app/layout.js +12 -0
  25. package/templates/base-project/apps/web/src/app/page.js +10 -0
  26. package/templates/base-project/apps/web/src/app/page.test.js +11 -0
  27. package/templates/base-project/apps/web/src/lib/auth-middleware.js +14 -0
  28. package/templates/base-project/apps/web/src/lib/auth.js +102 -0
  29. package/templates/base-project/apps/web/src/middleware.js +265 -0
  30. package/templates/base-project/apps/worker/package.json +28 -0
  31. package/templates/base-project/apps/worker/src/index.js +154 -0
  32. package/templates/base-project/apps/worker/src/index.test.js +19 -0
  33. package/templates/base-project/docker-compose.yml +40 -0
  34. package/templates/base-project/package.json +26 -0
  35. package/templates/base-project/packages/db/package.json +29 -0
  36. package/templates/base-project/packages/db/prisma/migrations/20260202061128_initial/migration.sql +176 -0
  37. package/templates/base-project/packages/db/prisma/migrations/migration_lock.toml +3 -0
  38. package/templates/base-project/packages/db/prisma/schema.prisma +147 -0
  39. package/templates/base-project/packages/db/prisma.config.ts +25 -0
  40. package/templates/base-project/packages/db/scripts/seed.js +47 -0
  41. package/templates/base-project/packages/db/src/client.js +52 -0
  42. package/templates/base-project/packages/db/src/generated/prisma/browser.ts +53 -0
  43. package/templates/base-project/packages/db/src/generated/prisma/client.ts +82 -0
  44. package/templates/base-project/packages/db/src/generated/prisma/commonInputTypes.ts +649 -0
  45. package/templates/base-project/packages/db/src/generated/prisma/enums.ts +19 -0
  46. package/templates/base-project/packages/db/src/generated/prisma/internal/class.ts +305 -0
  47. package/templates/base-project/packages/db/src/generated/prisma/internal/prismaNamespace.ts +1428 -0
  48. package/templates/base-project/packages/db/src/generated/prisma/internal/prismaNamespaceBrowser.ts +217 -0
  49. package/templates/base-project/packages/db/src/generated/prisma/models/Account.ts +2098 -0
  50. package/templates/base-project/packages/db/src/generated/prisma/models/AuditLog.ts +1805 -0
  51. package/templates/base-project/packages/db/src/generated/prisma/models/Job.ts +1737 -0
  52. package/templates/base-project/packages/db/src/generated/prisma/models/Post.ts +1762 -0
  53. package/templates/base-project/packages/db/src/generated/prisma/models/Session.ts +1738 -0
  54. package/templates/base-project/packages/db/src/generated/prisma/models/User.ts +2298 -0
  55. package/templates/base-project/packages/db/src/generated/prisma/models/VerificationToken.ts +1450 -0
  56. package/templates/base-project/packages/db/src/generated/prisma/models.ts +18 -0
  57. package/templates/base-project/packages/db/src/index.js +3 -0
  58. package/templates/base-project/packages/db/src/queries.js +267 -0
  59. package/templates/base-project/packages/db/src/queries.test.js +79 -0
  60. package/templates/base-project/packages/db/src/schemas.js +31 -0
  61. package/templates/base-project/pnpm-workspace.yaml +7 -0
  62. package/templates/base-project/turbo.json +25 -0
  63. package/templates/config/package.json +8 -0
  64. package/templates/config/src/index.js +21 -0
  65. package/templates/jobs/package.json +8 -0
  66. package/templates/jobs/src/definitions.js +9 -0
  67. package/templates/jobs/src/handlers.js +20 -0
  68. package/templates/jobs/src/index.js +2 -0
  69. package/templates/ui/package.json +11 -0
  70. package/templates/ui/src/button.js +19 -0
  71. package/templates/ui/src/card.js +14 -0
  72. package/templates/ui/src/index.js +3 -0
  73. package/templates/ui/src/input.js +11 -0
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Next.js Middleware
3
+ * Handles rate limiting, CORS, and security headers
4
+ */
5
+
6
+ import { getAllowedOrigins } from "@techstream/quark-config/app-url";
7
+ import { NextResponse } from "next/server";
8
+
9
+ // Simple in-memory rate limiter (use Redis for production)
10
+ const rateLimit = new Map();
11
+
12
+ const RATE_LIMIT_CONFIG = {
13
+ windowMs: 15 * 60 * 1000, // 15 minutes
14
+ maxRequests: {
15
+ api: 100, // 100 requests per 15 minutes for general API
16
+ auth: 5, // 5 requests per 15 minutes for auth endpoints
17
+ },
18
+ };
19
+
20
+ /**
21
+ * Rate limiting implementation
22
+ */
23
+ function checkRateLimit(ip, path) {
24
+ const now = Date.now();
25
+ const key = `${ip}:${path}`;
26
+
27
+ // Determine which limit to use
28
+ const isAuthEndpoint = path.startsWith("/api/auth/");
29
+ const maxRequests = isAuthEndpoint
30
+ ? RATE_LIMIT_CONFIG.maxRequests.auth
31
+ : RATE_LIMIT_CONFIG.maxRequests.api;
32
+
33
+ // Get or create rate limit record
34
+ const record = rateLimit.get(key) || {
35
+ count: 0,
36
+ resetTime: now + RATE_LIMIT_CONFIG.windowMs,
37
+ };
38
+
39
+ // Reset if window has passed
40
+ if (now > record.resetTime) {
41
+ record.count = 0;
42
+ record.resetTime = now + RATE_LIMIT_CONFIG.windowMs;
43
+ }
44
+
45
+ // Check if limit exceeded
46
+ if (record.count >= maxRequests) {
47
+ return {
48
+ limited: true,
49
+ resetTime: record.resetTime,
50
+ remaining: 0,
51
+ };
52
+ }
53
+
54
+ // Increment counter
55
+ record.count++;
56
+ rateLimit.set(key, record);
57
+
58
+ return {
59
+ limited: false,
60
+ resetTime: record.resetTime,
61
+ remaining: maxRequests - record.count,
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Clean up old rate limit records periodically
67
+ */
68
+ setInterval(() => {
69
+ const now = Date.now();
70
+ for (const [key, record] of rateLimit.entries()) {
71
+ if (now > record.resetTime) {
72
+ rateLimit.delete(key);
73
+ }
74
+ }
75
+ }, 60 * 1000); // Clean up every minute
76
+
77
+ /**
78
+ * CORS configuration
79
+ */
80
+ const CORS_CONFIG = {
81
+ allowedOrigins: getAllowedOrigins(),
82
+ allowedMethods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
83
+ allowedHeaders: ["Content-Type", "Authorization", "X-CSRF-Token"],
84
+ exposedHeaders: [
85
+ "X-RateLimit-Limit",
86
+ "X-RateLimit-Remaining",
87
+ "X-RateLimit-Reset",
88
+ ],
89
+ credentials: true,
90
+ maxAge: 86400, // 24 hours
91
+ };
92
+
93
+ /**
94
+ * Security headers configuration
95
+ */
96
+ const SECURITY_HEADERS = {
97
+ "X-DNS-Prefetch-Control": "on",
98
+ "Strict-Transport-Security": "max-age=63072000; includeSubDomains",
99
+ "X-Frame-Options": "SAMEORIGIN",
100
+ "X-Content-Type-Options": "nosniff",
101
+ "Referrer-Policy": "strict-origin-when-cross-origin",
102
+ "Permissions-Policy": "camera=(), microphone=(), geolocation=()",
103
+ "Content-Security-Policy":
104
+ "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self';",
105
+ };
106
+
107
+ /**
108
+ * Request size limits (in bytes)
109
+ */
110
+ const REQUEST_SIZE_LIMITS = {
111
+ api: parseInt(process.env.API_BODY_SIZE_LIMIT || "2097152", 10), // 2MB default
112
+ upload: parseInt(process.env.UPLOAD_SIZE_LIMIT || "10485760", 10), // 10MB for uploads
113
+ };
114
+
115
+ export function middleware(request) {
116
+ const { pathname } = request.nextUrl;
117
+ const origin = request.headers.get("origin") || "";
118
+
119
+ // Create response
120
+ const response = NextResponse.next();
121
+
122
+ // Check request body size for API routes
123
+ if (
124
+ pathname.startsWith("/api/") &&
125
+ ["POST", "PUT", "PATCH"].includes(request.method)
126
+ ) {
127
+ const contentLength = request.headers.get("content-length");
128
+ if (contentLength) {
129
+ const size = parseInt(contentLength, 10);
130
+ const limit = pathname.startsWith("/api/upload")
131
+ ? REQUEST_SIZE_LIMITS.upload
132
+ : REQUEST_SIZE_LIMITS.api;
133
+
134
+ if (size > limit) {
135
+ return new NextResponse(
136
+ JSON.stringify({
137
+ error: "Payload too large",
138
+ message: `Request body size exceeds limit of ${Math.round(limit / 1024 / 1024)}MB`,
139
+ maxSize: limit,
140
+ }),
141
+ {
142
+ status: 413,
143
+ headers: {
144
+ "Content-Type": "application/json",
145
+ },
146
+ },
147
+ );
148
+ }
149
+ }
150
+ }
151
+
152
+ // Apply rate limiting to API routes only
153
+ if (pathname.startsWith("/api/")) {
154
+ const ip =
155
+ request.ip || request.headers.get("x-forwarded-for") || "unknown";
156
+ const rateLimitResult = checkRateLimit(ip, pathname);
157
+
158
+ if (rateLimitResult.limited) {
159
+ const retryAfter = Math.ceil(
160
+ (rateLimitResult.resetTime - Date.now()) / 1000,
161
+ );
162
+ return new NextResponse(
163
+ JSON.stringify({
164
+ error: "Too many requests",
165
+ message: "You have exceeded the rate limit. Please try again later.",
166
+ retryAfter,
167
+ }),
168
+ {
169
+ status: 429,
170
+ headers: {
171
+ "Content-Type": "application/json",
172
+ "Retry-After": retryAfter.toString(),
173
+ "X-RateLimit-Limit": pathname.startsWith("/api/auth/")
174
+ ? RATE_LIMIT_CONFIG.maxRequests.auth.toString()
175
+ : RATE_LIMIT_CONFIG.maxRequests.api.toString(),
176
+ "X-RateLimit-Remaining": "0",
177
+ "X-RateLimit-Reset": new Date(
178
+ rateLimitResult.resetTime,
179
+ ).toISOString(),
180
+ },
181
+ },
182
+ );
183
+ }
184
+
185
+ // Add rate limit headers to response
186
+ response.headers.set(
187
+ "X-RateLimit-Limit",
188
+ pathname.startsWith("/api/auth/")
189
+ ? RATE_LIMIT_CONFIG.maxRequests.auth.toString()
190
+ : RATE_LIMIT_CONFIG.maxRequests.api.toString(),
191
+ );
192
+ response.headers.set(
193
+ "X-RateLimit-Remaining",
194
+ rateLimitResult.remaining.toString(),
195
+ );
196
+ response.headers.set(
197
+ "X-RateLimit-Reset",
198
+ new Date(rateLimitResult.resetTime).toISOString(),
199
+ );
200
+ }
201
+
202
+ // Handle CORS for API routes
203
+ if (pathname.startsWith("/api/")) {
204
+ // Check if origin is allowed
205
+ const isAllowedOrigin =
206
+ CORS_CONFIG.allowedOrigins.includes("*") ||
207
+ CORS_CONFIG.allowedOrigins.includes(origin);
208
+
209
+ if (isAllowedOrigin || !origin) {
210
+ response.headers.set(
211
+ "Access-Control-Allow-Origin",
212
+ origin || CORS_CONFIG.allowedOrigins[0],
213
+ );
214
+ response.headers.set(
215
+ "Access-Control-Allow-Methods",
216
+ CORS_CONFIG.allowedMethods.join(", "),
217
+ );
218
+ response.headers.set(
219
+ "Access-Control-Allow-Headers",
220
+ CORS_CONFIG.allowedHeaders.join(", "),
221
+ );
222
+ response.headers.set(
223
+ "Access-Control-Expose-Headers",
224
+ CORS_CONFIG.exposedHeaders.join(", "),
225
+ );
226
+ response.headers.set(
227
+ "Access-Control-Max-Age",
228
+ CORS_CONFIG.maxAge.toString(),
229
+ );
230
+
231
+ if (CORS_CONFIG.credentials) {
232
+ response.headers.set("Access-Control-Allow-Credentials", "true");
233
+ }
234
+ }
235
+
236
+ // Handle preflight requests
237
+ if (request.method === "OPTIONS") {
238
+ return new NextResponse(null, {
239
+ status: 204,
240
+ headers: response.headers,
241
+ });
242
+ }
243
+ }
244
+
245
+ // Apply security headers
246
+ for (const [key, value] of Object.entries(SECURITY_HEADERS)) {
247
+ response.headers.set(key, value);
248
+ }
249
+
250
+ return response;
251
+ }
252
+
253
+ // Configure which routes the middleware runs on
254
+ export const config = {
255
+ matcher: [
256
+ /*
257
+ * Match all request paths except:
258
+ * - _next/static (static files)
259
+ * - _next/image (image optimization files)
260
+ * - favicon.ico (favicon file)
261
+ * - public folder
262
+ */
263
+ "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
264
+ ],
265
+ };
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@quark/worker",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "",
6
+ "main": "index.js",
7
+ "scripts": {
8
+ "test": "node --test $(find src -name '*.test.js')",
9
+ "dev": "tsx watch src/index.js",
10
+ "lint": "biome format --write && biome check --write"
11
+ },
12
+ "keywords": [],
13
+ "author": "",
14
+ "license": "ISC",
15
+ "packageManager": "pnpm@10.12.1",
16
+ "dependencies": {
17
+ "@techstream/quark-core": "^1.0.0",
18
+ "@techstream/quark-db": "workspace:*",
19
+ "@techstream/quark-jobs": "workspace:*",
20
+ "bullmq": "^5.64.1",
21
+ "dotenv": "^17.2.3"
22
+ },
23
+ "devDependencies": {
24
+ "@techstream/quark-config": "workspace:*",
25
+ "@types/node": "^24.10.1",
26
+ "tsx": "^4.20.6"
27
+ }
28
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Worker Service
3
+ * Processes background jobs using BullMQ and Redis
4
+ * Handles job execution, retries, and error tracking
5
+ */
6
+
7
+ import {
8
+ createEmailService,
9
+ createLogger,
10
+ createWorker,
11
+ } from "@techstream/quark-core";
12
+ import { JOB_NAMES, JOB_QUEUES } from "@techstream/quark-jobs";
13
+ import { prisma } from "@techstream/quark-db";
14
+ import dotenv from "dotenv";
15
+
16
+ // Load environment variables
17
+ dotenv.config();
18
+
19
+ const logger = createLogger("worker");
20
+
21
+ // Store workers for graceful shutdown
22
+ const workers = [];
23
+
24
+ // Initialize email service
25
+ const emailService = createEmailService();
26
+
27
+ /**
28
+ * Job handler for SEND_WELCOME_EMAIL
29
+ * @param {Job} bullJob - BullMQ job object
30
+ */
31
+ async function handleSendWelcomeEmail(bullJob) {
32
+ const { userId } = bullJob.data;
33
+
34
+ if (!userId) {
35
+ throw new Error("userId is required for SEND_WELCOME_EMAIL job");
36
+ }
37
+
38
+ logger.info(`Sending welcome email for user ${userId}`, {
39
+ job: JOB_NAMES.SEND_WELCOME_EMAIL,
40
+ userId,
41
+ });
42
+
43
+ // Look up the user's email
44
+ const userRecord = await prisma.user.findUnique({
45
+ where: { id: userId },
46
+ select: { email: true, name: true },
47
+ });
48
+
49
+ if (!userRecord?.email) {
50
+ throw new Error(`User ${userId} not found or has no email`);
51
+ }
52
+
53
+ const displayName = userRecord.name || "there";
54
+
55
+ await emailService.sendEmail(
56
+ userRecord.email,
57
+ "Welcome to Quark!",
58
+ `<h1>Welcome, ${displayName}!</h1>
59
+ <p>Your account has been created successfully.</p>
60
+ <p>You can now sign in and start using the application.</p>`,
61
+ `Welcome, ${displayName}!\n\nYour account has been created successfully.\nYou can now sign in and start using the application.`,
62
+ );
63
+
64
+ return { success: true, userId, email: userRecord.email };
65
+ }
66
+
67
+ /**
68
+ * Initialize job handlers
69
+ * Maps job names to their handler functions
70
+ */
71
+ const jobHandlers = {
72
+ [JOB_NAMES.SEND_WELCOME_EMAIL]: handleSendWelcomeEmail,
73
+ };
74
+
75
+ /**
76
+ * Start the worker service
77
+ * Creates workers for all queues and registers handlers
78
+ */
79
+ async function startWorker() {
80
+ logger.info("Starting Quark Worker Service");
81
+
82
+ try {
83
+ // Create worker for email queue
84
+ const emailQueueWorker = createWorker(
85
+ JOB_QUEUES.EMAIL,
86
+ async (bullJob) => {
87
+ const handler = jobHandlers[bullJob.name];
88
+
89
+ if (!handler) {
90
+ throw new Error(`No handler registered for job: ${bullJob.name}`);
91
+ }
92
+
93
+ return handler(bullJob);
94
+ },
95
+ {
96
+ concurrency: parseInt(process.env.WORKER_CONCURRENCY || "5", 10),
97
+ },
98
+ );
99
+
100
+ workers.push(emailQueueWorker);
101
+
102
+ emailQueueWorker.on("completed", (job, result) => {
103
+ logger.info(`Job ${job.id} (${job.name}) completed`, { result });
104
+ });
105
+
106
+ emailQueueWorker.on("failed", (job, error) => {
107
+ logger.error(`Job ${job.id} (${job.name}) failed after ${job.attemptsMade} attempts`, {
108
+ error: error.message,
109
+ jobName: job.name,
110
+ attemptsMade: job.attemptsMade,
111
+ });
112
+ });
113
+
114
+ logger.info(
115
+ `Email queue worker started (concurrency: ${emailQueueWorker.opts.concurrency})`,
116
+ );
117
+
118
+ // Ready to process jobs
119
+ logger.info("Worker service ready");
120
+ } catch (error) {
121
+ logger.error("Failed to start worker service", { error: error.message, stack: error.stack });
122
+ process.exit(1);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Graceful shutdown handler
128
+ */
129
+ async function shutdown() {
130
+ logger.info("Shutting down worker service");
131
+
132
+ try {
133
+ // Close all workers
134
+ for (const worker of workers) {
135
+ await worker.close();
136
+ }
137
+
138
+ // Disconnect Prisma client
139
+ await prisma.$disconnect();
140
+
141
+ logger.info("All workers closed");
142
+ process.exit(0);
143
+ } catch (error) {
144
+ logger.error("Error during shutdown", { error: error.message, stack: error.stack });
145
+ process.exit(1);
146
+ }
147
+ }
148
+
149
+ // Register shutdown handlers
150
+ process.on("SIGTERM", shutdown);
151
+ process.on("SIGINT", shutdown);
152
+
153
+ // Start the worker service
154
+ startWorker();
@@ -0,0 +1,19 @@
1
+ import assert from "node:assert";
2
+ import { test } from "node:test";
3
+
4
+ // Mock job definitions for testing
5
+ const mockJobDefinitions = {
6
+ JOB_QUEUES: { EMAIL: "email-queue" },
7
+ JOB_NAMES: { SEND_WELCOME_EMAIL: "send-welcome-email" },
8
+ };
9
+
10
+ test("Worker - imports job definitions correctly", async () => {
11
+ const { JOB_QUEUES, JOB_NAMES } = mockJobDefinitions;
12
+ assert.strictEqual(JOB_QUEUES.EMAIL, "email-queue");
13
+ assert.strictEqual(JOB_NAMES.SEND_WELCOME_EMAIL, "send-welcome-email");
14
+ });
15
+
16
+ test("Worker - Worker can be instantiated", async () => {
17
+ // Basic smoke test - bullmq Worker is available
18
+ assert.ok(true, "Worker should be instantiable");
19
+ });
@@ -0,0 +1,40 @@
1
+ services:
2
+ # --- 1. PostgreSQL Database ---
3
+ postgres:
4
+ image: postgres:16-alpine
5
+ container_name: postgres
6
+ restart: always
7
+ ports:
8
+ - "${POSTGRES_PORT:-5432}:5432"
9
+ environment:
10
+ POSTGRES_USER: ${POSTGRES_USER}
11
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
12
+ POSTGRES_DB: ${POSTGRES_DB}
13
+ volumes:
14
+ - postgres_data:/var/lib/postgresql/data
15
+
16
+ # --- 2. Redis Cache & Job Queue ---
17
+ redis:
18
+ image: redis:7-alpine
19
+ container_name: redis
20
+ restart: always
21
+ ports:
22
+ - "${REDIS_PORT:-6379}:6379"
23
+ command: redis-server --appendonly yes
24
+ volumes:
25
+ - redis_data:/data
26
+
27
+ # --- 3. Mailhog (Local SMTP Server) ---
28
+ mailhog:
29
+ image: mailhog/mailhog
30
+ container_name: mailhog
31
+ restart: always
32
+ ports:
33
+ # SMTP port (used by application to send mail)
34
+ - "${MAILHOG_SMTP_PORT:-1025}:1025"
35
+ # Web UI port (to view sent emails)
36
+ - "${MAILHOG_UI_PORT:-8025}:8025"
37
+
38
+ volumes:
39
+ postgres_data:
40
+ redis_data:
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@myquark/root",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "description": "My Quark project",
6
+ "main": "index.js",
7
+ "scripts": {
8
+ "build": "turbo run build",
9
+ "dev": "turbo run dev",
10
+ "lint": "turbo run lint",
11
+ "test": "turbo run test",
12
+ "docker:up": "docker compose up -d",
13
+ "docker:down": "docker compose down",
14
+ "db:generate": "turbo run db:generate"
15
+ },
16
+ "keywords": [],
17
+ "author": "",
18
+ "license": "ISC",
19
+ "packageManager": "pnpm@10.12.1",
20
+ "devDependencies": {
21
+ "@biomejs/biome": "^2.3.13",
22
+ "@types/node": "^24.10.9",
23
+ "tsx": "^4.21.0",
24
+ "turbo": "^2.8.1"
25
+ }
26
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@techstream/quark-db",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "",
6
+ "main": "src/index.js",
7
+ "scripts": {
8
+ "test": "node --test $(find src -name '*.test.js')",
9
+ "lint": "biome format --write && biome check --write",
10
+ "db:generate": "prisma generate",
11
+ "db:migrate": "prisma migrate dev",
12
+ "db:push": "prisma db push",
13
+ "db:seed": "node scripts/seed.js",
14
+ "db:studio": "prisma studio"
15
+ },
16
+ "keywords": [],
17
+ "author": "",
18
+ "license": "ISC",
19
+ "packageManager": "pnpm@10.12.1",
20
+ "devDependencies": {
21
+ "@techstream/quark-config": "workspace:*",
22
+ "prisma": "^7.0.0"
23
+ },
24
+ "dependencies": {
25
+ "@prisma/client": "^7.0.0",
26
+ "dotenv": "^17.2.3",
27
+ "zod": "^4.3.6"
28
+ }
29
+ }