@rapidd/core 2.1.2 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example CHANGED
@@ -40,10 +40,10 @@ DB_USER_PASSWORD_FIELD=
40
40
  # Auto-detected from unique string fields; comma-separated (default: email)
41
41
  DB_USER_IDENTIFIER_FIELDS=
42
42
  # Comma-separated: bearer, basic, cookie, header (default: bearer)
43
- AUTH_METHODS=bearer
44
- # Cookie name for cookie auth method (default: token)
43
+ AUTH_STRATEGIES=bearer
44
+ # Cookie name for cookie auth strategy (default: token)
45
45
  AUTH_COOKIE_NAME=token
46
- # Header name for header auth method (default: X-Auth-Token)
46
+ # Header name for header auth strategy (default: X-Auth-Token)
47
47
  AUTH_CUSTOM_HEADER=X-Auth-Token
48
48
 
49
49
  # ── API Settings ───────────────────────────────────────
@@ -68,3 +68,9 @@ RLS_ENABLED=
68
68
  # Namespace prefix for SQL session variables (default: app)
69
69
  RLS_NAMESPACE=app
70
70
  # Configure RLS variables in src/config/rls.ts
71
+
72
+ # ── Logging ───────────────────────────────────────────
73
+ # essential (default), fine, or finest
74
+ LOG_LEVEL=essential
75
+ # Directory for log files (default: logs/). Empty string disables file logging.
76
+ LOG_DIR=logs
package/README.md CHANGED
@@ -31,18 +31,14 @@ Rapidd generates a fully-featured REST API from your database schema — then ge
31
31
 
32
32
  ```bash
33
33
  mkdir my-api && cd my-api
34
- npx rapidd create-project # scaffold project files
35
- npm install
34
+ npx @rapidd/core create-project && npm install
36
35
  ```
37
36
 
38
- ```env
39
- DATABASE_URL="postgresql://user:pass@localhost:5432/mydb"
40
- ```
37
+ Set `DATABASE_URL` in `.env`, then add your schema in `prisma/schema.prisma`(or create from DB via `npx prisma db pull`):
41
38
 
42
39
  ```bash
43
- npx prisma db pull # introspect existing database
44
- npx rapidd build # generate models, routes & ACL scaffold
45
- npm run dev # http://localhost:3000
40
+ npx rapidd build # generate models, routes & ACL scaffold
41
+ npm run dev # http://localhost:3000
46
42
  ```
47
43
 
48
44
  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.
@@ -51,25 +47,22 @@ Every table gets full CRUD endpoints. Auth is enabled automatically when a user
51
47
 
52
48
  ---
53
49
 
54
- ## Features
55
-
56
- | Feature | PostgreSQL | MySQL/MariaDB |
57
- |---------|:----------:|:-------------:|
58
- | CRUD API generation | ✓ | ✓ |
59
- | Query filtering (20+ operators) | ✓ | ✓ |
60
- | Relations & deep includes | ✓ | ✓ |
61
- | Field selection | ✓ | ✓ |
62
- | JWT authentication (4 methods) | ✓ | ✓ |
63
- | Per-model ACL | ✓ | ✓ |
64
- | Row-Level Security (database-enforced) | ✓ | — |
65
- | Rate limiting (Redis + memory fallback) | | ✓ |
66
- | File uploads with MIME validation | ✓ | ✓ |
67
- | SMTP mailer with EJS templates | ✓ | ✓ |
68
- | Config-driven HTTP client | ✓ | ✓ |
69
- | i18n (10 languages) | | |
70
- | Security headers (HSTS, CSP, etc.) | ✓ | ✓ |
71
-
72
- > **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.
50
+ ## How It Compares
51
+
52
+ | | Rapidd | Hasura | PostgREST | Supabase | Strapi |
53
+ |---|:---:|:---:|:---:|:---:|:---:|
54
+ | Full source code ownership | ✓ | — | — | — | ✓ |
55
+ | Schema-first (no UI) | ✓ | ✓ | ✓ | ✓ | — |
56
+ | REST API | | partial | ✓ | ✓ | ✓ |
57
+ | Multi-database | | ✓ | — | — | ✓ |
58
+ | Built-in auth | | — | — | ✓ | ✓ |
59
+ | Per-model ACL | ✓ | ✓ | — | — | ✓ |
60
+ | Row-level security | ✓* | ✓ | ✓ | ✓ | — |
61
+ | Before/after middleware | | | | — | ✓ |
62
+ | Custom routes alongside generated | | — | — | ✓ | ✓ |
63
+ | No vendor dependency | | ✓ | ✓ | — | ✓ |
64
+
65
+ <sub>* PostgreSQL only. All other features support PostgreSQL and MySQL/MariaDB.</sub>
73
66
 
74
67
  ---
75
68
 
