@rapidd/core 2.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 (59) hide show
  1. package/.dockerignore +71 -0
  2. package/.env.example +70 -0
  3. package/.gitignore +11 -0
  4. package/LICENSE +15 -0
  5. package/README.md +231 -0
  6. package/bin/cli.js +145 -0
  7. package/config/app.json +166 -0
  8. package/config/rate-limit.json +12 -0
  9. package/dist/main.js +26 -0
  10. package/dockerfile +57 -0
  11. package/locales/ar_SA.json +179 -0
  12. package/locales/de_DE.json +179 -0
  13. package/locales/en_US.json +180 -0
  14. package/locales/es_ES.json +179 -0
  15. package/locales/fr_FR.json +179 -0
  16. package/locales/it_IT.json +179 -0
  17. package/locales/ja_JP.json +179 -0
  18. package/locales/pt_BR.json +179 -0
  19. package/locales/ru_RU.json +179 -0
  20. package/locales/tr_TR.json +179 -0
  21. package/main.ts +25 -0
  22. package/package.json +126 -0
  23. package/prisma/schema.prisma +9 -0
  24. package/prisma.config.ts +12 -0
  25. package/public/static/favicon.ico +0 -0
  26. package/public/static/image/logo.png +0 -0
  27. package/routes/api/v1/index.ts +113 -0
  28. package/src/app.ts +197 -0
  29. package/src/auth/Auth.ts +446 -0
  30. package/src/auth/stores/ISessionStore.ts +19 -0
  31. package/src/auth/stores/MemoryStore.ts +70 -0
  32. package/src/auth/stores/RedisStore.ts +92 -0
  33. package/src/auth/stores/index.ts +149 -0
  34. package/src/config/acl.ts +9 -0
  35. package/src/config/rls.ts +38 -0
  36. package/src/core/dmmf.ts +226 -0
  37. package/src/core/env.ts +183 -0
  38. package/src/core/errors.ts +87 -0
  39. package/src/core/i18n.ts +144 -0
  40. package/src/core/middleware.ts +123 -0
  41. package/src/core/prisma.ts +236 -0
  42. package/src/index.ts +112 -0
  43. package/src/middleware/model.ts +61 -0
  44. package/src/orm/Model.ts +881 -0
  45. package/src/orm/QueryBuilder.ts +2078 -0
  46. package/src/plugins/auth.ts +162 -0
  47. package/src/plugins/language.ts +79 -0
  48. package/src/plugins/rateLimit.ts +210 -0
  49. package/src/plugins/response.ts +80 -0
  50. package/src/plugins/rls.ts +51 -0
  51. package/src/plugins/security.ts +23 -0
  52. package/src/plugins/upload.ts +299 -0
  53. package/src/types.ts +308 -0
  54. package/src/utils/ApiClient.ts +526 -0
  55. package/src/utils/Mailer.ts +348 -0
  56. package/src/utils/index.ts +25 -0
  57. package/templates/email/example.ejs +17 -0
  58. package/templates/layouts/email.ejs +35 -0
  59. package/tsconfig.json +33 -0
