@venizia/ignis-docs 0.0.3 → 0.0.4-1

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 (131) hide show
  1. package/README.md +1 -1
  2. package/package.json +4 -2
  3. package/wiki/best-practices/api-usage-examples.md +591 -0
  4. package/wiki/best-practices/architectural-patterns.md +415 -0
  5. package/wiki/best-practices/architecture-decisions.md +488 -0
  6. package/wiki/{get-started/best-practices → best-practices}/code-style-standards.md +406 -17
  7. package/wiki/{get-started/best-practices → best-practices}/common-pitfalls.md +109 -4
  8. package/wiki/{get-started/best-practices → best-practices}/contribution-workflow.md +34 -7
  9. package/wiki/best-practices/data-modeling.md +376 -0
  10. package/wiki/best-practices/deployment-strategies.md +698 -0
  11. package/wiki/best-practices/index.md +27 -0
  12. package/wiki/best-practices/performance-optimization.md +196 -0
  13. package/wiki/best-practices/security-guidelines.md +218 -0
  14. package/wiki/{get-started/best-practices → best-practices}/troubleshooting-tips.md +97 -1
  15. package/wiki/changelogs/2025-12-16-initial-architecture.md +1 -1
  16. package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +1 -1
  17. package/wiki/changelogs/2025-12-17-refactor.md +1 -1
  18. package/wiki/changelogs/2025-12-18-performance-optimizations.md +5 -5
  19. package/wiki/changelogs/2025-12-18-repository-validation-security.md +13 -7
  20. package/wiki/changelogs/2025-12-26-nested-relations-and-generics.md +2 -2
  21. package/wiki/changelogs/2025-12-29-dynamic-binding-registration.md +104 -0
  22. package/wiki/changelogs/2025-12-29-snowflake-uid-helper.md +100 -0
  23. package/wiki/changelogs/2025-12-30-repository-enhancements.md +214 -0
  24. package/wiki/changelogs/2025-12-31-json-path-filtering-array-operators.md +214 -0
  25. package/wiki/changelogs/2025-12-31-string-id-custom-generator.md +137 -0
  26. package/wiki/changelogs/2026-01-02-default-filter-and-repository-mixins.md +418 -0
  27. package/wiki/changelogs/index.md +6 -0
  28. package/wiki/changelogs/planned-schema-migrator.md +0 -8
  29. package/wiki/{get-started/core-concepts → guides/core-concepts/application}/bootstrapping.md +18 -5
  30. package/wiki/{get-started/core-concepts/application.md → guides/core-concepts/application/index.md} +47 -104
  31. package/wiki/guides/core-concepts/components-guide.md +509 -0
  32. package/wiki/{get-started → guides}/core-concepts/components.md +24 -17
  33. package/wiki/{get-started → guides}/core-concepts/controllers.md +30 -13
  34. package/wiki/{get-started → guides}/core-concepts/dependency-injection.md +97 -0
  35. package/wiki/guides/core-concepts/persistent/datasources.md +179 -0
  36. package/wiki/guides/core-concepts/persistent/index.md +119 -0
  37. package/wiki/guides/core-concepts/persistent/models.md +241 -0
  38. package/wiki/guides/core-concepts/persistent/repositories.md +219 -0
  39. package/wiki/guides/core-concepts/persistent/transactions.md +170 -0
  40. package/wiki/{get-started → guides}/core-concepts/services.md +26 -3
  41. package/wiki/{get-started → guides/get-started}/5-minute-quickstart.md +59 -14
  42. package/wiki/guides/get-started/philosophy.md +682 -0
  43. package/wiki/guides/get-started/setup.md +157 -0
  44. package/wiki/guides/index.md +89 -0
  45. package/wiki/guides/reference/glossary.md +243 -0
  46. package/wiki/{get-started → guides/reference}/mcp-docs-server.md +0 -10
  47. package/wiki/{get-started → guides/tutorials}/building-a-crud-api.md +134 -132
  48. package/wiki/{get-started/quickstart.md → guides/tutorials/complete-installation.md} +107 -71
  49. package/wiki/guides/tutorials/ecommerce-api.md +1399 -0
  50. package/wiki/guides/tutorials/realtime-chat.md +1261 -0
  51. package/wiki/guides/tutorials/testing.md +723 -0
  52. package/wiki/index.md +176 -37
  53. package/wiki/references/base/application.md +27 -0
  54. package/wiki/references/base/bootstrapping.md +31 -26
  55. package/wiki/references/base/components.md +24 -7
  56. package/wiki/references/base/controllers.md +50 -20
  57. package/wiki/references/base/datasources.md +30 -0
  58. package/wiki/references/base/dependency-injection.md +39 -3
  59. package/wiki/references/base/filter-system/application-usage.md +224 -0
  60. package/wiki/references/base/filter-system/array-operators.md +132 -0
  61. package/wiki/references/base/filter-system/comparison-operators.md +109 -0
  62. package/wiki/references/base/filter-system/default-filter.md +428 -0
  63. package/wiki/references/base/filter-system/fields-order-pagination.md +155 -0
  64. package/wiki/references/base/filter-system/index.md +127 -0
  65. package/wiki/references/base/filter-system/json-filtering.md +197 -0
  66. package/wiki/references/base/filter-system/list-operators.md +71 -0
  67. package/wiki/references/base/filter-system/logical-operators.md +156 -0
  68. package/wiki/references/base/filter-system/null-operators.md +58 -0
  69. package/wiki/references/base/filter-system/pattern-matching.md +108 -0
  70. package/wiki/references/base/filter-system/quick-reference.md +431 -0
  71. package/wiki/references/base/filter-system/range-operators.md +63 -0
  72. package/wiki/references/base/filter-system/tips.md +190 -0
  73. package/wiki/references/base/filter-system/use-cases.md +452 -0
  74. package/wiki/references/base/index.md +90 -0
  75. package/wiki/references/base/middlewares.md +604 -0
  76. package/wiki/references/base/models.md +215 -23
  77. package/wiki/references/base/providers.md +731 -0
  78. package/wiki/references/base/repositories/advanced.md +555 -0
  79. package/wiki/references/base/repositories/index.md +228 -0
  80. package/wiki/references/base/repositories/mixins.md +331 -0
  81. package/wiki/references/base/repositories/relations.md +486 -0
  82. package/wiki/references/base/repositories.md +40 -635
  83. package/wiki/references/base/services.md +28 -4
  84. package/wiki/references/components/authentication.md +22 -2
  85. package/wiki/references/components/health-check.md +12 -0
  86. package/wiki/references/components/index.md +23 -0
  87. package/wiki/references/components/mail.md +687 -0
  88. package/wiki/references/components/request-tracker.md +16 -0
  89. package/wiki/references/components/socket-io.md +18 -0
  90. package/wiki/references/components/static-asset.md +14 -26
  91. package/wiki/references/components/swagger.md +17 -0
  92. package/wiki/references/configuration/environment-variables.md +427 -0
  93. package/wiki/references/configuration/index.md +73 -0
  94. package/wiki/references/helpers/cron.md +14 -0
  95. package/wiki/references/helpers/crypto.md +15 -0
  96. package/wiki/references/helpers/env.md +16 -0
  97. package/wiki/references/helpers/error.md +17 -0
  98. package/wiki/references/helpers/index.md +14 -0
  99. package/wiki/references/helpers/inversion.md +24 -4
  100. package/wiki/references/helpers/logger.md +19 -0
  101. package/wiki/references/helpers/network.md +11 -0
  102. package/wiki/references/helpers/queue.md +19 -0
  103. package/wiki/references/helpers/redis.md +21 -0
  104. package/wiki/references/helpers/socket-io.md +24 -5
  105. package/wiki/references/helpers/storage.md +18 -10
  106. package/wiki/references/helpers/testing.md +18 -0
  107. package/wiki/references/helpers/types.md +16 -0
  108. package/wiki/references/helpers/uid.md +167 -0
  109. package/wiki/references/helpers/worker-thread.md +16 -0
  110. package/wiki/references/index.md +177 -0
  111. package/wiki/references/quick-reference.md +634 -0
  112. package/wiki/references/src-details/boot.md +3 -3
  113. package/wiki/references/src-details/dev-configs.md +0 -4
  114. package/wiki/references/src-details/docs.md +2 -2
  115. package/wiki/references/src-details/index.md +86 -0
  116. package/wiki/references/src-details/inversion.md +1 -6
  117. package/wiki/references/src-details/mcp-server.md +3 -15
  118. package/wiki/references/utilities/index.md +86 -10
  119. package/wiki/references/utilities/jsx.md +577 -0
  120. package/wiki/references/utilities/request.md +0 -2
  121. package/wiki/references/utilities/statuses.md +740 -0
  122. package/wiki/get-started/best-practices/api-usage-examples.md +0 -266
  123. package/wiki/get-started/best-practices/architectural-patterns.md +0 -170
  124. package/wiki/get-started/best-practices/data-modeling.md +0 -177
  125. package/wiki/get-started/best-practices/deployment-strategies.md +0 -121
  126. package/wiki/get-started/best-practices/performance-optimization.md +0 -97
  127. package/wiki/get-started/best-practices/security-guidelines.md +0 -99
  128. package/wiki/get-started/core-concepts/persistent.md +0 -539
  129. package/wiki/get-started/index.md +0 -65
  130. package/wiki/get-started/philosophy.md +0 -296
  131. package/wiki/get-started/prerequisites.md +0 -113