@@ -101,24 +94,24 @@ POST /auth/refresh { "refreshToken": "..." }
101
94
  GET /auth/me Authorization: Bearer <token>
102
95
  ```
103
96
 
104
- Four methods — **bearer** (default), **basic**, **cookie**, and **custom header** — configurable globally via `AUTH_METHODS` env var or per endpoint prefix in `config/app.json`:
97
+ Four strategies — **bearer** (default), **basic**, **cookie**, and **custom header** — configurable globally via `AUTH_STRATEGIES` env var or per endpoint prefix in `config/app.json`:
105
98
 
106
99
  ```json
107
100
  {
108
- "endpointAuthMethod": {
101
+ "endpointAuthStrategy": {
109
102
  "/api/v1": ["basic", "bearer"],
110
103
  "/api/v2": "bearer"
111
104
  }
112
105
  }
113
106
  ```
114
107
 
115
- Set `null` for the global default, a string for a single method, or an array for multiple. Route-level config takes highest priority, then prefix match, then global default.
108
+ Set `null` for the global default, a string for a single strategy, or an array for multiple. Route-level config takes highest priority, then prefix match, then global default.
116
109
 
117
110
  Multi-identifier login lets users authenticate with any unique field (email, username, phone) in a single endpoint.
118
111
 
119
112
  **Production:** `JWT_SECRET` and `JWT_REFRESH_SECRET` must be set explicitly. The server refuses to start without them to prevent session invalidation on restart.
120
113
 
121
- > **[Authentication wiki](https://github.com/MertDalbudak/rapidd/wiki/Authentication)** — session stores, route protection, per-endpoint method overrides
114
+ > **[Authentication wiki](https://github.com/MertDalbudak/rapidd/wiki/Authentication)** — session stores, route protection, per-endpoint strategy overrides
122
115
 
123
116
  ---
124
117
 
@@ -235,11 +228,9 @@ docker build -t my-api . && docker run -p 3000:3000 --env-file .env my-api
235
228
  | [`@rapidd/core`](https://www.npmjs.com/package/@rapidd/core) | Framework runtime, project scaffolding, and unified `npx rapidd` CLI |
236
229
  | [`@rapidd/build`](https://www.npmjs.com/package/@rapidd/build) | Code generation — models, routes, and ACL from your Prisma schema |
237
230
 
238
- All commands go through `npx rapidd`:
239
-
240
231
  ```bash
241
- npx rapidd create-project # scaffold a new project (@rapidd/core)
242
- npx rapidd build # generate from schema (@rapidd/build)
232
+ npx @rapidd/core create-project # scaffold a new project
233
+ npx rapidd build # generate from schema (after npm install)
243
234
  ```
244
235
 
245
236
  ---
package/bin/cli.js CHANGED
@@ -87,6 +87,19 @@ function createProject() {
87
87
  }
88
88
  }
89
89
 
90
+ // Read versions from this package's own package.json so they never drift
91
+ const corePkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf-8'));
92
+ const coreDeps = corePkg.dependencies || {};
93
+ const coreDevDeps = corePkg.devDependencies || {};
94
+
95
+ function pick(source, keys) {
96
+ const result = {};
97
+ for (const key of keys) {
98
+ if (source[key]) result[key] = source[key];
99
+ }
100
+ return result;
101
+ }
102
+
90
103
  // Generate a fresh package.json for the new project
91
104
  const pkg = {
92
105
  name: projectName,
@@ -97,41 +110,19 @@ function createProject() {
97
110
  dev: 'tsx watch main.ts',
98
111
  build: 'tsc',
99
112
  },
100
- engines: { node: '>=24.0.0' },
101
- dependencies: {
102
- '@fastify/cookie': '^11.0.2',
103
- '@fastify/cors': '^11.0.0',
104
- '@fastify/formbody': '^8.0.2',
105
- '@fastify/multipart': '^9.4.0',
106
- '@fastify/static': '^9.0.0',
107
- '@prisma/adapter-mariadb': '^7.0.1',
108
- '@prisma/adapter-pg': '^7.0.1',
109
- '@prisma/client': '^7.0.1',
110
- '@prisma/internals': '^7.0.1',
111
- 'bcrypt': '^6.0.0',
112
- 'dotenv': '^17.3.1',
113
- 'ejs': '^4.0.1',
114
- 'fastify': '^5.2.1',
115
- 'fastify-plugin': '^5.0.1',
116
- 'ioredis': '^5.6.1',
117
- 'jsonwebtoken': '^9.0.2',
118
- 'luxon': '^3.7.2',
119
- 'nodemailer': '^8.0.1',
120
- 'pg': '^8.16.3',
121
- },
122
- devDependencies: {
123
- '@rapidd/build': '^2.1.3',
124
- '@types/bcrypt': '^6.0.0',
125
- '@types/ejs': '^3.1.5',
126
- '@types/jsonwebtoken': '^9.0.8',
127
- '@types/luxon': '^3.7.1',
128
- '@types/node': '^22.12.0',
129
- '@types/nodemailer': '^7.0.9',
130
- '@types/pg': '^8.11.11',
131
- 'prisma': '^7.0.2',
132
- 'tsx': '^4.19.2',
133
- 'typescript': '^5.7.3',
134
- },
113
+ engines: corePkg.engines || { node: '>=24.0.0' },
114
+ dependencies: pick(coreDeps, [
115
+ '@fastify/cookie', '@fastify/cors', '@fastify/formbody', '@fastify/multipart', '@fastify/static',
116
+ '@prisma/adapter-mariadb', '@prisma/adapter-pg', '@prisma/client', '@prisma/internals',
117
+ 'bcrypt', 'dotenv', 'ejs', 'fastify', 'fastify-plugin',
118
+ 'ioredis', 'jsonwebtoken', 'luxon', 'nodemailer', 'pg',
119
+ ]),
120
+ devDependencies: pick(coreDevDeps, [
121
+ '@rapidd/build',
122
+ '@types/bcrypt', '@types/ejs', '@types/jsonwebtoken', '@types/luxon',
123
+ '@types/node', '@types/nodemailer', '@types/pg',
124
+ 'prisma', 'tsx', 'typescript',
125
+ ]),
135
126
  };
136
127
 
137
128
  fs.writeFileSync(
package/config/app.json CHANGED
@@ -151,7 +151,7 @@
151
151
  "name": "Support Team"
152
152
  }
153
153
  },
154
- "endpointAuthMethod": {
154
+ "endpointAuthStrategy": {
155
155
  "/api/v1": null
156
156
  },
157
157
  "languages": [
package/dockerfile CHANGED
@@ -25,7 +25,7 @@ COPY prisma ./prisma
25
25
  RUN npx prisma generate --generator client
26
26
 
27
27
  # Stage 3: Runtime
28
- FROM node:24-alpine
28
+ FROM node:current-alpine
29
29
 
30
30
  WORKDIR /app
31
31
 
@@ -50,8 +50,16 @@ COPY --chown=rapidd:nodejs public ./public
50
50
 
51
51
  RUN apk update && apk upgrade --no-cache && rm -rf /var/cache/apk/*
52
52
 
53
+ RUN mkdir -p /app/uploads /app/temp/uploads /app/logs && \
54
+ chown -R rapidd:nodejs /app/uploads /app/temp /app/logs
55
+
56
+ VOLUME ["/app/uploads", "/app/logs"]
57
+
53
58
  USER rapidd
54
59
 
55
60
  EXPOSE 3000
56
61
 
62
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
63
+ CMD node -e "fetch('http://localhost:3000/').then(r => process.exit(r.status === 404 ? 0 : 1)).catch(() => process.exit(1))"
64
+
57
65
  ENTRYPOINT ["node", "dist/main.js"]
@@ -175,5 +175,23 @@
175
175
  "token_missing_email": "الرمز لا يحتوي على معلومات البريد الإلكتروني",
176
176
  "oauth_config_missing": "يجب تكوين معرف التطبيق والسر الخاص بـ {provider} في متغيرات البيئة",
177
177
  "token_app_mismatch": "الرمز لا ينتمي إلى هذا التطبيق",
178
- "oauth_email_permission_missing": "مستخدم {provider} لم يمنح إذن البريد الإلكتروني"
178
+ "oauth_email_permission_missing": "مستخدم {provider} لم يمنح إذن البريد الإلكتروني",
179
+ "internal_server_error": "حدث خطأ ما",
180
+ "duplicate_entry": "إدخال مكرر لـ {model}. السجل ذو {field}: '{value}' موجود بالفعل",
181
+ "authentication_not_available": "المصادقة غير متاحة",
182
+ "authentication_required": "المصادقة مطلوبة",
183
+ "user_and_password_required": "المستخدم وكلمة المرور مطلوبان",
184
+ "auth_not_configured": "المصادقة غير مهيأة",
185
+ "email_and_password_required": "البريد الإلكتروني وكلمة المرور مطلوبان",
186
+ "email_already_exists": "البريد الإلكتروني موجود بالفعل",
187
+ "user_registered": "تم تسجيل المستخدم بنجاح",
188
+ "invalid_composite_key": "مفتاح مركب غير صالح: متوقع {expected} حقول، تم استلام {received}",
189
+ "invalid_composite_key_format": "تنسيق مفتاح مركب غير صالح. متوقع كائن أو سلسلة نصية مفصولة بعلامة التلدة",
190
+ "no_permission_to_create": "لا توجد صلاحية لإنشاء {model}",
191
+ "no_permission_to_update": "لا توجد صلاحية لتحديث السجل",
192
+ "no_permission_to_delete": "لا توجد صلاحية لحذف السجل",
193
+ "field_not_nullable": "الحقل '{field}' لا يمكن أن يكون فارغاً",
194
+ "relation_not_included": "العلاقة '{relation}' مُشار إليها في الحقول لكنها غير مُضمّنة. {hint}",
195
+ "max_nesting_depth_exceeded": "تم تجاوز أقصى عمق تداخل وهو {depth}",
196
+ "no_file_uploaded": "لم يتم رفع أي ملف"
179
197
  }
@@ -175,5 +175,23 @@
175
175
  "token_missing_email": "Token enthält keine E-Mail-Informationen",
176
176
  "oauth_config_missing": "{provider} App-ID und Secret müssen in Umgebungsvariablen konfiguriert werden",
177
177
  "token_app_mismatch": "Token gehört nicht zu dieser Anwendung",
178
- "oauth_email_permission_missing": "{provider}-Benutzer hat keine E-Mail-Berechtigung erteilt"
178
+ "oauth_email_permission_missing": "{provider}-Benutzer hat keine E-Mail-Berechtigung erteilt",
179
+ "internal_server_error": "Etwas ist schiefgelaufen",
180
+ "duplicate_entry": "Doppelter Eintrag für {model}. Datensatz mit {field}: '{value}' existiert bereits",
181
+ "authentication_not_available": "Authentifizierung ist nicht verfügbar",
182
+ "authentication_required": "Authentifizierung erforderlich",
183
+ "user_and_password_required": "Benutzer und Passwort sind erforderlich",
184
+ "auth_not_configured": "Authentifizierung ist nicht konfiguriert",
185
+ "email_and_password_required": "E-Mail und Passwort sind erforderlich",
186
+ "email_already_exists": "E-Mail existiert bereits",
187
+ "user_registered": "Benutzer erfolgreich registriert",
188
+ "invalid_composite_key": "Ungültiger zusammengesetzter Schlüssel: {expected} Felder erwartet, {received} erhalten",
189
+ "invalid_composite_key_format": "Ungültiges Format für zusammengesetzten Schlüssel. Erwartet wird ein Objekt oder ein durch Tilde getrennter String",
190
+ "no_permission_to_create": "Keine Berechtigung zum Erstellen von {model}",
191
+ "no_permission_to_update": "Keine Berechtigung zum Aktualisieren des Datensatzes",
192
+ "no_permission_to_delete": "Keine Berechtigung zum Löschen des Datensatzes",
193
+ "field_not_nullable": "Feld '{field}' darf nicht null sein",
194
+ "relation_not_included": "Relation '{relation}' wird in Feldern referenziert, aber nicht eingeschlossen. {hint}",
195
+ "max_nesting_depth_exceeded": "Maximale Verschachtelungstiefe von {depth} überschritten",
196
+ "no_file_uploaded": "Keine Datei hochgeladen"
179
197
  }
@@ -176,5 +176,23 @@
176
176
  "token_missing_email": "Token does not contain email information",
177
177
  "oauth_config_missing": "{provider} App ID and Secret must be configured in environment variables",
178
178
  "token_app_mismatch": "Token does not belong to this application",
179
- "oauth_email_permission_missing": "{provider} user does not have email permission granted"
179
+ "oauth_email_permission_missing": "{provider} user does not have email permission granted",
180
+ "internal_server_error": "Something went wrong",
181
+ "duplicate_entry": "Duplicate entry for {model}. Record with {field}: '{value}' already exists",
182
+ "authentication_not_available": "Authentication is not available",
183
+ "authentication_required": "Authentication required",
184
+ "user_and_password_required": "User and password are required",
185
+ "auth_not_configured": "Authentication is not configured",
186
+ "email_and_password_required": "Email and password are required",
187
+ "email_already_exists": "Email already exists",
188
+ "user_registered": "User registered successfully",
189
+ "invalid_composite_key": "Invalid composite key: expected {expected} fields, received {received}",
190
+ "invalid_composite_key_format": "Invalid composite key format. Expected an object or tilde-separated string",
191
+ "no_permission_to_create": "No permission to create {model}",
192
+ "no_permission_to_update": "No permission to update record",
193
+ "no_permission_to_delete": "No permission to delete record",
194
+ "field_not_nullable": "Field '{field}' cannot be null",
195
+ "relation_not_included": "Relation '{relation}' is referenced in fields but not included. {hint}",
196
+ "max_nesting_depth_exceeded": "Maximum nesting depth of {depth} exceeded",
197
+ "no_file_uploaded": "No file uploaded"
180
198
  }
@@ -175,5 +175,23 @@
175
175
  "token_missing_email": "El token no contiene información de correo electrónico",
176
176
  "oauth_config_missing": "El ID de aplicación y el secreto de {provider} deben configurarse en las variables de entorno",
177
177
  "token_app_mismatch": "El token no pertenece a esta aplicación",
178
- "oauth_email_permission_missing": "El usuario de {provider} no ha otorgado permiso de correo electrónico"
178
+ "oauth_email_permission_missing": "El usuario de {provider} no ha otorgado permiso de correo electrónico",
179
+ "internal_server_error": "Algo salió mal",
180
+ "duplicate_entry": "Entrada duplicada para {model}. El registro con {field}: '{value}' ya existe",
181
+ "authentication_not_available": "La autenticación no está disponible",
182
+ "authentication_required": "Autenticación requerida",
183
+ "user_and_password_required": "Usuario y contraseña son requeridos",
184
+ "auth_not_configured": "La autenticación no está configurada",
185
+ "email_and_password_required": "Correo electrónico y contraseña son requeridos",
186
+ "email_already_exists": "El correo electrónico ya existe",
187
+ "user_registered": "Usuario registrado exitosamente",
188
+ "invalid_composite_key": "Clave compuesta inválida: se esperaban {expected} campos, se recibieron {received}",
189
+ "invalid_composite_key_format": "Formato de clave compuesta inválido. Se espera un objeto o una cadena separada por tildes",
190
+ "no_permission_to_create": "Sin permiso para crear {model}",
191
+ "no_permission_to_update": "Sin permiso para actualizar el registro",
192
+ "no_permission_to_delete": "Sin permiso para eliminar el registro",
193
+ "field_not_nullable": "El campo '{field}' no puede ser nulo",
194
+ "relation_not_included": "La relación '{relation}' está referenciada en los campos pero no incluida. {hint}",
195
+ "max_nesting_depth_exceeded": "Se excedió la profundidad máxima de anidación de {depth}",
196
+ "no_file_uploaded": "No se cargó ningún archivo"
179
197
  }
@@ -175,5 +175,23 @@
175
175
  "token_missing_email": "Le jeton ne contient pas d'informations d'e-mail",
176
176
  "oauth_config_missing": "L'ID d'application et le secret de {provider} doivent être configurés dans les variables d'environnement",
177
177
  "token_app_mismatch": "Le jeton n'appartient pas à cette application",
178
- "oauth_email_permission_missing": "L'utilisateur {provider} n'a pas accordé la permission d'accès à l'e-mail"
178
+ "oauth_email_permission_missing": "L'utilisateur {provider} n'a pas accordé la permission d'accès à l'e-mail",
179
+ "internal_server_error": "Une erreur est survenue",
180
+ "duplicate_entry": "Entrée en double pour {model}. L'enregistrement avec {field} : '{value}' existe déjà",
181
+ "authentication_not_available": "L'authentification n'est pas disponible",
182
+ "authentication_required": "Authentification requise",
183
+ "user_and_password_required": "Utilisateur et mot de passe sont requis",
184
+ "auth_not_configured": "L'authentification n'est pas configurée",
185
+ "email_and_password_required": "E-mail et mot de passe sont requis",
186
+ "email_already_exists": "L'e-mail existe déjà",
187
+ "user_registered": "Utilisateur enregistré avec succès",
188
+ "invalid_composite_key": "Clé composite invalide : {expected} champs attendus, {received} reçus",
189
+ "invalid_composite_key_format": "Format de clé composite invalide. Un objet ou une chaîne séparée par des tildes est attendu",
190
+ "no_permission_to_create": "Pas de permission pour créer {model}",
191
+ "no_permission_to_update": "Pas de permission pour mettre à jour l'enregistrement",
192
+ "no_permission_to_delete": "Pas de permission pour supprimer l'enregistrement",
193
+ "field_not_nullable": "Le champ '{field}' ne peut pas être nul",
194
+ "relation_not_included": "La relation '{relation}' est référencée dans les champs mais non incluse. {hint}",
195
+ "max_nesting_depth_exceeded": "Profondeur d'imbrication maximale de {depth} dépassée",
196
+ "no_file_uploaded": "Aucun fichier téléchargé"
179
197
  }
@@ -175,5 +175,23 @@
175
175
  "token_missing_email": "Il token non contiene informazioni e-mail",
176
176
  "oauth_config_missing": "L'ID applicazione e il segreto di {provider} devono essere configurati nelle variabili d'ambiente",
177
177
  "token_app_mismatch": "Il token non appartiene a questa applicazione",
178
- "oauth_email_permission_missing": "L'utente {provider} non ha concesso il permesso e-mail"
178
+ "oauth_email_permission_missing": "L'utente {provider} non ha concesso il permesso e-mail",
179
+ "internal_server_error": "Qualcosa è andato storto",
180
+ "duplicate_entry": "Voce duplicata per {model}. Il record con {field}: '{value}' esiste già",
181
+ "authentication_not_available": "L'autenticazione non è disponibile",
182
+ "authentication_required": "Autenticazione richiesta",
183
+ "user_and_password_required": "Utente e password sono obbligatori",
184
+ "auth_not_configured": "L'autenticazione non è configurata",
185
+ "email_and_password_required": "E-mail e password sono obbligatori",
186
+ "email_already_exists": "L'e-mail esiste già",
187
+ "user_registered": "Utente registrato con successo",
188
+ "invalid_composite_key": "Chiave composita non valida: previsti {expected} campi, ricevuti {received}",
189
+ "invalid_composite_key_format": "Formato chiave composita non valido. Previsto un oggetto o una stringa separata da tilde",
190
+ "no_permission_to_create": "Nessun permesso per creare {model}",
191
+ "no_permission_to_update": "Nessun permesso per aggiornare il record",
192
+ "no_permission_to_delete": "Nessun permesso per eliminare il record",
193
+ "field_not_nullable": "Il campo '{field}' non può essere nullo",
194
+ "relation_not_included": "La relazione '{relation}' è referenziata nei campi ma non inclusa. {hint}",
195
+ "max_nesting_depth_exceeded": "Profondità massima di annidamento di {depth} superata",
196
+ "no_file_uploaded": "Nessun file caricato"
179
197
  }
@@ -175,5 +175,23 @@
175
175
  "token_missing_email": "トークンにメール情報が含まれていません",
176
176
  "oauth_config_missing": "{provider}のアプリIDとシークレットを環境変数で設定する必要があります",
177
177
  "token_app_mismatch": "トークンはこのアプリケーションに属していません",
178
- "oauth_email_permission_missing": "{provider}ユーザーがメール許可を付与していません"
178
+ "oauth_email_permission_missing": "{provider}ユーザーがメール許可を付与していません",
179
+ "internal_server_error": "問題が発生しました",
180
+ "duplicate_entry": "{model}の重複エントリ。{field}: '{value}'のレコードは既に存在します",
181
+ "authentication_not_available": "認証は利用できません",
182
+ "authentication_required": "認証が必要です",
183
+ "user_and_password_required": "ユーザーとパスワードが必要です",
184
+ "auth_not_configured": "認証が設定されていません",
185
+ "email_and_password_required": "メールアドレスとパスワードが必要です",
186
+ "email_already_exists": "メールアドレスは既に存在します",
187
+ "user_registered": "ユーザーが正常に登録されました",
188
+ "invalid_composite_key": "無効な複合キー: {expected}フィールドが必要ですが、{received}を受信しました",
189
+ "invalid_composite_key_format": "無効な複合キー形式。オブジェクトまたはチルダ区切りの文字列が必要です",
190
+ "no_permission_to_create": "{model}を作成する権限がありません",
191
+ "no_permission_to_update": "レコードを更新する権限がありません",
192
+ "no_permission_to_delete": "レコードを削除する権限がありません",
193
+ "field_not_nullable": "フィールド'{field}'はnullにできません",
194
+ "relation_not_included": "リレーション'{relation}'はフィールドで参照されていますが、含まれていません。{hint}",
195
+ "max_nesting_depth_exceeded": "最大ネスト深度{depth}を超えました",
196
+ "no_file_uploaded": "ファイルがアップロードされていません"
179
197
  }
@@ -175,5 +175,23 @@
175
175
  "token_missing_email": "O token não contém informações de e-mail",
176
176
  "oauth_config_missing": "O ID do aplicativo e o segredo do {provider} devem ser configurados nas variáveis de ambiente",
177
177
  "token_app_mismatch": "O token não pertence a esta aplicação",
178
- "oauth_email_permission_missing": "O usuário do {provider} não concedeu permissão de e-mail"
178
+ "oauth_email_permission_missing": "O usuário do {provider} não concedeu permissão de e-mail",
179
+ "internal_server_error": "Algo deu errado",
180
+ "duplicate_entry": "Entrada duplicada para {model}. Registro com {field}: '{value}' já existe",
181
+ "authentication_not_available": "A autenticação não está disponível",
182
+ "authentication_required": "Autenticação necessária",
183
+ "user_and_password_required": "Usuário e senha são obrigatórios",
184
+ "auth_not_configured": "A autenticação não está configurada",
185
+ "email_and_password_required": "E-mail e senha são obrigatórios",
186
+ "email_already_exists": "E-mail já existe",
187
+ "user_registered": "Usuário registrado com sucesso",
188
+ "invalid_composite_key": "Chave composta inválida: esperados {expected} campos, recebidos {received}",
189
+ "invalid_composite_key_format": "Formato de chave composta inválido. Esperado um objeto ou string separada por til",
190
+ "no_permission_to_create": "Sem permissão para criar {model}",
191
+ "no_permission_to_update": "Sem permissão para atualizar o registro",
192
+ "no_permission_to_delete": "Sem permissão para excluir o registro",
193
+ "field_not_nullable": "O campo '{field}' não pode ser nulo",
194
+ "relation_not_included": "A relação '{relation}' é referenciada nos campos mas não está incluída. {hint}",
195
+ "max_nesting_depth_exceeded": "Profundidade máxima de aninhamento de {depth} excedida",
196
+ "no_file_uploaded": "Nenhum arquivo enviado"
179
197
  }
@@ -175,5 +175,23 @@
175
175
  "token_missing_email": "Токен не содержит информацию об электронной почте",
176
176
  "oauth_config_missing": "ID приложения и секрет {provider} должны быть настроены в переменных окружения",
177
177
  "token_app_mismatch": "Токен не принадлежит этому приложению",
178
- "oauth_email_permission_missing": "Пользователь {provider} не предоставил разрешение на доступ к электронной почте"
178
+ "oauth_email_permission_missing": "Пользователь {provider} не предоставил разрешение на доступ к электронной почте",
179
+ "internal_server_error": "Что-то пошло не так",
180
+ "duplicate_entry": "Дублирующая запись для {model}. Запись с {field}: '{value}' уже существует",
181
+ "authentication_not_available": "Аутентификация недоступна",
182
+ "authentication_required": "Требуется аутентификация",
183
+ "user_and_password_required": "Пользователь и пароль обязательны",
184
+ "auth_not_configured": "Аутентификация не настроена",
185
+ "email_and_password_required": "Электронная почта и пароль обязательны",
186
+ "email_already_exists": "Электронная почта уже существует",
187
+ "user_registered": "Пользователь успешно зарегистрирован",
188
+ "invalid_composite_key": "Недопустимый составной ключ: ожидалось {expected} полей, получено {received}",
189
+ "invalid_composite_key_format": "Недопустимый формат составного ключа. Ожидается объект или строка, разделенная тильдой",
190
+ "no_permission_to_create": "Нет разрешения на создание {model}",
191
+ "no_permission_to_update": "Нет разрешения на обновление записи",
192
+ "no_permission_to_delete": "Нет разрешения на удаление записи",
193
+ "field_not_nullable": "Поле '{field}' не может быть пустым",
194
+ "relation_not_included": "Связь '{relation}' указана в полях, но не включена. {hint}",
195
+ "max_nesting_depth_exceeded": "Превышена максимальная глубина вложенности {depth}",
196
+ "no_file_uploaded": "Файл не загружен"
179
197
  }
@@ -175,5 +175,23 @@
175
175
  "token_missing_email": "Token e-posta bilgisi içermiyor",
176
176
  "oauth_config_missing": "{provider} Uygulama ID'si ve Secret ortam değişkenlerinde yapılandırılmalıdır",
177
177
  "token_app_mismatch": "Token bu uygulamaya ait değil",
178
- "oauth_email_permission_missing": "{provider} kullanıcısı e-posta izni vermemiş"
178
+ "oauth_email_permission_missing": "{provider} kullanıcısı e-posta izni vermemiş",
179
+ "internal_server_error": "Bir şeyler ters gitti",
180
+ "duplicate_entry": "{model} için yinelenen kayıt. {field}: '{value}' değerine sahip kayıt zaten mevcut",
181
+ "authentication_not_available": "Kimlik doğrulama kullanılamıyor",
182
+ "authentication_required": "Kimlik doğrulama gerekli",
183
+ "user_and_password_required": "Kullanıcı ve şifre gerekli",
184
+ "auth_not_configured": "Kimlik doğrulama yapılandırılmamış",
185
+ "email_and_password_required": "E-posta ve şifre gerekli",
186
+ "email_already_exists": "E-posta zaten mevcut",
187
+ "user_registered": "Kullanıcı başarıyla kaydedildi",
188
+ "invalid_composite_key": "Geçersiz bileşik anahtar: {expected} alan bekleniyor, {received} alındı",
189
+ "invalid_composite_key_format": "Geçersiz bileşik anahtar formatı. Bir nesne veya tilde ile ayrılmış bir dize bekleniyor",
190
+ "no_permission_to_create": "{model} oluşturma izni yok",
191
+ "no_permission_to_update": "Kaydı güncelleme izni yok",
192
+ "no_permission_to_delete": "Kaydı silme izni yok",
193
+ "field_not_nullable": "'{field}' alanı null olamaz",
194
+ "relation_not_included": "'{relation}' ilişkisi alanlarda referans ediliyor ancak dahil edilmemiş. {hint}",
195
+ "max_nesting_depth_exceeded": "Maksimum iç içe geçme derinliği {depth} aşıldı",
196
+ "no_file_uploaded": "Dosya yüklenmedi"
179
197
  }
package/main.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import 'dotenv/config';
2
2
  import { getEnv } from './src/core/env';
3
3
  import { buildApp } from './src/app';
4
+ import { Logger } from './src/utils/Logger';
4
5
 
5
6
  /**
6
7
  * Application entry point
@@ -14,16 +15,15 @@ export async function start(): Promise<void> {
14
15
  const app = await buildApp();
15
16
 
16
17
  await app.listen({ port, host });
17
- console.log(`[Rapidd] Server running at http://${host}:${port}`);
18
- console.log(`[Rapidd] Environment: ${getEnv('NODE_ENV')}`);
18
+ Logger.log('Server running', { host, port });
19
+ Logger.log('Environment', { env: getEnv('NODE_ENV') });
19
20
 
20
21
  // Warn if running compiled build with development NODE_ENV
21
22
  if (process.argv[1]?.includes('/dist/') && getEnv('NODE_ENV') === 'development') {
22
- console.warn('[Rapidd] Warning: Running compiled build with NODE_ENV=development.');
23
- console.warn('[Rapidd] Set NODE_ENV=production in your .env for production use.');
23
+ Logger.warn('Running compiled build with NODE_ENV=development. Set NODE_ENV=production in your .env for production use.');
24
24
  }
25
25
  } catch (err) {
26
- console.error('[Startup Error]', (err as Error).message);
26
+ Logger.error(err as Error);
27
27
  process.exit(1);
28
28
  }
