@venizia/ignis-docs 0.0.8-0 → 0.0.8-2
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/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
- package/dist/mcp-server/helpers/docs.helper.js +1 -1
- package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
- package/dist/mcp-server/tools/base.tool.d.ts +1 -1
- package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +1 -1
- package/dist/mcp-server/tools/docs/search-documents.tool.js +1 -1
- package/dist/mcp-server/tools/docs/search-documents.tool.js.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js.map +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
- package/package.json +14 -14
- package/wiki/extensions/components/authorization/api.md +239 -376
- package/wiki/extensions/components/authorization/errors.md +52 -43
- package/wiki/extensions/components/authorization/index.md +127 -65
- package/wiki/extensions/components/authorization/usage.md +198 -98
- package/wiki/extensions/helpers/kafka/consumer.md +6 -5
- package/wiki/extensions/helpers/kafka/examples.md +1 -1
- package/wiki/extensions/helpers/kafka/index.md +16 -12
- package/wiki/extensions/helpers/kafka/producer.md +4 -3
- package/wiki/guides/core-concepts/persistent/datasources.md +10 -11
- package/wiki/guides/core-concepts/persistent/index.md +6 -6
- package/wiki/guides/core-concepts/persistent/models.md +7 -5
- package/wiki/guides/core-concepts/persistent/repositories.md +11 -3
- package/wiki/guides/core-concepts/persistent/transactions.md +2 -1
- package/wiki/guides/core-concepts/rest-controllers.md +2 -2
- package/wiki/guides/core-concepts/services.md +0 -1
- package/wiki/guides/get-started/5-minute-quickstart.md +11 -10
- package/wiki/guides/migrations/scoped-rbac-migration.md +300 -0
- package/wiki/guides/tutorials/building-a-crud-api.md +43 -37
- package/wiki/guides/tutorials/complete-installation.md +64 -44
- package/wiki/guides/tutorials/ecommerce-api.md +21 -12
- package/wiki/guides/tutorials/realtime-chat.md +4 -5
- package/wiki/references/base/filter-system/default-filter.md +6 -3
- package/wiki/references/base/filter-system/fields-order-pagination.md +26 -0
- package/wiki/references/base/models.md +6 -3
- package/wiki/references/base/repositories/advanced.md +111 -0
|
@@ -19,15 +19,15 @@ bun init -y
|
|
|
19
19
|
### Production Dependencies
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
bun add hono @hono/zod-openapi @scalar/hono-api-reference @venizia/ignis
|
|
22
|
+
bun add hono @hono/zod-openapi @scalar/hono-api-reference @venizia/ignis @venizia/ignis-helpers
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
**What each package does:**
|
|
26
26
|
- `hono` - High-performance web framework
|
|
27
27
|
- `@hono/zod-openapi` - OpenAPI schema generation with Zod validation
|
|
28
28
|
- `@scalar/hono-api-reference` - Interactive API documentation UI
|
|
29
|
-
- `@venizia/ignis` - Core Ignis framework
|
|
30
|
-
-
|
|
29
|
+
- `@venizia/ignis` - Core Ignis framework (application, controllers, repositories, DI)
|
|
30
|
+
- `@venizia/ignis-helpers` - Utilities (HTTP constants, logger, environment helpers)
|
|
31
31
|
|
|
32
32
|
### Development Dependencies
|
|
33
33
|
|
|
@@ -121,7 +121,7 @@ This setup might seem verbose compared to minimal frameworks. The trade-off: ~50
|
|
|
121
121
|
### Create Project Structure
|
|
122
122
|
|
|
123
123
|
```bash
|
|
124
|
-
mkdir -p src/{common,components,configurations,controllers,datasources,helpers,models/{entities,requests,responses},repositories,services,utilities}
|
|
124
|
+
mkdir -p src/{common,components,configurations,controllers/hello,datasources,helpers,models/{entities,requests,responses},repositories,services,utilities}
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
Your structure will look like:
|
|
@@ -137,7 +137,10 @@ src/
|
|
|
137
137
|
│ └── index.ts # App configuration files
|
|
138
138
|
├── controllers/
|
|
139
139
|
│ ├── index.ts # Export all controllers
|
|
140
|
-
│ └── hello
|
|
140
|
+
│ └── hello/
|
|
141
|
+
│ ├── definitions.ts # Route configs, schemas, constants
|
|
142
|
+
│ ├── hello.controller.ts
|
|
143
|
+
│ └── index.ts # Barrel export
|
|
141
144
|
├── datasources/
|
|
142
145
|
│ └── index.ts # Database connections
|
|
143
146
|
├── helpers/
|
|
@@ -155,6 +158,8 @@ src/
|
|
|
155
158
|
└── index.ts # Utility functions
|
|
156
159
|
```
|
|
157
160
|
|
|
161
|
+
Each controller gets its own folder: `definitions.ts` for route configs and Zod schemas, the controller file itself, and an `index.ts` barrel export. This keeps things organized as controllers grow with custom routes, validations, and transformations.
|
|
162
|
+
|
|
158
163
|
> **Note:** For this guide, we only use `controllers/`. Other folders will be used in the [CRUD Tutorial](./building-a-crud-api.md) when you add database support.
|
|
159
164
|
|
|
160
165
|
### Create Application Class
|
|
@@ -163,7 +168,7 @@ Create `src/application.ts` - this is where you configure and register all your
|
|
|
163
168
|
|
|
164
169
|
```typescript
|
|
165
170
|
import { BaseApplication, IApplicationConfigs, IApplicationInfo, SwaggerComponent, ValueOrPromise } from '@venizia/ignis';
|
|
166
|
-
import { HelloController } from './controllers
|
|
171
|
+
import { HelloController } from './controllers';
|
|
167
172
|
import packageJson from '../package.json';
|
|
168
173
|
|
|
169
174
|
// Define application configurations
|
|
@@ -255,22 +260,39 @@ export class Application extends BaseApplication {
|
|
|
255
260
|
|
|
256
261
|
### Create Controller
|
|
257
262
|
|
|
258
|
-
|
|
263
|
+
Each controller lives in its own folder with separate files for definitions, logic, and exports.
|
|
264
|
+
|
|
265
|
+
Create `src/controllers/hello/definitions.ts` — route configs and schemas:
|
|
259
266
|
|
|
260
267
|
```typescript
|
|
261
|
-
import {
|
|
262
|
-
BaseRestController,
|
|
263
|
-
controller,
|
|
264
|
-
api,
|
|
265
|
-
jsonContent,
|
|
266
|
-
} from '@venizia/ignis';
|
|
268
|
+
import { jsonContent } from '@venizia/ignis';
|
|
267
269
|
import { HTTP } from '@venizia/ignis-helpers';
|
|
268
270
|
import { z } from '@hono/zod-openapi';
|
|
269
|
-
import { Context } from 'hono';
|
|
270
271
|
|
|
271
|
-
const BASE_PATH = '/hello';
|
|
272
|
+
export const BASE_PATH = '/hello';
|
|
273
|
+
|
|
274
|
+
export const helloRouteConfigs = {
|
|
275
|
+
sayHello: {
|
|
276
|
+
method: HTTP.Methods.GET,
|
|
277
|
+
path: '/',
|
|
278
|
+
responses: {
|
|
279
|
+
[HTTP.ResultCodes.RS_2.Ok]: jsonContent({
|
|
280
|
+
description: 'A simple hello message',
|
|
281
|
+
schema: z.object({ message: z.string() }),
|
|
282
|
+
}),
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
} as const;
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Create `src/controllers/hello/hello.controller.ts` — the controller class:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
import { BaseRestController, controller, api } from '@venizia/ignis';
|
|
292
|
+
import { HTTP } from '@venizia/ignis-helpers';
|
|
293
|
+
import { Context } from 'hono';
|
|
294
|
+
import { BASE_PATH, helloRouteConfigs } from './definitions';
|
|
272
295
|
|
|
273
|
-
// The @controller decorator registers this class as a controller
|
|
274
296
|
// All routes in this controller will be under /api/hello (remember path.base: '/api')
|
|
275
297
|
@controller({ path: BASE_PATH })
|
|
276
298
|
export class HelloController extends BaseRestController {
|
|
@@ -278,47 +300,38 @@ export class HelloController extends BaseRestController {
|
|
|
278
300
|
super({ scope: HelloController.name, path: BASE_PATH });
|
|
279
301
|
}
|
|
280
302
|
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
// this.bindRoute({ configs: { method: 'get', path: '/programmatic', responses: {...} } }).to({ handler: this.myHandler });
|
|
285
|
-
|
|
286
|
-
// Option 2: Use @api decorator on methods (shown below) - recommended
|
|
287
|
-
}
|
|
303
|
+
// Override binding() to register custom routes via bindRoute() or defineRoute().
|
|
304
|
+
// For decorator-based routes (@api, @get, @post), this can be empty.
|
|
305
|
+
override binding() {}
|
|
288
306
|
|
|
289
|
-
|
|
290
|
-
@api({
|
|
291
|
-
configs: {
|
|
292
|
-
method: HTTP.Methods.GET,
|
|
293
|
-
path: '/',
|
|
294
|
-
// This 'responses' config does two things:
|
|
295
|
-
// 1. Generates OpenAPI/Swagger documentation automatically
|
|
296
|
-
// 2. Validates that your handler returns the correct shape
|
|
297
|
-
responses: {
|
|
298
|
-
[HTTP.ResultCodes.RS_2.Ok]: jsonContent({
|
|
299
|
-
description: 'A simple hello message',
|
|
300
|
-
schema: z.object({ message: z.string() }),
|
|
301
|
-
}),
|
|
302
|
-
},
|
|
303
|
-
},
|
|
304
|
-
})
|
|
307
|
+
@api({ configs: helloRouteConfigs.sayHello })
|
|
305
308
|
sayHello(c: Context) {
|
|
306
309
|
return c.json({ message: 'Hello, World!' }, HTTP.ResultCodes.RS_2.Ok);
|
|
307
310
|
}
|
|
308
|
-
|
|
309
|
-
// For authenticated endpoints, add 'authenticate':
|
|
310
|
-
// @api({ configs: { method: HTTP.Methods.GET, path: '/secure', authenticate: { strategies: [Authentication.STRATEGY_JWT] } } })
|
|
311
311
|
}
|
|
312
312
|
```
|
|
313
313
|
|
|
314
|
+
Create `src/controllers/hello/index.ts` — barrel export:
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
export * from './hello.controller';
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Create `src/controllers/index.ts` — export all controllers:
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
export * from './hello';
|
|
324
|
+
```
|
|
325
|
+
|
|
314
326
|
**Controller Patterns:**
|
|
315
327
|
|
|
316
328
|
| Pattern | Description |
|
|
317
329
|
|---------|-------------|
|
|
318
|
-
|
|
|
330
|
+
| `definitions.ts` | Route configs, Zod schemas, and constants — keeps controller file clean |
|
|
331
|
+
| `@controller` | Registers the class as a controller with a base path |
|
|
319
332
|
| `@api` | Defines a route with `method` specified in configs |
|
|
320
333
|
| `@get`, `@post`, etc. | Shorthand decorators that auto-set the HTTP method (recommended) |
|
|
321
|
-
| `binding()` |
|
|
334
|
+
| `binding()` | Override for programmatic routes via `bindRoute()` / `defineRoute()`. Empty for decorator-based routes |
|
|
322
335
|
| Zod schemas | Provide automatic validation and OpenAPI docs |
|
|
323
336
|
|
|
324
337
|
> **Deep Dive:** See [Controllers Reference](../core-concepts/rest-controllers.md) for advanced routing patterns and validation.
|
|
@@ -341,6 +354,10 @@ const main = async () => {
|
|
|
341
354
|
|
|
342
355
|
const applicationName = process.env.APP_ENV_APPLICATION_NAME?.toUpperCase() ?? 'My-App';
|
|
343
356
|
logger.info('[main] Getting ready to start up %s Application...', applicationName);
|
|
357
|
+
|
|
358
|
+
// start() runs the full lifecycle automatically:
|
|
359
|
+
// staticConfigure → preConfigure → registerDataSources → registerComponents
|
|
360
|
+
// → registerControllers → postConfigure → setupMiddlewares → HTTP server
|
|
344
361
|
await application.start();
|
|
345
362
|
return application;
|
|
346
363
|
};
|
|
@@ -348,6 +365,9 @@ const main = async () => {
|
|
|
348
365
|
export default main();
|
|
349
366
|
```
|
|
350
367
|
|
|
368
|
+
> [!TIP]
|
|
369
|
+
> `start()` runs the full lifecycle internally. You only need the explicit `init()` → `boot()` → `start()` sequence when using **auto-discovery** (glob-based booters) to discover controllers/repositories from the filesystem. For manual registration (as shown here), `start()` alone is sufficient.
|
|
370
|
+
|
|
351
371
|
## 5. Run Your Application
|
|
352
372
|
|
|
353
373
|
Add common application scripts to your `package.json`:
|
|
@@ -28,7 +28,7 @@ cd ecommerce-api
|
|
|
28
28
|
bun init -y
|
|
29
29
|
|
|
30
30
|
# Install dependencies
|
|
31
|
-
bun add hono @hono/zod-openapi @venizia/ignis
|
|
31
|
+
bun add hono @hono/zod-openapi @venizia/ignis @venizia/ignis-helpers
|
|
32
32
|
bun add drizzle-orm drizzle-zod pg stripe
|
|
33
33
|
bun add -d typescript @types/bun @venizia/dev-configs drizzle-kit @types/pg
|
|
34
34
|
```
|
|
@@ -57,9 +57,19 @@ ecommerce-api/
|
|
|
57
57
|
│ │ ├── order.service.ts
|
|
58
58
|
│ │ └── payment.service.ts
|
|
59
59
|
│ ├── controllers/
|
|
60
|
-
│ │ ├── product
|
|
61
|
-
│ │ ├──
|
|
62
|
-
│ │
|
|
60
|
+
│ │ ├── product/
|
|
61
|
+
│ │ │ ├── definitions.ts
|
|
62
|
+
│ │ │ ├── product.controller.ts
|
|
63
|
+
│ │ │ └── index.ts
|
|
64
|
+
│ │ ├── cart/
|
|
65
|
+
│ │ │ ├── definitions.ts
|
|
66
|
+
│ │ │ ├── cart.controller.ts
|
|
67
|
+
│ │ │ └── index.ts
|
|
68
|
+
│ │ ├── order/
|
|
69
|
+
│ │ │ ├── definitions.ts
|
|
70
|
+
│ │ │ ├── order.controller.ts
|
|
71
|
+
│ │ │ └── index.ts
|
|
72
|
+
│ │ └── index.ts
|
|
63
73
|
│ └── datasources/
|
|
64
74
|
│ └── postgres.datasource.ts
|
|
65
75
|
└── package.json
|
|
@@ -314,7 +324,6 @@ export * from './order.model';
|
|
|
314
324
|
import {
|
|
315
325
|
BaseDataSource,
|
|
316
326
|
datasource,
|
|
317
|
-
TNodePostgresConnector,
|
|
318
327
|
ValueOrPromise,
|
|
319
328
|
} from '@venizia/ignis';
|
|
320
329
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
@@ -329,7 +338,7 @@ interface IDSConfigs {
|
|
|
329
338
|
}
|
|
330
339
|
|
|
331
340
|
@datasource({ driver: 'node-postgres' })
|
|
332
|
-
export class PostgresDataSource extends BaseDataSource<
|
|
341
|
+
export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
|
|
333
342
|
constructor() {
|
|
334
343
|
super({
|
|
335
344
|
name: PostgresDataSource.name,
|
|
@@ -915,7 +924,7 @@ export class PaymentService extends BaseService {
|
|
|
915
924
|
### Product Controller
|
|
916
925
|
|
|
917
926
|
```typescript
|
|
918
|
-
// src/controllers/product.controller.ts
|
|
927
|
+
// src/controllers/product/product.controller.ts
|
|
919
928
|
import { z } from '@hono/zod-openapi';
|
|
920
929
|
import {
|
|
921
930
|
BaseRestController,
|
|
@@ -1003,7 +1012,7 @@ export class ProductController extends BaseRestController {
|
|
|
1003
1012
|
### Cart Controller
|
|
1004
1013
|
|
|
1005
1014
|
```typescript
|
|
1006
|
-
// src/controllers/cart.controller.ts
|
|
1015
|
+
// src/controllers/cart/cart.controller.ts
|
|
1007
1016
|
import { z } from '@hono/zod-openapi';
|
|
1008
1017
|
import {
|
|
1009
1018
|
BaseRestController,
|
|
@@ -1142,7 +1151,7 @@ export class CartController extends BaseRestController {
|
|
|
1142
1151
|
### Order Controller
|
|
1143
1152
|
|
|
1144
1153
|
```typescript
|
|
1145
|
-
// src/controllers/order.controller.ts
|
|
1154
|
+
// src/controllers/order/order.controller.ts
|
|
1146
1155
|
import { z } from '@hono/zod-openapi';
|
|
1147
1156
|
import {
|
|
1148
1157
|
BaseRestController,
|
|
@@ -1265,9 +1274,9 @@ export class OrderController extends BaseRestController {
|
|
|
1265
1274
|
import { BaseApplication, IApplicationInfo } from '@venizia/ignis';
|
|
1266
1275
|
import { HealthCheckComponent, SwaggerComponent } from '@venizia/ignis';
|
|
1267
1276
|
|
|
1268
|
-
import { ProductController } from './controllers/product
|
|
1269
|
-
import { CartController } from './controllers/cart
|
|
1270
|
-
import { OrderController } from './controllers/order
|
|
1277
|
+
import { ProductController } from './controllers/product';
|
|
1278
|
+
import { CartController } from './controllers/cart';
|
|
1279
|
+
import { OrderController } from './controllers/order';
|
|
1271
1280
|
|
|
1272
1281
|
import { ProductService } from './services/product.service';
|
|
1273
1282
|
import { CartService } from './services/cart.service';
|
|
@@ -25,7 +25,7 @@ cd chat-api
|
|
|
25
25
|
bun init -y
|
|
26
26
|
|
|
27
27
|
# Install dependencies
|
|
28
|
-
bun add hono @hono/zod-openapi @venizia/ignis
|
|
28
|
+
bun add hono @hono/zod-openapi @venizia/ignis @venizia/ignis-helpers
|
|
29
29
|
bun add drizzle-orm drizzle-zod pg
|
|
30
30
|
bun add -d typescript @types/bun @venizia/dev-configs drizzle-kit @types/pg
|
|
31
31
|
|
|
@@ -241,7 +241,6 @@ export * from './message.model';
|
|
|
241
241
|
import {
|
|
242
242
|
BaseDataSource,
|
|
243
243
|
datasource,
|
|
244
|
-
TNodePostgresConnector,
|
|
245
244
|
ValueOrPromise,
|
|
246
245
|
} from '@venizia/ignis';
|
|
247
246
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
@@ -256,7 +255,7 @@ interface IDSConfigs {
|
|
|
256
255
|
}
|
|
257
256
|
|
|
258
257
|
@datasource({ driver: 'node-postgres' })
|
|
259
|
-
export class PostgresDataSource extends BaseDataSource<
|
|
258
|
+
export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
|
|
260
259
|
constructor() {
|
|
261
260
|
super({
|
|
262
261
|
name: PostgresDataSource.name,
|
|
@@ -953,7 +952,7 @@ import {
|
|
|
953
952
|
SocketIOServerHelper,
|
|
954
953
|
} from '@venizia/ignis-helpers';
|
|
955
954
|
import { ChatService } from './services/chat.service';
|
|
956
|
-
import { ChatController } from './controllers/chat
|
|
955
|
+
import { ChatController } from './controllers/chat';
|
|
957
956
|
import { UserRepository } from './repositories/user.repository';
|
|
958
957
|
import { RoomRepository, RoomMemberRepository } from './repositories/room.repository';
|
|
959
958
|
import { MessageRepository, DirectMessageRepository } from './repositories/message.repository';
|
|
@@ -1124,7 +1123,7 @@ Client connects → 'authenticate' event → authenticateFn() → 'authenticated
|
|
|
1124
1123
|
## 7. REST API for Chat History
|
|
1125
1124
|
|
|
1126
1125
|
```typescript
|
|
1127
|
-
// src/controllers/chat.controller.ts
|
|
1126
|
+
// src/controllers/chat/chat.controller.ts
|
|
1128
1127
|
import { z } from '@hono/zod-openapi';
|
|
1129
1128
|
import {
|
|
1130
1129
|
BaseRestController,
|
|
@@ -299,13 +299,13 @@ export class Subscription extends BaseEntity<typeof Subscription.schema> {}
|
|
|
299
299
|
|
|
300
300
|
### Query Limit Protection
|
|
301
301
|
|
|
302
|
+
Use the dedicated `settings.defaultLimit` to raise (or lower) the per-model default page size. Prefer it over putting `limit` inside `defaultFilter`:
|
|
303
|
+
|
|
302
304
|
```typescript
|
|
303
305
|
@model({
|
|
304
306
|
type: 'entity',
|
|
305
307
|
settings: {
|
|
306
|
-
|
|
307
|
-
limit: 1000, // Prevent unbounded queries
|
|
308
|
-
},
|
|
308
|
+
defaultLimit: 1000, // Per-model default when a query omits `limit`
|
|
309
309
|
},
|
|
310
310
|
})
|
|
311
311
|
export class LogEntry extends BaseEntity<typeof LogEntry.schema> {}
|
|
@@ -315,6 +315,9 @@ await logRepo.find({ filter: {} }); // LIMIT 1000
|
|
|
315
315
|
await logRepo.find({ filter: { limit: 50 } }); // LIMIT 50
|
|
316
316
|
```
|
|
317
317
|
|
|
318
|
+
> [!TIP]
|
|
319
|
+
> `defaultLimit` is independent of `defaultFilter`: bypassing the default filter via `shouldSkipDefaultFilter` does **not** drop the limit. See [Pagination → Default Limit](/references/base/filter-system/fields-order-pagination#default-limit).
|
|
320
|
+
|
|
318
321
|
|
|
319
322
|
## Relation Include Default Filters
|
|
320
323
|
|
|
@@ -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
|
|
@@ -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.
|
|
76
|
-
2.
|
|
77
|
-
3. The
|
|
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
|
|
|
@@ -101,6 +101,114 @@ async function transferFunds(fromId: string, toId: string, amount: number) {
|
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
|
|
104
|
+
## Row-Level Locking
|
|
105
|
+
|
|
106
|
+
Acquire pessimistic locks on selected rows within a transaction using PostgreSQL's `SELECT ... FOR UPDATE/SHARE` syntax.
|
|
107
|
+
|
|
108
|
+
### Basic Usage
|
|
109
|
+
|
|
110
|
+
Pass `lock` in options alongside a `transaction`:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const tx = await repo.beginTransaction();
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
// Lock the row — other transactions will wait
|
|
117
|
+
const item = await repo.findOne({
|
|
118
|
+
filter: { where: { id: '123' } },
|
|
119
|
+
options: {
|
|
120
|
+
transaction: tx,
|
|
121
|
+
lock: { strength: 'update' },
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Safe to modify — no concurrent changes possible
|
|
126
|
+
await repo.updateById({
|
|
127
|
+
id: '123',
|
|
128
|
+
data: { quantity: item.quantity - 1 },
|
|
129
|
+
options: { transaction: tx },
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
await tx.commit();
|
|
133
|
+
} catch (error) {
|
|
134
|
+
await tx.rollback();
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Lock Strengths
|
|
140
|
+
|
|
141
|
+
Use the `LockStrengths` constant class or string literals:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { LockStrengths } from '@venizia/ignis';
|
|
145
|
+
|
|
146
|
+
// Using constant
|
|
147
|
+
lock: { strength: LockStrengths.UPDATE }
|
|
148
|
+
|
|
149
|
+
// Using string literal
|
|
150
|
+
lock: { strength: 'update' }
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
| Strength | SQL | Use Case |
|
|
154
|
+
|----------|-----|----------|
|
|
155
|
+
| `update` | `FOR UPDATE` | Exclusive lock for writes |
|
|
156
|
+
| `no key update` | `FOR NO KEY UPDATE` | Exclusive lock, allows concurrent `FOR KEY SHARE` |
|
|
157
|
+
| `share` | `FOR SHARE` | Shared read lock, prevents writes |
|
|
158
|
+
| `key share` | `FOR KEY SHARE` | Weakest lock, only prevents key changes |
|
|
159
|
+
|
|
160
|
+
### Wait Behavior
|
|
161
|
+
|
|
162
|
+
Control what happens when rows are already locked:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// Skip locked rows (queue-style worker pattern)
|
|
166
|
+
const items = await repo.find({
|
|
167
|
+
filter: { where: { status: 'pending' }, limit: 10 },
|
|
168
|
+
options: {
|
|
169
|
+
transaction: tx,
|
|
170
|
+
lock: { strength: 'update', config: { skipLocked: true } },
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Fail immediately instead of waiting
|
|
175
|
+
const item = await repo.findOne({
|
|
176
|
+
filter: { where: { id: '123' } },
|
|
177
|
+
options: {
|
|
178
|
+
transaction: tx,
|
|
179
|
+
lock: { strength: 'update', config: { noWait: true } },
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
| Config | SQL | Behavior |
|
|
185
|
+
|--------|-----|----------|
|
|
186
|
+
| *(none)* | `FOR UPDATE` | Wait until lock is released |
|
|
187
|
+
| `{ noWait: true }` | `FOR UPDATE NOWAIT` | Throw error immediately if locked |
|
|
188
|
+
| `{ skipLocked: true }` | `FOR UPDATE SKIP LOCKED` | Silently skip locked rows |
|
|
189
|
+
|
|
190
|
+
### Constraints
|
|
191
|
+
|
|
192
|
+
> [!WARNING]
|
|
193
|
+
> Row-level locking requires a **transaction** and is **incompatible with `include`/`fields`** in the filter (these use the Drizzle Query API which does not support `.for()`).
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// Error — no transaction
|
|
197
|
+
await repo.findOne({
|
|
198
|
+
filter: { where: { id: '123' } },
|
|
199
|
+
options: { lock: { strength: 'update' } },
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Error — include uses Query API
|
|
203
|
+
await repo.findOne({
|
|
204
|
+
filter: { where: { id: '123' }, include: [{ relation: 'posts' }] },
|
|
205
|
+
options: { transaction: tx, lock: { strength: 'update' } },
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Supported methods:** `find`, `findOne`, `findById`
|
|
210
|
+
|
|
211
|
+
|
|
104
212
|
## Hidden Properties
|
|
105
213
|
|
|
106
214
|
Automatically exclude sensitive fields from query results.
|
|
@@ -599,6 +707,7 @@ All repository operations accept an `options` parameter with these fields:
|
|
|
599
707
|
| `transaction` | `ITransaction` | - | Transaction context for the operation |
|
|
600
708
|
| `log` | `{ use: boolean; level?: TLogLevel }` | - | Enable operation logging |
|
|
601
709
|
| `shouldSkipDefaultFilter` | `boolean` | `false` | Bypass the default filter from model settings |
|
|
710
|
+
| `lock` | `TLockOptions` | - | Row-level locking (requires transaction, Core API only) |
|
|
602
711
|
|
|
603
712
|
Write operations additionally support:
|
|
604
713
|
|
|
@@ -618,6 +727,8 @@ Write operations additionally support:
|
|
|
618
727
|
| Commit | `await tx.commit()` |
|
|
619
728
|
| Rollback | `await tx.rollback()` |
|
|
620
729
|
| Bypass default filter | `options: { shouldSkipDefaultFilter: true }` |
|
|
730
|
+
| Lock rows for update | `options: { transaction: tx, lock: { strength: 'update' } }` |
|
|
731
|
+
| Lock + skip locked | `options: { transaction: tx, lock: { strength: 'update', config: { skipLocked: true } } }` |
|
|
621
732
|
| Enable logging | `options: { log: { use: true, level: 'debug' } }` |
|
|
622
733
|
| Force delete all | `options: { force: true }` |
|
|
623
734
|
| Skip returning data | `options: { shouldReturn: false }` |
|