@@ -1,3 +1,9 @@
1
+ ---
2
+ title: Controllers Reference
3
+ description: Technical reference for controller classes and API endpoints
4
+ difficulty: beginner
5
+ ---
6
+
1
7
  # Deep Dive: Controllers
2
8
 
3
9
  Technical reference for controller classes - the foundation for creating API endpoints in Ignis.
@@ -86,8 +92,8 @@ For convenience, `Ignis` provides decorator shortcuts for each HTTP method: Thes
86
92
  import { get, post, z, jsonContent, jsonResponse, Authentication, TRouteContext, HTTP } from '@venizia/ignis';
87
93
 
88
94
  // Define route configs as const for full type inference
89
- const USER_ROUTES = {
90
- listUsers: {
95
+ const UserRoutes = {
96
+ LIST_USERS: {
91
97
  path: '/',
92
98
  method: 'get',
93
99
  responses: jsonResponse({
@@ -95,7 +101,7 @@ const USER_ROUTES = {
95
101
  schema: z.array(z.object({ id: z.string(), name: z.string() })),
96
102
  }),
97
103
  },
98
- getUser: {
104
+ GET_USER: {
99
105
  path: '/:id',
100
106
  method: 'get',
101
107
  request: {
@@ -106,7 +112,7 @@ const USER_ROUTES = {
106
112
  schema: z.object({ id: z.string(), name: z.string() }),
107
113
  }),
108
114
  },
109
- createUser: {
115
+ CREATE_USER: {
110
116
  path: '/',
111
117
  method: 'post',
112
118
  authStrategies: [Authentication.STRATEGY_JWT], // Secure this endpoint
@@ -123,26 +129,26 @@ const USER_ROUTES = {
123
129
 
124
130
  // ... inside a controller class
125
131
 
126
- @get({ configs: USER_ROUTES.listUsers })
127
- getAllUsers(c: TRouteContext<typeof USER_ROUTES.listUsers>) { // Return type is automatically inferred
132
+ @get({ configs: UserRoutes.LIST_USERS })
133
+ getAllUsers(c: TRouteContext<typeof UserRoutes.LIST_USERS>) { // Return type is automatically inferred
128
134
  return c.json([{ id: '1', name: 'John Doe' }], HTTP.ResultCodes.RS_2.Ok);
129
135
  }
130
136
 
131
- @get({ configs: USER_ROUTES.getUser })
132
- getUserById(c: TRouteContext<typeof USER_ROUTES.getUser>) { // Return type is automatically inferred
137
+ @get({ configs: UserRoutes.GET_USER })
138
+ getUserById(c: TRouteContext<typeof UserRoutes.GET_USER>) { // Return type is automatically inferred
133
139
  const { id } = c.req.valid('param'); // id is typed as string
134
140
  return c.json({ id, name: 'John Doe' }, HTTP.ResultCodes.RS_2.Ok);
135
141
  }
136
142
 
137
- @post({ configs: USER_ROUTES.createUser })
138
- createUser(c: TRouteContext<typeof USER_ROUTES.createUser>) { // Return type is automatically inferred
143
+ @post({ configs: UserRoutes.CREATE_USER })
144
+ createUser(c: TRouteContext<typeof UserRoutes.CREATE_USER>) { // Return type is automatically inferred
139
145
  const { name } = c.req.valid('json'); // name is typed as string
140
146
  const newUser = { id: '2', name };
141
147
  return c.json(newUser, HTTP.ResultCodes.RS_2.Created); // Return type is validated
142
148
  }
143
149
  ```
144
150
 
145
- **Example using shared `ROUTE_CONFIGS`:**
151
+ **Example using shared `RouteConfigs`:**
146
152
 
147
153
  For better organization, you can define all your route configurations in a constant and reference them in your decorators. This approach also allows you to get a typed context for your handler.
148
154
 
@@ -150,8 +156,8 @@ For better organization, you can define all your route configurations in a const
150
156
  import { api, BaseController, controller, TRouteContext, jsonContent, jsonResponse, HTTP } from '@venizia/ignis';
151
157
  import { z } from 'hono/zod-openapi';
152
158
 
153
- const HEALTH_CHECK_ROUTES = {
154
- '/ping': {
159
+ const RouteConfigs = {
160
+ PING: {
155
161
  method: HTTP.Methods.POST,
156
162
  path: '/ping',
157
163
  request: {
@@ -167,9 +173,9 @@ const HEALTH_CHECK_ROUTES = {
167
173
 
168
174
  @controller({ path: '/health' })
169
175
  export class HealthCheckController extends BaseController {
170
-
171
- @api({ configs: HEALTH_CHECK_ROUTES['/ping'] })
172
- ping(c: TRouteContext<typeof HEALTH_CHECK_ROUTES['/ping']>) { // Return type is automatically inferred
176
+
177
+ @api({ configs: RouteConfigs.PING })
178
+ ping(c: TRouteContext<typeof RouteConfigs.PING>) { // Return type is automatically inferred
173
179
  const { message } = c.req.valid('json');
174
180
  return c.json({ pong: message }, HTTP.ResultCodes.RS_2.Ok);
175
181
  }
@@ -258,17 +264,17 @@ request: {
258
264
  The `defineRouteConfigs` function is a simple helper for creating a typed object containing multiple route configurations. This is particularly useful for organizing all of a controller's route definitions in a single, type-checked constant.
259
265
 
260
266
  ```typescript
261
- import { defineRouteConfigs, HTTP, jsonResponse, z } from '@venizia/ignis';
267
+ import { defineRouteConfigs, HTTP, jsonResponse, jsonContent, z } from '@venizia/ignis';
262
268
 
263
- const ROUTE_CONFIGS = defineRouteConfigs({
264
- '/': {
269
+ const RouteConfigs = defineRouteConfigs({
270
+ ROOT: {
265
271
  method: HTTP.Methods.GET,
266
272
  path: '/',
267
273
  responses: jsonResponse({
268
274
  schema: z.object({ status: z.string() }),
269
275
  }),
270
276
  },
271
- '/ping': {
277
+ PING: {
272
278
  method: HTTP.Methods.POST,
273
279
  path: '/ping',
274
280
  request: {
@@ -452,3 +458,27 @@ export class ConfigurationController extends _ConfigurationController {
452
458
  ```
453
459
 
454
460
  By leveraging these structured configuration options and the `ControllerFactory`, you ensure that your API is not only functional but also well-documented, easy to validate, and rapidly deployable for standard CRUD operations.
461
+
462
+ ---
463
+
464
+ ## See Also
465
+
466
+ - **Related References:**
467
+ - [Services](./services.md) - Business logic layer called by controllers
468
+ - [Repositories](./repositories/) - Data access layer for CRUD operations
469
+ - [Middlewares](./middlewares.md) - Request/response middleware
470
+ - [Application](./application.md) - Application setup and controller mounting
471
+ - [Dependency Injection](./dependency-injection.md) - DI patterns and injection
472
+
473
+ - **Guides:**
474
+ - [Controllers Guide](/guides/core-concepts/controllers)
475
+ - [Building a CRUD API](/guides/tutorials/building-a-crud-api)
476
+
477
+ - **Best Practices:**
478
+ - [API Usage Examples](/best-practices/api-usage-examples)
479
+ - [Troubleshooting Tips](/best-practices/troubleshooting-tips)
480
+ - [Security Guidelines](/best-practices/security-guidelines)
481
+
482
+ - **External Resources:**
483
+ - [OpenAPI Specification](https://swagger.io/specification/)
484
+ - [HTTP Methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)
@@ -1,3 +1,9 @@
1
+ ---
2
+ title: DataSources Reference
3
+ description: Technical reference for DataSource classes and database connections
4
+ difficulty: intermediate
5
+ ---
6
+
1
7
  # Deep Dive: DataSources
2
8
 
3
9
  Technical reference for DataSource classes - managing database connections in Ignis.
@@ -338,3 +344,27 @@ try {
338
344
  > **Note:** For most use cases, prefer using `repository.beginTransaction()` which provides a higher-level API. See [Repositories Reference](./repositories.md#transactions) for details.
339
345
 
340
346
  This architecture ensures that datasources are configured consistently and that the fully-initialized Drizzle connector, aware of all schemas and relations, is available to repositories for querying.
347
+
348
+ ## See Also
349
+
350
+ - **Related Concepts:**
351
+ - [DataSources Guide](/guides/core-concepts/persistent/datasources) - Creating DataSources tutorial
352
+ - [Repositories](/guides/core-concepts/persistent/repositories) - Using DataSources for database access
353
+ - [Models](/guides/core-concepts/persistent/models) - Entity schemas loaded by DataSource
354
+ - [Transactions](/guides/core-concepts/persistent/transactions) - Multi-operation database transactions
355
+
356
+ - **References:**
357
+ - [Repositories API](/references/base/repositories/) - Data access layer
358
+ - [Environment Variables](/references/configuration/environment-variables) - Configuration management
359
+
360
+ - **External Resources:**
361
+ - [Drizzle ORM Documentation](https://orm.drizzle.team/) - ORM configuration
362
+ - [node-postgres Documentation](https://node-postgres.com/) - Connection pooling guide
363
+
364
+ - **Best Practices:**
365
+ - [Performance Optimization](/best-practices/performance-optimization) - Connection pool tuning
366
+ - [Security Guidelines](/best-practices/security-guidelines) - Database credential management
367
+
368
+ - **Tutorials:**
369
+ - [Complete Installation](/guides/tutorials/complete-installation) - Database setup
370
+ - [Building a CRUD API](/guides/tutorials/building-a-crud-api) - DataSource configuration
@@ -1,3 +1,9 @@
1
+ ---
2
+ title: Dependency Injection Reference
3
+ description: Technical reference for the DI system in IGNIS
4
+ difficulty: advanced
5
+ ---
6
+
1
7
  # Deep Dive: Dependency Injection
2
8
 
3
9
  Technical reference for the DI system in Ignis - managing resource lifecycles and dependency resolution.
@@ -18,6 +24,15 @@ Technical reference for the DI system in Ignis - managing resource lifecycles an
18
24
  | **MetadataRegistry** | Stores decorator metadata | Singleton accessed via `getInstance()` |
19
25
  | **Boot System** | Automatic artifact discovery and binding | Integrates with Container via tags and bindings |
20
26
 
27
+ ## Prerequisites
28
+
29
+ Before reading this document, you should understand:
30
+
31
+ - [TypeScript Decorators](https://www.typescriptlang.org/docs/handbook/decorators.html) - How decorators work in TypeScript
32
+ - [IGNIS Application basics](./application.md) - Application lifecycle and initialization
33
+ - [Services](./services.md) and [Controllers](./controllers.md) - Basic understanding of IGNIS architecture
34
+ - Inversion of Control (IoC) pattern - [Martin Fowler's article](https://martinfowler.com/articles/injection.html)
35
+
21
36
  ## `Container` Class
22
37
 
23
38
  Heart of the DI system - registry managing all application resources.
@@ -161,8 +176,8 @@ app.bind({ key: 'controllers.UserController' }).toClass(UserController);
161
176
  @injectable()
162
177
  class UserController {
163
178
  constructor(
164
- @inject({ key: 'services.UserService' })
165
- private userService: UserService // Auto-injected!
179
+ @inject({ key: 'services.UserService' })
180
+ private _userService: UserService // Auto-injected!
166
181
  ) {}
167
182
  }
168
183
  ```
@@ -174,4 +189,25 @@ class UserController {
174
189
  - **Extensible**: Custom booters integrate seamlessly via tags
175
190
  - **Type-safe**: Full TypeScript support throughout boot process
176
191
 
177
- > **Learn More:** See [Bootstrapping Concepts](/get-started/core-concepts/bootstrapping.md) and [Boot Package Reference](/references/src-details/boot.md)
192
+ > **Learn More:** See [Bootstrapping Concepts](/guides/core-concepts/application/bootstrapping) and [Boot Package Reference](/references/src-details/boot.md)
193
+
194
+ ## See Also
195
+
196
+ - **Related Concepts:**
197
+ - [Dependency Injection Guide](/guides/core-concepts/dependency-injection) - DI fundamentals tutorial
198
+ - [Application](/guides/core-concepts/application/) - Application extends Container
199
+ - [Controllers](/guides/core-concepts/controllers) - Use DI for injecting services
200
+ - [Services](/guides/core-concepts/services) - Use DI for injecting repositories
201
+ - [Providers](/references/base/providers) - Factory pattern for dynamic injection
202
+
203
+ - **References:**
204
+ - [Inversion Helper](/references/helpers/inversion) - DI container utilities
205
+ - [Bootstrapping API](/references/base/bootstrapping) - Auto-discovery and DI
206
+ - [Glossary](/guides/reference/glossary#dependency-injection-di) - DI concepts explained
207
+
208
+ - **Tutorials:**
209
+ - [Testing](/guides/tutorials/testing) - Unit testing with mocked dependencies
210
+ - [Building a CRUD API](/guides/tutorials/building-a-crud-api) - DI in practice
211
+
212
+ - **Best Practices:**
213
+ - [Architectural Patterns](/best-practices/architectural-patterns) - DI patterns and anti-patterns
@@ -0,0 +1,224 @@
1
+ ---
2
+ title: Using Filters in Your Application
3
+ description: How filters flow through application layers
4
+ difficulty: intermediate
5
+ ---
6
+
7
+ # Using Filters in Your Application
8
+
9
+ How filters flow through the application layers.
10
+
11
+
12
+ ## Architecture Overview
13
+
14
+ ```
15
+ +-----------------------------------------------------------------+
16
+ | HTTP Request |
17
+ | GET /products?filter={"where":{"status":"active"},"limit":10} |
18
+ +--------------------------------+--------------------------------+
19
+ |
20
+ v
21
+ +-----------------------------------------------------------------+
22
+ | Controller Layer |
23
+ | - Validates filter via Zod schema |
24
+ | - Parses JSON string -> Filter object |
25
+ | - Passes to service/repository |
26
+ +-----------------------------------------------------------------+
27
+ |
28
+ v
29
+ +-----------------------------------------------------------------+
30
+ | Service Layer (Optional) |
31
+ | - Business logic, authorization |
32
+ | - May modify filter before passing |
33
+ +-----------------------------------------------------------------+
34
+ |
35
+ v
36
+ +-----------------------------------------------------------------+
37
+ | Repository Layer |
38
+ | - FilterBuilder transforms Filter -> SQL |
39
+ | - Executes query via Drizzle ORM |
40
+ | - Returns typed results |
41
+ +-----------------------------------------------------------------+
42
+ ```
43
+
44
+
45
+ ## Controller Layer
46
+
47
+ ### Using ControllerFactory (Recommended)
48
+
49
+ The `ControllerFactory` automatically handles filter parsing and validation:
50
+
51
+ ```typescript
52
+ // src/controllers/product.controller.ts
53
+ import { Product } from '@/models';
54
+ import { ProductRepository } from '@/repositories';
55
+ import {
56
+ controller,
57
+ ControllerFactory,
58
+ inject,
59
+ BindingKeys,
60
+ BindingNamespaces,
61
+ } from '@venizia/ignis';
62
+
63
+ const BASE_PATH = '/products';
64
+
65
+ const _Controller = ControllerFactory.defineCrudController({
66
+ repository: { name: ProductRepository.name },
67
+ controller: {
68
+ name: 'ProductController',
69
+ basePath: BASE_PATH,
70
+ isStrict: true,
71
+ defaultLimit: 20,
72
+ },
73
+ entity: () => Product,
74
+ });
75
+
76
+ @controller({ path: BASE_PATH })
77
+ export class ProductController extends _Controller {
78
+ constructor(
79
+ @inject({
80
+ key: BindingKeys.build({
81
+ namespace: BindingNamespaces.REPOSITORY,
82
+ key: ProductRepository.name,
83
+ }),
84
+ })
85
+ repository: ProductRepository,
86
+ ) {
87
+ super(repository);
88
+ }
89
+ }
90
+ ```
91
+
92
+ **Generated Endpoints:**
93
+
94
+ | Method | Endpoint | Filter Location |
95
+ |--------|----------|-----------------|
96
+ | GET | `/products` | Query param: `?filter={...}` |
97
+ | GET | `/products/:id` | Query param: `?filter={...}` (for includes) |
98
+ | GET | `/products/one` | Query param: `?filter={...}` |
99
+ | GET | `/products/count` | Query param: `?where={...}` |
100
+
101
+ ### Custom Controller with Manual Filter Handling
102
+
103
+ ```typescript
104
+ @controller({ path: '/products' })
105
+ export class ProductController extends BaseController {
106
+ constructor(
107
+ @inject({ key: 'repositories.ProductRepository' })
108
+ private _productRepo: ProductRepository,
109
+ ) {
110
+ super({ scope: 'ProductController', path: '/products' });
111
+ }
112
+
113
+ override binding() {
114
+ this.defineRoute({
115
+ configs: {
116
+ path: '/search',
117
+ method: 'get',
118
+ query: {
119
+ filter: FilterSchema,
120
+ },
121
+ },
122
+ handler: async (context) => {
123
+ const { filter = {} } = context.req.valid('query');
124
+ const results = await this._productRepo.find({ filter });
125
+ return context.json(results);
126
+ },
127
+ });
128
+ }
129
+ }
130
+ ```
131
+
132
+
133
+ ## Service Layer
134
+
135
+ Services can modify filters before passing to repositories:
136
+
137
+ ```typescript
138
+ @service()
139
+ export class ProductService {
140
+ constructor(
141
+ @inject({ key: 'repositories.ProductRepository' })
142
+ private _productRepo: ProductRepository,
143
+ ) {}
144
+
145
+ async findProducts(filter: TFilter<TProductSchema> = {}) {
146
+ // Merge user filter with soft-delete condition
147
+ const enhancedFilter: TFilter<TProductSchema> = {
148
+ ...filter,
149
+ where: {
150
+ ...filter.where,
151
+ deletedAt: { is: null },
152
+ },
153
+ };
154
+
155
+ return this._productRepo.find({ filter: enhancedFilter });
156
+ }
157
+
158
+ async findProductsForTenant(
159
+ tenantId: string,
160
+ filter: TFilter<TProductSchema> = {},
161
+ ) {
162
+ const isolatedFilter: TFilter<TProductSchema> = {
163
+ ...filter,
164
+ where: {
165
+ ...filter.where,
166
+ tenantId,
167
+ },
168
+ };
169
+
170
+ return this._productRepo.find({ filter: isolatedFilter });
171
+ }
172
+ }
173
+ ```
174
+
175
+
176
+ ## HTTP Request Examples
177
+
178
+ **cURL:**
179
+ ```bash
180
+ # Simple filter
181
+ curl "http://localhost:3000/products?filter=%7B%22where%22%3A%7B%22status%22%3A%22active%22%7D%2C%22limit%22%3A10%7D"
182
+
183
+ # Decoded filter: {"where":{"status":"active"},"limit":10}
184
+
185
+ # Complex filter with URL encoding
186
+ curl -G "http://localhost:3000/products" \
187
+ --data-urlencode 'filter={"where":{"price":{"gte":100,"lte":500},"tags":{"contains":["featured"]}},"order":["price ASC"],"limit":20}'
188
+ ```
189
+
190
+ **JavaScript/TypeScript:**
191
+ ```typescript
192
+ // Using fetch
193
+ const filter = {
194
+ where: { status: 'active', price: { lte: 100 } },
195
+ order: ['createdAt DESC'],
196
+ limit: 10,
197
+ };
198
+
199
+ const response = await fetch(
200
+ `/api/products?filter=${encodeURIComponent(JSON.stringify(filter))}`
201
+ );
202
+
203
+ // Using axios
204
+ const response = await axios.get('/api/products', {
205
+ params: { filter: JSON.stringify(filter) },
206
+ });
207
+ ```
208
+
209
+
210
+ ## Debugging Filters
211
+
212
+ ```typescript
213
+ // Enable logging to see generated SQL
214
+ const result = await repo.find({
215
+ filter: complexFilter,
216
+ options: {
217
+ log: { use: true, level: 'debug' },
218
+ },
219
+ });
220
+
221
+ // Or use buildQuery to inspect without executing
222
+ const queryOptions = repo.buildQuery({ filter: complexFilter });
223
+ console.log('Generated query options:', queryOptions);
224
+ ```
@@ -0,0 +1,132 @@
1
+ ---
2
+ title: PostgreSQL Array Operators
3
+ description: Operators for PostgreSQL array columns
4
+ difficulty: intermediate
5
+ ---
6
+
7
+ # PostgreSQL Array Operators
8
+
9
+ Operators for PostgreSQL array columns (`varchar[]`, `text[]`, `integer[]`, etc.).
10
+
11
+ | Operator | PostgreSQL | Description |
12
+ |----------|------------|-------------|
13
+ | `contains` | `@>` | Array contains **ALL** specified elements |
14
+ | `containedBy` | `<@` | Array is a **subset** of specified elements |
15
+ | `overlaps` | `&&` | Array shares **ANY** element with specified |
16
+
17
+
18
+ ## contains (@>)
19
+
20
+ Find rows where the array column contains **all** specified elements.
21
+
22
+ ```typescript
23
+ // Schema: tags varchar(100)[]
24
+ // Data: Product A has ['electronics', 'featured', 'sale']
25
+
26
+ // Find products with BOTH 'electronics' AND 'featured'
27
+ { where: { tags: { contains: ['electronics', 'featured'] } } }
28
+ // SQL: "tags"::text[] @> ARRAY['electronics', 'featured']::text[]
29
+
30
+ // Single element
31
+ { where: { tags: { contains: ['featured'] } } }
32
+ // Matches: ['featured'], ['featured', 'sale'], ['a', 'featured', 'b']
33
+ ```
34
+
35
+
36
+ ## containedBy (<@)
37
+
38
+ Find rows where **all** array elements are within the specified set.
39
+
40
+ ```typescript
41
+ // Find products where ALL tags are in the allowed list
42
+ { where: { tags: { containedBy: ['sale', 'featured', 'new', 'popular'] } } }
43
+ // SQL: "tags"::text[] <@ ARRAY['sale', 'featured', 'new', 'popular']::text[]
44
+
45
+ // Product A ['featured', 'sale'] -> matches (all in list)
46
+ // Product B ['featured', 'clearance'] -> no match ('clearance' not in list)
47
+ // Product C [] -> matches (empty is subset of everything)
48
+ ```
49
+
50
+
51
+ ## overlaps (&&)
52
+
53
+ Find rows where the arrays share at least one common element.
54
+
55
+ ```typescript
56
+ // Find products with 'premium' OR 'sale' tag
57
+ { where: { tags: { overlaps: ['premium', 'sale'] } } }
58
+ // SQL: "tags"::text[] && ARRAY['premium', 'sale']::text[]
59
+
60
+ // Product A ['featured', 'sale'] -> matches (has 'sale')
61
+ // Product B ['premium', 'luxury'] -> matches (has 'premium')
62
+ // Product C ['new', 'featured'] -> no match (no overlap)
63
+ ```
64
+
65
+
66
+ ## Visual Comparison
67
+
68
+ | Product | tags | `contains ['featured']` | `containedBy ['a','b','featured']` | `overlaps ['sale','premium']` |
69
+ |---------|------|------------------------|-----------------------------------|------------------------------|
70
+ | A | `['featured', 'sale']` | Yes | No (has 'sale') | Yes (has 'sale') |
71
+ | B | `['featured']` | Yes | Yes | No |
72
+ | C | `['a', 'b']` | No | Yes | No |
73
+ | D | `['premium']` | No | No | Yes (has 'premium') |
74
+ | E | `[]` | No | Yes (empty subset) | No |
75
+
76
+
77
+ ## Decision Guide
78
+
79
+ | Question | Use |
80
+ |----------|-----|
81
+ | "Must have ALL these tags" | `contains` |
82
+ | "Tags must only be from this list" | `containedBy` |
83
+ | "Must have AT LEAST ONE of these tags" | `overlaps` |
84
+
85
+
86
+ ## Empty Array Behavior
87
+
88
+ | Operator | Empty Value `[]` | Behavior |
89
+ |----------|------------------|----------|
90
+ | `contains: []` | Returns **ALL** rows | Everything contains empty set |
91
+ | `containedBy: []` | Returns only rows with **empty arrays** | Only `[]` is subset of `[]` |
92
+ | `overlaps: []` | Returns **NO** rows | Nothing overlaps with empty |
93
+
94
+
95
+ ## Type Handling
96
+
97
+ **String Arrays** (`varchar[]`, `text[]`, `char[]`):
98
+ ```typescript
99
+ { where: { tags: { contains: ['a', 'b'] } } }
100
+ // SQL: "tags"::text[] @> ARRAY['a', 'b']::text[]
101
+ ```
102
+
103
+ **Numeric Arrays** (`integer[]`, `numeric[]`):
104
+ ```typescript
105
+ { where: { scores: { contains: [100, 200] } } }
106
+ // SQL: "scores" @> ARRAY[100, 200]
107
+ ```
108
+
109
+ **Boolean Arrays**:
110
+ ```typescript
111
+ { where: { flags: { contains: [true, false] } } }
112
+ // SQL: "flags" @> ARRAY[true, false]
113
+ ```
114
+
115
+
116
+ ## Defining Array Columns
117
+
118
+ In your Drizzle schema:
119
+
120
+ ```typescript
121
+ import { pgTable, text, varchar, integer } from 'drizzle-orm/pg-core';
122
+
123
+ export const productTable = pgTable('Product', {
124
+ id: text('id').primaryKey(),
125
+ name: text('name').notNull(),
126
+
127
+ // Array columns
128
+ tags: varchar('tags', { length: 100 }).array(), // varchar(100)[]
129
+ categories: text('categories').array(), // text[]
130
+ scores: integer('scores').array(), // integer[]
131
+ });
132
+ ```