29
29
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "2.1.2",
6
+ "version": "2.1.4",
7
7
  "description": "Code-first REST API framework for TypeScript. Database in, API out.",
8
8
  "main": "dist/main.js",
9
9
  "bin": {
package/src/app.ts CHANGED
@@ -12,6 +12,7 @@ import { LanguageDict } from './core/i18n';
12
12
  import { disconnectAll } from './core/prisma';
13
13
  import { validateEnv } from './core/env';
14
14
  import { env } from './utils';
15
+ import { Logger } from './utils/Logger';
15
16
 
16
17
  // Plugins
17
18
  import securityPlugin from './plugins/security';
@@ -137,9 +138,35 @@ export async function buildApp(options: RapiddOptions = {}): Promise<FastifyInst
137
138
  await loadRoutes(app, routesPath);
138
139
  }
139
140
 
141
+ // ── Request Logging ─────────────────────────────
142
+ app.addHook('onSend', async (request, _reply, payload) => {
143
+ if (typeof payload === 'string') {
144
+ (request as any)._responsePayload = payload;
145
+ }
146
+ return payload;
147
+ });
148
+
149
+ app.addHook('onResponse', async (request, reply) => {
150
+ Logger.request({
151
+ method: request.method,
152
+ url: request.url,
153
+ status: reply.statusCode,
154
+ time: reply.elapsedTime,
155
+ ip: request.ip,
156
+ contentLength: request.headers['content-length'],
157
+ userId: request.user?.id,
158
+ userAgent: request.headers['user-agent'],
159
+ requestHeaders: request.headers,
160
+ requestBody: request.body,
161
+ responseHeaders: reply.getHeaders(),
162
+ responseBody: (request as any)._responsePayload,
163
+ });
164
+ });
165
+
140
166
  // ── 404 Handler ─────────────────────────────────
