@venizia/ignis-docs 0.0.8-1 → 0.0.8-3

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.
@@ -132,6 +132,32 @@ await repo.find({
132
132
  > [!TIP]
133
133
  > Always use `limit` for public-facing endpoints to prevent memory exhaustion. The default limit is 10 if not specified.
134
134
 
135
+ ### Default Limit
136
+
137
+ When a query omits `limit`, the repository resolves one with this precedence:
138
+
139
+ ```
140
+ query.limit ?? model settings.defaultLimit ?? DEFAULT_LIMIT (10)
141
+ ```
142
+
143
+ - **`query.limit`** — an explicit `limit` in the filter always wins.
144
+ - **`settings.defaultLimit`** — a per-model default set on the `@model` decorator. Must be a positive integer (validated at decoration time). Applies to top-level `find()` and to every to-many relation (using the related model's own `defaultLimit`).
145
+ - **`DEFAULT_LIMIT`** — the global fallback, `10`.
146
+
147
+ ```typescript
148
+ @model({
149
+ type: 'entity',
150
+ settings: { defaultLimit: 200 }, // Small lookup table — default to 200 rows
151
+ })
152
+ export class Country extends BaseEntity<typeof Country.schema> {}
153
+
154
+ await countryRepo.find({ filter: {} }); // LIMIT 200
155
+ await countryRepo.find({ filter: { limit: 10 } }); // LIMIT 10 (explicit wins)
156
+ ```
157
+
158
+ > [!NOTE]
159
+ > `defaultLimit` is independent of `defaultFilter`: passing `shouldSkipDefaultFilter` to bypass the default `where` clause does **not** drop the default limit. There is no "unbounded" sentinel — to fetch more rows, pass an explicit `limit`.
160
+
135
161
  ### Pagination Helper
136
162
 
137
163
  ```typescript
@@ -43,14 +43,14 @@ IGNIS provides a collection of built-in middlewares for common application needs
43
43
 
44
44
  The error handler middleware catches all unhandled errors in your application and formats them into consistent JSON responses.
45
45
 
46
- **File:** `packages/core/src/base/middlewares/app-error.middleware.ts`
46
+ **File:** `packages/core/src/base/middlewares/app-error/app-error.middleware.ts`
47
47
 
48
48
  #### Features
49
49
 
50
50
  - **Automatic Error Formatting**: Converts all errors to structured JSON responses
51
- - **ZodError Support**: Special handling for Zod validation errors with detailed field-level messages
52
- - **Database Error Handling**: Automatically returns 400 for database constraint violations (unique, foreign key, not null, etc.)
53
- - **Environment-Aware**: Hides stack traces and error causes in production
51
+ - **ZodError Support**: Validation errors surface a schema-driven `messageCode` and `message` (from `params.code`, else the raw Zod code), with the full per-field list under `details.cause`
52
+ - **Database Error Handling**: Returns 400 for SQLSTATE class `22` (data exception) and `23` (integrity) errors, with a fallback message; other classes (e.g. `42` programming errors) stay 500
53
+ - **Production-Safe**: Hides stack traces, error causes, DB driver internals (`detail`/`table`/`constraint`), and raw system messages in production
54
54
  - **Request Tracking**: Includes `requestId` for debugging and tracing
55
55
  - **Status Code Detection**: Automatically extracts `statusCode` from errors
56
56
 
@@ -87,9 +87,13 @@ app.onError(appErrorHandler({
87
87
  ```
88
88
 
89
89
  **Validation Error (ZodError):**
90
+
91
+ Top-level `message`/`messageCode` come from the first failing issue — its `params.code` if the schema set one (see below), otherwise its raw Zod code. The full per-field list stays under `details.cause`. If `error.rootKey` is configured, the whole body is wrapped under that key.
92
+
90
93
  ```json
91
94
  {
92
- "message": "ValidationError",
95
+ "message": "Invalid email address",
96
+ "messageCode": "invalid_type",
93
97
  "statusCode": 422,
94
98
  "requestId": "abc123",
95
99
  "details": {
@@ -100,45 +104,50 @@ app.onError(appErrorHandler({
100
104
  {
101
105
  "path": "email",
102
106
  "message": "Invalid email address",
103
- "code": "invalid_string",
104
- "expected": "string",
105
- "received": "undefined"
107
+ "code": "invalid_type",
108
+ "expected": "string"
106
109
  }
107
110
  ]
108
111
  }
109
112
  }
110
113
  ```
111
114
 
115
+ To emit a stable, domain-specific `messageCode`, attach `params.code` to a custom check:
116
+
117
+ ```typescript
118
+ z.string().refine(isEmail, { message: 'Invalid email address', params: { code: 'user.email.invalid' } });
119
+ // → "messageCode": "user.email.invalid"
120
+ ```
121
+
112
122
  **Database Constraint Error:**
113
123
 
114
- Database constraint violations (unique, foreign key, not null, check) are automatically detected and returned as 400 Bad Request with a human-readable message:
124
+ Database errors in SQLSTATE class `22` (data exception) and `23` (integrity constraint) are detected by **class** and returned as 400 Bad Request. A known code uses its specific message; any other in-class code uses `DATABASE_CLIENT_ERROR_FALLBACK_MESSAGE` (`"Invalid database request"`).
115
125
 
116
126
  ```json
127
+ // non-production — full driver context for debugging
117
128
  {
118
129
  "message": "Unique constraint violation\nDetail: Key (email)=(test@example.com) already exists.\nTable: User\nConstraint: UQ_User_email",
119
130
  "statusCode": 400,
120
131
  "requestId": "abc123",
121
- "details": {
122
- "url": "http://localhost:3000/api/users",
123
- "path": "/api/users",
124
- "stack": "...", // development only
125
- "cause": { ... } // development only
126
- }
132
+ "details": { "url": "...", "path": "/api/users", "stack": "...", "cause": { } }
127
133
  }
128
134
  ```
129
135
 
130
- **Supported PostgreSQL Error Codes:**
131
-
132
- | Code | Error Type |
133
- |------|------------|
134
- | 23505 | Unique constraint violation |
135
- | 23503 | Foreign key constraint violation |
136
- | 23502 | Not null constraint violation |
137
- | 23514 | Check constraint violation |
138
- | 23P01 | Exclusion constraint violation |
139
- | 22P02 | Invalid text representation |
140
- | 22003 | Numeric value out of range |
141
- | 22001 | String data too long |
136
+ :::warning Production sanitizes database internals
137
+ In **production** the message is the base message only — `Detail:`/`Table:`/`Constraint:` are stripped (they echo row values and schema names), and `details.stack`/`details.cause` are omitted. Codes outside class 22/23 (e.g. `42703` undefined column) and connection failures return a generic `"Internal Server Error"`, so SQL, schema names, and connection host/port never leak.
138
+ :::
139
+
140
+ **Database client error classes** — codes in SQLSTATE class `22` (data exception), `23` (integrity constraint), and `44` (WITH CHECK OPTION) map to HTTP 400. Common codes get a specific message; any other in-class code uses the fallback (`"Invalid database request"`).
141
+
142
+ | Class | Codes with a specific message |
143
+ |-------|-------------------------------|
144
+ | `23` Integrity | `23505` unique · `23503` foreign key · `23502` not null · `23514` check · `23P01` exclusion · `23000` integrity · `23001` restrict |
145
+ | `22` Data exception | `22001` string too long · `22003` numeric range · `22004` null not allowed · `22007` datetime format · `22008` datetime overflow · `22009` tz displacement · `22011` substring · `22012` division by zero · `22023` invalid parameter · `22025` invalid escape · `22026` length mismatch · `22030` duplicate JSON key · `22032` invalid JSON · `22P01` floating-point · `22P02` invalid text · `22P03` invalid binary · `22P05` untranslatable char |
146
+ | `44` View check | `44000` WITH CHECK OPTION violation |
147
+
148
+ :::tip Transient conflicts return 409, not 400/500
149
+ Class `40` (`40001` serialization failure, `40P01` deadlock) is **transient/retryable** and returns **409 Conflict** with `messageCode: "database.conflict"` and a safe "please retry" message — the client can safely retry the same request. Programming/infra classes (`42` syntax, `53` resources, `0A`, `25`, `28`) remain 500.
150
+ :::
142
151
 
143
152
  #### API Reference
144
153
 
@@ -51,6 +51,7 @@ The `@model` decorator marks a class as a database entity and configures its beh
51
51
  settings?: {
52
52
  hiddenProperties?: string[], // Properties to exclude from query results
53
53
  defaultFilter?: TFilter, // Filter applied to all repository queries
54
+ defaultLimit?: number, // Default row limit when a query omits `limit`
54
55
  authorize?: { // Authorization settings
55
56
  principal: string, // Authorization subject name
56
57
  [extra: string | symbol]: any, // Extensible metadata
@@ -66,15 +67,17 @@ The `@model` decorator marks a class as a database entity and configures its beh
66
67
  | `skipMigrate` | `boolean` | Skip this model during schema migrations |
67
68
  | `settings.hiddenProperties` | `string[]` | Array of property names to exclude from all repository query results |
68
69
  | `settings.defaultFilter` | `TFilter` | Filter automatically applied to all repository queries (see [Default Filter](/references/base/filter-system/default-filter)) |
70
+ | `settings.defaultLimit` | `number` | Default row limit applied when a query omits `limit`. Must be a positive integer (validated at decoration time). Falls back to the global `DEFAULT_LIMIT` (10). See [Pagination](/references/base/filter-system/fields-order-pagination#default-limit) |
69
71
  | `settings.authorize` | `IModelAuthorizeSettings` | Authorization settings — declares the model's authorization principal (see [Authorization](/extensions/components/authorization/usage#model-based-resource-references)) |
70
72
  | `settings.authorize.principal` | `string` | The authorization subject name for this model. Auto-populates `AUTHORIZATION_SUBJECT` static property |
71
73
 
72
74
  #### `@model` Behavior
73
75
 
74
76
  When the `@model` decorator is applied:
75
- 1. If `settings.authorize.principal` is provided and `AUTHORIZATION_SUBJECT` is not already defined on the class, it auto-populates `AUTHORIZATION_SUBJECT` with the principal value
76
- 2. The model is registered in the `MetadataRegistry` model registry, keyed by table name (resolved as: `metadata.tableName` > `static TABLE_NAME` > class name)
77
- 3. The static `relations` property is stored as a resolver (not immediately resolved) to avoid circular dependency issues between models
77
+ 1. If `settings.defaultLimit` is provided, it is validated to be a positive integer otherwise the decorator throws at decoration (boot) time
78
+ 2. If `settings.authorize.principal` is provided and `AUTHORIZATION_SUBJECT` is not already defined on the class, it auto-populates `AUTHORIZATION_SUBJECT` with the principal value
79
+ 3. The model is registered in the `MetadataRegistry` model registry, keyed by table name (resolved as: `metadata.tableName` > `static TABLE_NAME` > class name)
80
+ 4. The static `relations` property is stored as a resolver (not immediately resolved) to avoid circular dependency issues between models
78
81
 
79
82
  ### Hidden Properties
80
83