package/.dockerignore ADDED
@@ -0,0 +1,71 @@
1
+ # Dependencies
2
+ node_modules
3
+ npm-debug.log*
4
+ yarn-debug.log*
5
+ yarn-error.log*
6
+
7
+ # Testing and development
8
+ __test__
9
+ *.test.ts
10
+ *.test.js
11
+ *.spec.ts
12
+ *.spec.js
13
+ jest.setup.*
14
+ coverage
15
+
16
+ # Environment files
17
+ .env
18
+ .env.*
19
+ !.env.example
20
+
21
+ # Version control and CI/CD
22
+ .git
23
+ .gitignore
24
+ .github
25
+ .gitlab-ci.yml
26
+
27
+ # Documentation
28
+ *.md
29
+ README*
30
+ CHANGELOG*
31
+ LICENSE*
32
+
33
+ # Docker files
34
+ Dockerfile*
35
+ dockerfile*
36
+ docker-compose*
37
+ compose.yml
38
+ .dockerignore
39
+
40
+ # IDE and editor files
41
+ .vscode
42
+ .idea
43
+ .claude
44
+ *.swp
45
+ *.swo
46
+ *~
47
+
48
+ # OS files
49
+ .DS_Store
50
+ Thumbs.db
51
+
52
+ # Logs and temporary files
53
+ logs
54
+ *.log
55
+ tmp
56
+ temp
57
+ *.tmp
58
+
59
+ # Build artifacts (rebuilt in Docker)
60
+ dist
61
+
62
+ # Prisma client (regenerated in Docker)
63
+ prisma/client
64
+
65
+ # TypeScript config for tests
66
+ tsconfig.test.json
67
+
68
+ # Other
69
+ *.orig
70
+ *.backup
71
+ data/
package/.env.example ADDED
@@ -0,0 +1,70 @@
1
+ NODE_ENV=development
2
+
3
+ # ── Database (REQUIRED) ────────────────────────────────
4
+ DATABASE_URL="postgresql://user:password@localhost:5432/database?schema=public"
5
+ # Admin connection (bypasses RLS); defaults to DATABASE_URL
6
+ DATABASE_URL_ADMIN=
7
+ # Auto-detected from URL; set to "postgresql" or "mysql" to override
8
+ DATABASE_PROVIDER=
9
+
10
+ # ── Server ─────────────────────────────────────────────
11
+ HOST=0.0.0.0
12
+ PORT=3000
13
+ DOMAIN=localhost
14
+ FRONTEND_DOMAIN=frontend.domain
15
+ ALLOWED_ORIGINS=frontend.domain
16
+ COOKIE_SECRET=your-cookie-secret-here
17
+ # true/false; defaults to true in production
18
+ TRUST_PROXY=
19
+
20
+ # ── Authentication (auto-configured) ───────────────────
21
+ # Auth is auto-enabled when a user table is detected in the Prisma schema.
22
+ # Identifier fields and password field are auto-detected from the schema.
23
+ # JWT secrets are auto-generated if not set (won't persist across restarts).
24
+ JWT_SECRET=
25
+ JWT_REFRESH_SECRET=
26
+ # redis or memory
27
+ AUTH_SESSION_STORAGE=redis
28
+ # Session TTL in seconds (default: 24h)
29
+ AUTH_SESSION_TTL=86400
30
+ # bcrypt salt rounds
31
+ AUTH_SALT_ROUNDS=10
32
+ # JWT access token expiry
33
+ AUTH_ACCESS_TOKEN_EXPIRY=1d
34
+ # JWT refresh token expiry
35
+ AUTH_REFRESH_TOKEN_EXPIRY=30d
36
+ # Auto-detected; set to override (e.g., "accounts")
37
+ DB_USER_TABLE=
38
+ # Auto-detected; set to override (e.g., "hash")
39
+ DB_USER_PASSWORD_FIELD=
40
+ # Auto-detected from unique string fields; comma-separated (default: email)
41
+ DB_USER_IDENTIFIER_FIELDS=
42
+ # Comma-separated: bearer, basic, cookie, header (default: bearer)
43
+ AUTH_STRATEGIES=bearer
44
+ # Cookie name for cookie strategy (default: token)
45
+ AUTH_COOKIE_NAME=token
46
+ # Header name for header strategy (default: X-Auth-Token)
47
+ AUTH_CUSTOM_HEADER=X-Auth-Token
48
+
49
+ # ── API Settings ───────────────────────────────────────
50
+ # Max results per query
51
+ API_RESULT_LIMIT=500
52
+ # Rate limit window in ms (default: 15min)
53
+ RATE_LIMIT_WINDOW_MS=900000
54
+ # Max requests per window
55
+ RATE_LIMIT_MAX_REQUESTS=100
56
+
57
+ # ── Redis ──────────────────────────────────────────────
58
+ REDIS_HOST=localhost
59
+ REDIS_PORT=6379
60
+ REDIS_PASSWORD=
61
+ REDIS_DB_RATE_LIMIT=0
62
+ REDIS_DB_AUTH=1
63
+
64
+ # ── Row-Level Security ─────────────────────────────────
65
+ # RLS is auto-enabled for PostgreSQL and disabled for MySQL.
66
+ # true/false; override auto-detection
67
+ RLS_ENABLED=
68
+ # Namespace prefix for SQL session variables (default: app)
69
+ RLS_NAMESPACE=app
70
+ # Configure RLS variables in src/config/rls.ts
package/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ .DS_Store
2
+ ._*
3
+ /node_modules
4
+ .env
5
+ compose.yml
6
+ /config/app.json
7
+ /logs
8
+ /data/api_credentials.json
9
+ /prisma/client
10
+ /dist
11
+ /wiki
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2025 Mert Dalbudak
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,231 @@
1
+ <img width="64" height="64" alt="logo" src="https://github.com/user-attachments/assets/706dd13b-212c-4076-b4d7-94dec4001a06" />
2
+
3
+ # Rapidd
4
+
5
+ Code-first REST API framework for TypeScript. Database in, API out.
6
+
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
8
+ [![Fastify](https://img.shields.io/badge/Fastify-5.x-000000?logo=fastify&logoColor=white)](https://fastify.dev/)
9
+ [![Prisma](https://img.shields.io/badge/Prisma-7.x-2D3748?logo=prisma&logoColor=white)](https://www.prisma.io/)
10
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](LICENSE)
11
+ [![CI](https://github.com/MertDalbudak/rapidd/actions/workflows/ci.yml/badge.svg)](https://github.com/MertDalbudak/rapidd/actions/workflows/ci.yml)
12
+
13
+ ---
14
+
15
+ ## Why Rapidd
16
+
17
+ Rapidd generates a fully-featured REST API from your database schema — then gets out of your way. It's not a scaffolder that dumps code you'll rewrite. It's not a hosted service between you and your data. It's a framework you own, extend, and deploy anywhere.
18
+
19
+ **Unlike Hasura or Supabase**, you get a full TypeScript codebase — no vendor lock-in, no managed service dependency. **Unlike PostgREST**, you get auth, ACL, middleware hooks, and utilities built in. **Unlike Strapi**, it's schema-first via Prisma, not UI-driven.
20
+
21
+ - **Zero to API in 3 commands** — CRUD endpoints with filtering, pagination, relations, and field selection
22
+ - **Convention over configuration** — auto-detects auth tables, password fields, DB provider, and RLS support. Every default overridable
23
+ - **Production-grade from day one** — security headers, JWT with refresh rotation, row-level security, per-model ACL, rate limiting
24
+ - **Batteries included** — HTTP client, SMTP mailer with templates, file uploads, rate limiting, i18n across 10 languages
25
+ - **Fully extensible** — before/after middleware on every CRUD operation, custom routes alongside generated ones
26
+
27
+ ---
28
+
29
+ ## Quick Start
30
+
31
+ ```bash
32
+ npm install @rapidd/build
33
+ ```
34
+
35
+ ```env
36
+ DATABASE_URL="postgresql://user:pass@localhost:5432/mydb"
37
+ ```
38
+
39
+ ```bash
40
+ npx prisma db pull # introspect existing database
41
+ npx rapidd build # generate models, routes & ACL scaffold
42
+ npm run dev # http://localhost:3000
43
+ ```
44
+
45
+ Every table gets full CRUD endpoints. Auth is enabled automatically when a user table is detected. Every auto-detected value — auth fields, password hashing, JWT secrets, session store — is overridable via env vars. See [`.env.example`](.env.example) for the full list.
46
+
47
+ > 📖 **[Getting Started guide →](https://github.com/MertDalbudak/rapidd/wiki/Getting-Started)** — full walkthrough with project structure
48
+
49
+ ---
50
+
51
+ ## Features
52
+
53
+ | Feature | PostgreSQL | MySQL/MariaDB |
54
+ |---------|:----------:|:-------------:|
55
+ | CRUD API generation | ✓ | ✓ |
56
+ | Query filtering (20+ operators) | ✓ | ✓ |
57
+ | Relations & deep includes | ✓ | ✓ |
58
+ | Field selection | ✓ | ✓ |
59
+ | JWT authentication (4 strategies) | ✓ | ✓ |
60
+ | Per-model ACL | ✓ | ✓ |
61
+ | Row-Level Security (database-enforced) | ✓ | — |
62
+ | Rate limiting (Redis + memory fallback) | ✓ | ✓ |
63
+ | File uploads with MIME validation | ✓ | ✓ |
64
+ | SMTP mailer with EJS templates | ✓ | ✓ |
65
+ | Config-driven HTTP client | ✓ | ✓ |
66
+ | i18n (10 languages) | ✓ | ✓ |
67
+ | Security headers (HSTS, CSP, etc.) | ✓ | ✓ |
68
+
69
+ > **MySQL note:** ACL provides application-level access control for all databases. RLS adds database-enforced row filtering as a second layer (PostgreSQL-only). For MySQL, ACL is your primary access control mechanism and covers most use cases. See the **[Access Control wiki](https://github.com/MertDalbudak/rapidd/wiki/Access-Control-(ACL))** for details.
70
+
71
+ ---
72
+
73
+ ## Query API
74
+
75
+ All generated endpoints support filtering, relations, field selection, sorting, and pagination.
76
+
77
+ ```
78
+ GET /api/v1/posts?filter=status=active,title=%typescript%
79
+ GET /api/v1/posts?filter=createdAt=after:2025-01-01,views=gte:100
80
+ GET /api/v1/posts?include=author,comments.user&fields=id,title,author.name
81
+ GET /api/v1/posts?sortBy=createdAt&sortOrder=desc&limit=10&offset=20
82
+ ```
83
+
84
+ 20+ filter operators for strings, numbers, dates, arrays, nulls, and nested relation fields. Responses include pagination metadata with `total`, `count`, `limit`, `offset`, and `hasMore`.
85
+
86
+ > 📖 **[Query API wiki →](https://github.com/MertDalbudak/rapidd/wiki/Query-API)** — all operators, composite PKs, relation filtering
87
+
88
+ ---
89
+
90
+ ## Authentication
91
+
92
+ Auto-enabled when a user table is detected.
93
+
94
+ ```
95
+ POST /auth/login { "user": "john@example.com", "password": "..." }
96
+ POST /auth/logout Authorization: Bearer <token>
97
+ POST /auth/refresh { "refreshToken": "..." }
98
+ GET /auth/me Authorization: Bearer <token>
99
+ ```
100
+
101
+ Four strategies — **bearer** (default), **basic**, **cookie**, and **custom header** — configurable per-route. Multi-identifier login lets users authenticate with any unique field (email, username, phone) in a single endpoint.
102
+
103
+ ⚠️ **Production:** `JWT_SECRET` and `JWT_REFRESH_SECRET` must be set explicitly. The server refuses to start without them to prevent session invalidation on restart.
104
+
105
+ > 📖 **[Authentication wiki →](https://github.com/MertDalbudak/rapidd/wiki/Authentication)** — session stores, route protection, per-endpoint strategy overrides
106
+
107
+ ---
108
+
109
+ ## Access Control
110
+
111
+ Define per-model rules in `src/config/acl.ts`. Enforced on every CRUD operation, relation include, and field selection.
112
+
113
+ ```typescript
114
+ const acl: AclConfig = {
115
+ model: {
116
+ posts: {
117
+ canCreate: (user, data) => user.role !== 'GUEST',
118
+ getAccessFilter: (user) => user.role === 'ADMIN' ? {} : { authorId: user.id },
119
+ getUpdateFilter: (user) => user.role === 'ADMIN' ? {} : { authorId: user.id },
120
+ getDeleteFilter: (user) => user.role === 'ADMIN' ? {} : false,
121
+ getOmitFields: (user) => user.role !== 'ADMIN' ? ['internalNotes'] : [],
122
+ }
123
+ }
124
+ };
125
+ ```
126
+
127
+ Return `{}` for full access, a filter object to scope records, or `false` to deny.
128
+
129
+ > 📖 **[Access Control wiki →](https://github.com/MertDalbudak/rapidd/wiki/Access-Control-(ACL))** — all 5 ACL methods, relation ACL, 404 vs 403 distinction
130
+
131
+ ---
132
+
133
+ ## Model Middleware
134
+
135
+ Hook into any CRUD operation before or after execution.
136
+
137
+ ```typescript
138
+ // Auto-timestamps
139
+ Model.middleware.use('before', 'create', async (ctx) => {
140
+ ctx.data.createdAt = new Date();
141
+ ctx.data.createdBy = ctx.user?.id;
142
+ return ctx;
143
+ });
144
+
145
+ // Soft deletes (scoped to a specific model)
146
+ Model.middleware.use('before', 'delete', async (ctx) => {
147
+ ctx.softDelete = true;
148
+ ctx.data = { deletedAt: new Date(), deletedBy: ctx.user?.id };
149
+ return ctx;
150
+ }, 'posts');
151
+ ```
152
+
153
+ Supports `create`, `update`, `upsert`, `upsertMany`, `delete`, `get`, `getMany`, and `count`. Middleware can abort operations, modify data, and short-circuit with cached results.
154
+
155
+ > 📖 **[Model Middleware wiki →](https://github.com/MertDalbudak/rapidd/wiki/Model-Middleware)** — all hooks, context object, patterns (soft delete, validation, caching)
156
+
157
+ ---
158
+
159
+ ## Row-Level Security
160
+
161
+ Auto-enabled for PostgreSQL. Define which variables to inject in `src/config/rls.ts`:
162
+
163
+ ```typescript
164
+ // src/config/rls.ts
165
+ const rlsContext: RlsContextFn = (request) => ({
166
+ current_user_id: request.user?.id ?? null,
167
+ current_tenant_id: request.headers['x-tenant-id'] ?? null,
168
+ });
169
+ ```
170
+
171
+ ```sql
172
+ CREATE POLICY tenant_isolation ON orders
173
+ USING (tenant_id = current_setting('app.current_tenant_id')::int);
174
+ ```
175
+
176
+ > 📖 **[Row-Level Security wiki →](https://github.com/MertDalbudak/rapidd/wiki/Row%E2%80%90Level-Security-(RLS))** — policy examples, RLS vs ACL comparison
177
+
178
+ ---
179
+
180
+ ## Built-in Utilities
181
+
182
+ | Utility | Description | Docs |
183
+ |---------|-------------|------|
184
+ | **ApiClient** | Config-driven HTTP client with Bearer, Basic, API Key, and OAuth2 auth. Automatic token caching, retries, and fluent builder. | [Wiki →](https://github.com/MertDalbudak/rapidd/wiki/ApiClient) |
185
+ | **Mailer** | SMTP email with EJS template rendering, layout wrappers, i18n support, batch sending, and attachments. | [Wiki →](https://github.com/MertDalbudak/rapidd/wiki/Mailer) |
186
+ | **File Uploads** | Multipart uploads with MIME validation, size limits, and type presets (`images`, `documents`, etc.). | [Wiki →](https://github.com/MertDalbudak/rapidd/wiki/File-Uploads) |
187
+ | **Rate Limiting** | Redis-backed with automatic memory fallback. Per-path configuration via `config/rate-limit.json`. | [Wiki →](https://github.com/MertDalbudak/rapidd/wiki/Rate-Limiting) |
188
+ | **i18n** | 10 languages included. Auto-detected from `Accept-Language` header. Parameter interpolation in error messages. | [Wiki →](https://github.com/MertDalbudak/rapidd/wiki/Internationalization-(i18n)) |
189
+
190
+ ---
191
+
192
+ ## Production & Deployment
193
+
194
+ ```env
195
+ NODE_ENV=production
196
+ JWT_SECRET=your-secret-here # Required — server won't start without it
197
+ JWT_REFRESH_SECRET=your-refresh-secret
198
+ ALLOWED_ORIGINS=yourdomain.com
199
+ TRUST_PROXY=true
200
+ ```
201
+
202
+ ```bash
203
+ npm run build && npm start
204
+
205
+ # or Docker
206
+ docker build -t rapidd . && docker run -p 3000:3000 --env-file .env rapidd
207
+ ```
208
+
209
+ **Security defaults in production:** HSTS, Content-Security-Policy, X-Content-Type-Options, Referrer-Policy, and CORS with explicit origin whitelisting — all enabled automatically.
210
+
211
+ > 📖 **[Deployment wiki →](https://github.com/MertDalbudak/rapidd/wiki/Deployment-&-Production)** — Docker Compose, nginx reverse proxy, production checklist, horizontal scaling
212
+
213
+ ---
214
+
215
+ ## Documentation
216
+
217
+ Full documentation: **[github.com/MertDalbudak/rapidd/wiki](https://github.com/MertDalbudak/rapidd/wiki)**
218
+
219
+ [Getting Started](https://github.com/MertDalbudak/rapidd/wiki/Getting-Started) · [Configuration](https://github.com/MertDalbudak/rapidd/wiki/Configuration) · [Query API](https://github.com/MertDalbudak/rapidd/wiki/Query-API) · [Authentication](https://github.com/MertDalbudak/rapidd/wiki/Authentication) · [Access Control](https://github.com/MertDalbudak/rapidd/wiki/Access-Control-(ACL)) · [Model Middleware](https://github.com/MertDalbudak/rapidd/wiki/Model-Middleware) · [Row-Level Security](https://github.com/MertDalbudak/rapidd/wiki/Row%E2%80%90Level-Security-(RLS)) · [ApiClient](https://github.com/MertDalbudak/rapidd/wiki/ApiClient) · [Mailer](https://github.com/MertDalbudak/rapidd/wiki/Mailer) · [File Uploads](https://github.com/MertDalbudak/rapidd/wiki/File-Uploads) · [Rate Limiting](https://github.com/MertDalbudak/rapidd/wiki/Rate-Limiting) · [i18n](https://github.com/MertDalbudak/rapidd/wiki/Internationalization-(i18n)) · [Deployment](https://github.com/MertDalbudak/rapidd/wiki/Deployment-&-Production)
220
+
221
+ ---
222
+
223
+ ## Contributing
224
+
225
+ Issues and pull requests are welcome. If you find a bug or have a feature request, [open an issue](https://github.com/MertDalbudak/rapidd/issues).
226
+
227
+ ## License
228
+
229
+ [ISC](LICENSE)
230
+
231
+ Built by [Mert Dalbudak](https://github.com/MertDalbudak)
package/bin/cli.js ADDED
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const COMMANDS = { 'create-project': createProject };
7
+
8
+ const args = process.argv.slice(2);
9
+ const command = args[0];
10
+
11
+ if (!command || !COMMANDS[command]) {
12
+ console.log('Usage: npx rapidd <command>\n');
13
+ console.log('Commands:');
14
+ console.log(' create-project Scaffold a new Rapidd project in the current directory');
15
+ process.exit(command ? 1 : 0);
16
+ }
17
+
18
+ COMMANDS[command](args.slice(1));
19
+
20
+ function createProject() {
21
+ const targetDir = process.cwd();
22
+
23
+ // Safety check — don't overwrite an existing project
24
+ if (fs.existsSync(path.join(targetDir, 'src')) || fs.existsSync(path.join(targetDir, 'main.ts'))) {
25
+ console.error('Error: Current directory already contains a project (src/ or main.ts found).');
26
+ process.exit(1);
27
+ }
28
+
29
+ const packageRoot = path.resolve(__dirname, '..');
30
+ const projectName = path.basename(targetDir);
31
+
32
+ const SKIP = new Set([
33
+ 'node_modules', 'dist', '__test__', '.github', '.claude', 'wiki',
34
+ 'bin', '.git', 'package-lock.json', '.env', 'prisma/client',
35
+ ]);
36
+
37
+ const FILES = [
38
+ 'src/',
39
+ 'config/',
40
+ 'locales/',
41
+ 'routes/',
42
+ 'templates/',
43
+ 'public/',
44
+ 'prisma/schema.prisma',
45
+ 'prisma.config.ts',
46
+ 'main.ts',
47
+ 'tsconfig.json',
48
+ '.env.example',
49
+ '.gitignore',
50
+ '.dockerignore',
51
+ 'dockerfile',
52
+ ];
53
+
54
+ console.log(`\nCreating project in ${targetDir}...\n`);
55
+
56
+ for (const entry of FILES) {
57
+ const src = path.join(packageRoot, entry);
58
+ if (!fs.existsSync(src)) continue;
59
+
60
+ const dest = path.join(targetDir, entry);
61
+
62
+ if (entry.endsWith('/')) {
63
+ copyDir(src, dest, SKIP);
64
+ } else {
65
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
66
+ fs.copyFileSync(src, dest);
67
+ }
68
+ }
69
+
70
+ // Generate a fresh package.json for the new project
71
+ const pkg = {
72
+ name: projectName,
73
+ version: '1.0.0',
74
+ private: true,
75
+ scripts: {
76
+ start: 'node dist/main.js',
77
+ dev: 'tsx watch main.ts',
78
+ build: 'tsc',
79
+ },
80
+ engines: { node: '>=24.0.0' },
81
+ dependencies: {
82
+ '@fastify/cookie': '^11.0.2',
83
+ '@fastify/cors': '^11.0.0',
84
+ '@fastify/formbody': '^8.0.2',
85
+ '@fastify/multipart': '^9.4.0',
86
+ '@fastify/static': '^9.0.0',
87
+ '@prisma/adapter-mariadb': '^7.0.1',
88
+ '@prisma/adapter-pg': '^7.0.1',
89
+ '@prisma/client': '^7.0.1',
90
+ '@prisma/internals': '^7.0.1',
91
+ 'bcrypt': '^6.0.0',
92
+ 'dotenv': '^17.3.1',
93
+ 'ejs': '^4.0.1',
94
+ 'fastify': '^5.2.1',
95
+ 'fastify-plugin': '^5.0.1',
96
+ 'ioredis': '^5.6.1',
97
+ 'jsonwebtoken': '^9.0.2',
98
+ 'luxon': '^3.7.2',
99
+ 'nodemailer': '^8.0.1',
100
+ 'pg': '^8.16.3',
101
+ },
102
+ devDependencies: {
103
+ '@rapidd/build': '^2.1.3',
104
+ '@types/bcrypt': '^6.0.0',
105
+ '@types/ejs': '^3.1.5',
106
+ '@types/jsonwebtoken': '^9.0.8',
107
+ '@types/luxon': '^3.7.1',
108
+ '@types/node': '^22.12.0',
109
+ '@types/nodemailer': '^7.0.9',
110
+ '@types/pg': '^8.11.11',
111
+ 'prisma': '^7.0.2',
112
+ 'tsx': '^4.19.2',
113
+ 'typescript': '^5.7.3',
114
+ },
115
+ };
116
+
117
+ fs.writeFileSync(
118
+ path.join(targetDir, 'package.json'),
119
+ JSON.stringify(pkg, null, 2) + '\n'
120
+ );
121
+
122
+ console.log('Project created. Next steps:\n');
123
+ console.log(' npm install');
124
+ console.log(' # Set DATABASE_URL in .env');
125
+ console.log(' npx prisma db pull');
126
+ console.log(' npx rapidd build');
127
+ console.log(' npm run dev\n');
128
+ }
129
+
130
+ function copyDir(src, dest, skip) {
131
+ fs.mkdirSync(dest, { recursive: true });
132
+
133
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
134
+ if (skip.has(entry.name) || entry.name === '.DS_Store') continue;
135
+
136
+ const srcPath = path.join(src, entry.name);
137
+ const destPath = path.join(dest, entry.name);
138
+
139
+ if (entry.isDirectory()) {
140
+ copyDir(srcPath, destPath, skip);
141
+ } else {
142
+ fs.copyFileSync(srcPath, destPath);
143
+ }
144
+ }
145
+ }