141
- app.setNotFoundHandler((_request, reply) => {
142
- reply.code(404).send({ status_code: 404, message: 'Not found' });
167
+ app.setNotFoundHandler((request, reply) => {
168
+ const language = request.language || 'en_US';
169
+ reply.code(404).send({ status_code: 404, message: LanguageDict.get('record_not_found', null, language) });
143
170
  });
144
171
 
145
172
  // ── Graceful Shutdown ───────────────────────────
@@ -183,7 +210,7 @@ async function loadRoutes(app: FastifyInstance, routePath: string): Promise<void
183
210
  await app.register(plugin, { prefix: route });
184
211
  }
185
212
  } catch (err) {
186
- console.error(`Failed to load route ${route}:`, (err as Error).message);
213
+ Logger.error(err as Error, { route });
187
214
  }
188
215
  }
189
216
  }
@@ -193,12 +220,12 @@ async function loadRoutes(app: FastifyInstance, routePath: string): Promise<void
193
220
 
194
221
  // Handle uncaught errors
195
222
  process.on('uncaughtException', (err) => {
196
- console.error('[Uncaught Exception]', err);
223
+ Logger.error(err);
197
224
  process.exit(1);
198
225
  });
199
226
 
200
- process.on('unhandledRejection', (reason, promise) => {
201
- console.error('[Unhandled Rejection] at:', promise, 'reason:', reason);
227
+ process.on('unhandledRejection', (reason) => {
228
+ Logger.error(reason as Error);
202
229
  });
203
230
 
204
231
  export default buildApp;