@venizia/ignis-docs 0.2.1-0 → 0.2.1-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 (145) hide show
  1. package/content/best-practices/architectural-patterns.md +3 -3
  2. package/content/best-practices/code-style-standards/naming-conventions.md +1 -1
  3. package/content/best-practices/contribution-workflow.md +2 -2
  4. package/content/best-practices/error-handling.md +94 -89
  5. package/content/best-practices/security-guidelines.md +5 -5
  6. package/content/extensions/components/api-reference.md +22 -21
  7. package/content/extensions/components/authentication/api.md +64 -28
  8. package/content/extensions/components/authentication/errors.md +19 -5
  9. package/content/extensions/components/authentication/index.md +19 -18
  10. package/content/extensions/components/authentication/usage.md +55 -30
  11. package/content/extensions/components/authorization/api.md +351 -84
  12. package/content/extensions/components/authorization/errors.md +51 -17
  13. package/content/extensions/components/authorization/getting-started.md +227 -0
  14. package/content/extensions/components/authorization/index.md +24 -16
  15. package/content/extensions/components/authorization/usage.md +45 -21
  16. package/content/extensions/components/health-check.md +15 -9
  17. package/content/extensions/components/index.md +24 -90
  18. package/content/extensions/components/mail/api.md +105 -54
  19. package/content/extensions/components/mail/errors.md +12 -10
  20. package/content/extensions/components/mail/index.md +32 -13
  21. package/content/extensions/components/mail/usage.md +26 -18
  22. package/content/extensions/components/request-tracker.md +18 -14
  23. package/content/extensions/components/socket-io/api.md +377 -882
  24. package/content/extensions/components/socket-io/errors.md +49 -51
  25. package/content/extensions/components/socket-io/index.md +72 -88
  26. package/content/extensions/components/socket-io/usage.md +107 -117
  27. package/content/extensions/components/static-asset/api.md +83 -31
  28. package/content/extensions/components/static-asset/errors.md +18 -7
  29. package/content/extensions/components/static-asset/index.md +18 -11
  30. package/content/extensions/components/static-asset/usage.md +11 -6
  31. package/content/extensions/components/template/index.md +3 -3
  32. package/content/extensions/components/websocket/api.md +58 -27
  33. package/content/extensions/components/websocket/errors.md +3 -3
  34. package/content/extensions/components/websocket/index.md +8 -7
  35. package/content/extensions/components/websocket/usage.md +21 -8
  36. package/content/extensions/helpers/cron/index.md +8 -7
  37. package/content/extensions/helpers/crypto/index.md +16 -8
  38. package/content/extensions/helpers/crypto/reference.md +96 -24
  39. package/content/extensions/helpers/env/index.md +14 -10
  40. package/content/extensions/helpers/error/index.md +99 -23
  41. package/content/extensions/helpers/index.md +61 -47
  42. package/content/extensions/helpers/inversion/index.md +23 -6
  43. package/content/extensions/helpers/inversion/reference.md +30 -22
  44. package/content/extensions/helpers/kafka/admin.md +3 -2
  45. package/content/extensions/helpers/kafka/compile-binary.md +69 -44
  46. package/content/extensions/helpers/kafka/consumer.md +24 -21
  47. package/content/extensions/helpers/kafka/examples.md +22 -234
  48. package/content/extensions/helpers/kafka/index.md +30 -62
  49. package/content/extensions/helpers/kafka/producer.md +31 -26
  50. package/content/extensions/helpers/kafka/schema-registry.md +19 -14
  51. package/content/extensions/helpers/logger/hf-logger.md +49 -22
  52. package/content/extensions/helpers/logger/index.md +37 -12
  53. package/content/extensions/helpers/logger/pino.md +31 -11
  54. package/content/extensions/helpers/logger/reference.md +243 -52
  55. package/content/extensions/helpers/network/api.md +65 -30
  56. package/content/extensions/helpers/network/index.md +32 -11
  57. package/content/extensions/helpers/queue/index.md +17 -6
  58. package/content/extensions/helpers/queue/reference.md +52 -25
  59. package/content/extensions/helpers/redis/index.md +29 -11
  60. package/content/extensions/helpers/redis/reference.md +85 -28
  61. package/content/extensions/helpers/secrets/index.md +82 -12
  62. package/content/extensions/helpers/socket-io/api.md +40 -21
  63. package/content/extensions/helpers/socket-io/index.md +26 -10
  64. package/content/extensions/helpers/storage/api.md +42 -24
  65. package/content/extensions/helpers/storage/index.md +10 -7
  66. package/content/extensions/helpers/types/index.md +20 -7
  67. package/content/extensions/helpers/types/reference.md +53 -14
  68. package/content/extensions/helpers/uid/index.md +166 -10
  69. package/content/extensions/helpers/websocket/api.md +52 -13
  70. package/content/extensions/helpers/websocket/index.md +7 -4
  71. package/content/extensions/helpers/worker-thread/index.md +9 -5
  72. package/content/extensions/helpers/worker-thread/reference.md +20 -20
  73. package/content/extensions/index.md +38 -39
  74. package/content/extensions/src-details/mcp-server.md +96 -548
  75. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  76. package/content/guides/core-concepts/persistent/index.md +5 -1
  77. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  78. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  79. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  80. package/content/guides/core-concepts/persistent/search-typesense.md +26 -14
  81. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  82. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  83. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  84. package/content/guides/get-started/philosophy.md +135 -670
  85. package/content/guides/get-started/setup.md +53 -74
  86. package/content/guides/migrations/redis-helpers-migration.md +4 -3
  87. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  88. package/content/guides/migrations/unified-connectors-migration.md +7 -6
  89. package/content/guides/tutorials/realtime-chat.md +1 -1
  90. package/content/references/base/application.md +63 -21
  91. package/content/references/base/components.md +3 -3
  92. package/content/references/base/connectors.md +14 -14
  93. package/content/references/base/controllers.md +12 -12
  94. package/content/references/base/datasources-reference.md +32 -31
  95. package/content/references/base/datasources.md +6 -6
  96. package/content/references/base/dependency-injection.md +12 -11
  97. package/content/references/base/filter-system/application-usage.md +54 -30
  98. package/content/references/base/filter-system/array-operators.md +24 -46
  99. package/content/references/base/filter-system/comparison-operators.md +47 -67
  100. package/content/references/base/filter-system/default-filter.md +59 -53
  101. package/content/references/base/filter-system/fields-order-pagination.md +92 -146
  102. package/content/references/base/filter-system/index.md +25 -13
  103. package/content/references/base/filter-system/json-filtering.md +45 -184
  104. package/content/references/base/filter-system/list-operators.md +23 -53
  105. package/content/references/base/filter-system/logical-operators.md +63 -121
  106. package/content/references/base/filter-system/null-operators.md +34 -104
  107. package/content/references/base/filter-system/pattern-matching.md +40 -55
  108. package/content/references/base/filter-system/quick-reference.md +86 -198
  109. package/content/references/base/filter-system/range-operators.md +18 -46
  110. package/content/references/base/filter-system/tips.md +6 -6
  111. package/content/references/base/filter-system/use-cases.md +33 -15
  112. package/content/references/base/grpc-controllers.md +53 -17
  113. package/content/references/base/index.md +5 -3
  114. package/content/references/base/middlewares.md +11 -10
  115. package/content/references/base/models-reference.md +17 -17
  116. package/content/references/base/models.md +4 -3
  117. package/content/references/base/providers.md +8 -8
  118. package/content/references/base/repositories/advanced.md +224 -326
  119. package/content/references/base/repositories/index.md +19 -6
  120. package/content/references/base/repositories/mixins.md +5 -5
  121. package/content/references/base/repositories/relations.md +160 -293
  122. package/content/references/base/repositories/soft-deletable.md +16 -6
  123. package/content/references/base/secrets.md +17 -13
  124. package/content/references/base/services.md +6 -4
  125. package/content/references/configuration/environment-variables.md +31 -23
  126. package/content/references/configuration/index.md +6 -4
  127. package/content/references/index.md +1 -1
  128. package/content/references/utilities/duration.md +85 -0
  129. package/content/references/utilities/index.md +5 -1
  130. package/content/references/utilities/jsx-reference.md +11 -11
  131. package/content/references/utilities/jsx.md +2 -2
  132. package/content/references/utilities/module.md +78 -25
  133. package/content/references/utilities/request.md +2 -1
  134. package/content/references/utilities/retry.md +139 -0
  135. package/content/references/utilities/schema.md +2 -2
  136. package/content/references/utilities/statuses-reference.md +4 -4
  137. package/content/references/utilities/statuses.md +5 -5
  138. package/dist/mcp-server/index.js +0 -0
  139. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  140. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  141. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  142. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  143. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  144. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  145. package/package.json +17 -16
@@ -10,12 +10,12 @@ Exhaustive reference for `IDataSource`, `AbstractDataSource`, the PostgreSQL con
10
10
 
11
11
  **Files:**
12
12
 
13
- - [`packages/core/src/base/datasources/abstract.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/datasources/abstract.ts) - neutral `AbstractDataSource`
14
- - [`packages/core/src/base/datasources/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/datasources/common/types.ts) - `IDataSource`, `DataSourceDrivers`, neutral transaction types
15
- - [`packages/core/src/connectors/postgres/datasources/abstract.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/datasources/abstract.ts) - `AbstractPostgresDataSource`
16
- - [`packages/core/src/connectors/postgres/datasources/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/datasources/base.ts) - `BasePostgresDataSource`
17
- - [`packages/core/src/connectors/postgres/datasources/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/datasources/common/types.ts) - PostgreSQL connector types, `IsolationLevels`
18
- - [`packages/core/src/connectors/postgres/drivers`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/drivers) - `IRelationalDriver`, `NodePostgresDriver`, `PostgresJsDriver`
13
+ - [`packages/core-server/src/base/datasources/abstract.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/datasources/abstract.ts) - neutral `AbstractDataSource`
14
+ - [`packages/core-server/src/base/datasources/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/datasources/common/types.ts) - `IDataSource`, `DataSourceDrivers`, neutral transaction types
15
+ - [`packages/core-server/src/connectors/postgres/datasources/abstract.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/datasources/abstract.ts) - `AbstractPostgresDataSource`
16
+ - [`packages/core-server/src/connectors/postgres/datasources/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/datasources/base.ts) - `BasePostgresDataSource`
17
+ - [`packages/core-server/src/connectors/postgres/datasources/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/datasources/common/types.ts) - PostgreSQL connector types, `IsolationLevels`
18
+ - [`packages/core-server/src/connectors/postgres/drivers`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/drivers) - `IRelationalDriver`, `NodePostgresDriver`, `PostgresJsDriver`
19
19
 
20
20
  > [!IMPORTANT] Base vs. connectors
21
21
  > - **Split.** Engine-neutral root at `src/base/datasources/`; per-engine connectors at `src/connectors/{postgres,typesense}/datasources/`.
@@ -39,7 +39,7 @@ Exhaustive reference for `IDataSource`, `AbstractDataSource`, the PostgreSQL con
39
39
 
40
40
  Engine-neutral contract implemented by every datasource in the framework, regardless of engine.
41
41
 
42
- `Source ->` [`packages/core/src/base/datasources/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/datasources/common/types.ts)
42
+ `Source ->` [`packages/core-server/src/base/datasources/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/datasources/common/types.ts)
43
43
 
44
44
  ```typescript
45
45
  interface IDataSource<
@@ -82,7 +82,7 @@ interface IDataSource<
82
82
 
83
83
  Top-level abstract class extended by every engine. Extends `BaseHelper` for scoped logging. Contains **no SQL, no Drizzle, and no connection-pool members** - those are added by each connector.
84
84
 
85
- `Source ->` [`packages/core/src/base/datasources/abstract.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/datasources/abstract.ts)
85
+ `Source ->` [`packages/core-server/src/base/datasources/abstract.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/datasources/abstract.ts)
86
86
 
87
87
  ```typescript
88
88
  abstract class AbstractDataSource<
@@ -104,7 +104,7 @@ abstract class AbstractDataSource<
104
104
 
105
105
  | Method | Return type | Description |
106
106
  |---|---|---|
107
- | `configure(opts?)` | `ValueOrPromise<void>` | Initialize the underlying connection. This is the **only** abstract member of the neutral root - `getConnectionString()` is not part of it; it is declared one level down, on `AbstractPostgresDataSource` (see below) |
107
+ | `configure(opts?)` | `ValueOrPromise<void>` | Initialize the underlying connection. This is the **only** abstract member of the neutral root. `getConnectionString()` is not part of it - it is declared one level down, on `AbstractPostgresDataSource` (see below) |
108
108
 
109
109
  **Concrete methods** (defaults, overridable by connectors):
110
110
 
@@ -123,7 +123,7 @@ abstract class AbstractDataSource<
123
123
  | `discoverDefinitions({ read, kind })` | Walks the bound model classes, reads a connector-specific artifact via `read`, and returns a name-keyed registry. Skips undefined reads, throws on duplicate names, honors `autoDiscovery: false`. Shared plumbing every connector's own `discoverSchema()`-equivalent builds on |
124
124
 
125
125
  > [!NOTE] NotSupported convention
126
- > Every capability an engine does not implement - transactions, row-level locking - uses the same `throwNotSupported` utility ([`packages/core/src/utilities/error.utility.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/utilities/error.utility.ts)), producing a consistent `501 Not Implemented` whose `normalized.code` resolves to `'core.not_supported'`. This is how the typesense connector signals "not applicable to this engine" instead of silently no-op-ing.
126
+ > Every capability an engine does not implement - transactions, row-level locking - uses the same `throwNotSupported` utility ([`packages/core-server/src/utilities/error.utility.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/utilities/error.utility.ts)). It produces a consistent `501 Not Implemented` whose `normalized.code` resolves to `'core.not_supported'`. This is how the typesense connector signals "not applicable to this engine" instead of silently no-op-ing.
127
127
 
128
128
  ### `IDataSourceCapabilities`
129
129
 
@@ -137,7 +137,7 @@ Only `BasePostgresDataSource` overrides `getCapabilities()` to return `{ transac
137
137
 
138
138
  ## PostgreSQL connector: `AbstractPostgresDataSource` and `BasePostgresDataSource`
139
139
 
140
- `Source ->` [`packages/core/src/connectors/postgres/datasources/abstract.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/datasources/abstract.ts), [`packages/core/src/connectors/postgres/datasources/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/datasources/base.ts)
140
+ `Source ->` [`packages/core-server/src/connectors/postgres/datasources/abstract.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/datasources/abstract.ts), [`packages/core-server/src/connectors/postgres/datasources/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/datasources/base.ts)
141
141
 
142
142
  ### `AbstractPostgresDataSource`
143
143
 
@@ -176,20 +176,20 @@ The fourth generic, `Client = Pool`, is what lets a postgres-js datasource decla
176
176
  |---|---|---|
177
177
  | `getConnector()` | `TRelationalConnector<Schema>` | Wires the driver on first use (via `wireDriverFromMetadata()`), then returns `this.connector` |
178
178
  | `getClient()` | `Client` | Raw driver client escape hatch - `pg.Pool` for node-postgres, `Sql` for postgres-js. Reads `this.driver.getClient()` if a driver is resolved, else `this.client` directly. Throws if neither is set |
179
- | `getQueryDialect()` | `IRelationalQueryDialect` | Returns a shared, lazily-constructed `FilterBuilder` instance (static, one per process) |
180
- | `onSecretRotated(opts)` | `Promise<void>` | Applies rotated credentials to `this.settings`, rebuilds the driver/connector/client against a fresh pool, calls `this.configure()` and `this.resolveDriver()`, then drains the old pool once the new one is in place. See [Secrets & Vault](/guides/core-concepts/secrets-vault) |
179
+ | `getQueryDialect()` | `IRelationalQueryDialect` | Returns a shared, lazily-constructed `PostgresQueryDialect` instance (static, one per process) |
180
+ | `onSecretRotated(opts)` | `Promise<void>` | Applies rotated credentials to `this.settings` and rebuilds the driver/connector/client against a fresh pool. Calls `this.configure()` and `this.resolveDriver()`, then drains the old pool once the new one is in place. See [Secrets & Vault](/guides/core-concepts/secrets-vault) |
181
181
 
182
182
  **Protected methods:**
183
183
 
184
184
  | Method | Description |
185
185
  |---|---|
186
- | `wireDriverFromMetadata()` | Idempotent, lazy. If `this.connector` already exists, no-ops. If `this.driver` exists but `this.connector` does not, builds the connector from it. Otherwise reads the class named in `@datasource({ driver })` from `MetadataRegistry`, instantiates it over `this.client`, and calls `useDriver()`. Throws if neither `client` nor `driver` is set, or if the named `driver` metadata is not a class (a string, historically valid for search engines, is rejected here with a message pointing at `NodePostgresDriver`) |
186
+ | `wireDriverFromMetadata()` | Idempotent, lazy. If `this.connector` already exists, no-ops. If `this.driver` exists but `this.connector` does not, builds the connector from it. Otherwise reads the class named in `@datasource({ driver })` from `MetadataRegistry`, instantiates it over `this.client`, and calls `useDriver()`. Throws if neither `client` nor `driver` is set. It also throws if the named `driver` metadata is not a class. A string, historically valid for search engines, is rejected here with a message pointing at `NodePostgresDriver` |
187
187
  | `resolveDriver()` | Calls `wireDriverFromMetadata()`, then returns `this.driver` |
188
188
  | `useDriver({ driver, schema? })` | Assigns `this.driver` **and** builds `this.connector` from it in one step - the two-step form (driver set, connector forgotten) is unrepresentable. `schema` defaults to `getSchema()`. The public escape hatch for a custom or third-party driver, bypassing `@datasource({ driver })` entirely |
189
189
  | `mapSecretToSettings({ secret })` | Maps Vault's `{ username, password }` secret shape to `pg`'s `{ user, password }` settings shape, for `onSecretRotated()` |
190
190
 
191
191
  > [!NOTE] Driver seam
192
- > - **Class, not a string.** `@datasource({ driver })` names the driver **class** (`NodePostgresDriver` or `PostgresJsDriver`) - a driver-name string cannot carry `pg`/`postgres` into the app's bundle, only a real class reference can.
192
+ > - **Class, not a string.** `@datasource({ driver })` names the driver **class** (`NodePostgresDriver` or `PostgresJsDriver`). A driver-name string cannot carry `pg`/`postgres` into the app's bundle - only a real class reference can.
193
193
  > - **`configure()` only assigns `this.client`.** The protected `wireDriverFromMetadata()` (called internally by `getConnector()`/`resolveDriver()`) instantiates the named class over it and builds `this.connector`, lazily and idempotently.
194
194
  > - **Where the drivers live.** `pg` and `postgres` are both optional peer dependencies; concrete drivers live at `@venizia/ignis/postgres/node-postgres` and `@venizia/ignis/postgres/postgres-js`, and Supabase support at `@venizia/ignis/postgres/supabase`. See [Postgres Drivers & Supabase](/guides/core-concepts/persistent/postgres-drivers).
195
195
 
@@ -197,7 +197,7 @@ The fourth generic, `Client = Pool`, is what lets a postgres-js datasource decla
197
197
 
198
198
  Extends `AbstractPostgresDataSource` with a constructor, **schema auto-discovery**, and a real `beginTransaction()` implementation backed by the connection pool. Internally named `BaseRelationalDataSource`.
199
199
 
200
- `Source ->` [`packages/core/src/connectors/postgres/datasources/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/datasources/base.ts)
200
+ `Source ->` [`packages/core-server/src/connectors/postgres/datasources/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/datasources/base.ts)
201
201
 
202
202
  ```typescript
203
203
  abstract class BaseRelationalDataSource<
@@ -246,7 +246,7 @@ This means tables and relations never need to be manually merged in the datasour
246
246
 
247
247
  1. **Your DataSource's `constructor` runs.** You call `super()` with `name` and `config`. Schema is auto-discovered from `@repository` bindings unless `schema` is provided manually.
248
248
  2. **`Application.registerDataSources()` runs during startup.** The application fetches your datasource instance from the DI container and calls `configure()` on it.
249
- 3. **Your `configure()` method runs.** Its only job is to create the raw client and assign it to `this.client` - `getConnector()`/`beginTransaction()` lazily instantiate the class named in `@datasource({ driver })` over it and build the Drizzle connector. `configure()` never touches `this.connector` directly.
249
+ 3. **Your `configure()` method runs.** Its only job is to create the raw client and assign it to `this.client`. `getConnector()`/`beginTransaction()` lazily instantiate the class named in `@datasource({ driver })` over it and build the Drizzle connector. `configure()` never touches `this.connector` directly.
250
250
 
251
251
  ### Example implementations
252
252
 
@@ -362,11 +362,11 @@ export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfig
362
362
  ```
363
363
 
364
364
  > [!IMPORTANT]
365
- > `configure()` must leave the datasource with a way to reach the database: either assign the raw client to `this.client` (paired with naming the driver class in `@datasource({ driver })`), or wire a driver directly with `this.useDriver({ driver })` for a custom or third-party driver. `getConnector()`/`beginTransaction()` resolve the driver lazily from whichever you provided. With neither, `wireDriverFromMetadata()` throws `No driver and no client`.
365
+ > `configure()` must leave the datasource with a way to reach the database. Either assign the raw client to `this.client`, paired with naming the driver class in `@datasource({ driver })`. Or wire a driver directly with `this.useDriver({ driver })` for a custom or third-party driver. `getConnector()`/`beginTransaction()` resolve the driver lazily from whichever you provided. With neither, `wireDriverFromMetadata()` throws `No driver and no client`.
366
366
 
367
367
  ### `@datasource` decorator
368
368
 
369
- `Source ->` [`packages/core/src/base/metadata/persistents.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/metadata/persistents.ts)
369
+ `Source ->` [`packages/core-server/src/base/metadata/persistents.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/metadata/persistents.ts)
370
370
 
371
371
  ```typescript
372
372
  @datasource({
@@ -404,7 +404,7 @@ export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfig
404
404
 
405
405
  ## Driver interface
406
406
 
407
- `Source ->` [`packages/core/src/connectors/postgres/drivers`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/drivers)
407
+ `Source ->` [`packages/core-server/src/connectors/postgres/drivers`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/drivers)
408
408
 
409
409
  `IRelationalDriver` owns connection acquisition and the raw control statements (`BEGIN`/`COMMIT`/`ROLLBACK`) - the only two places the connector is hard-wired to a specific client library.
410
410
 
@@ -434,7 +434,7 @@ interface IStatementResult {
434
434
  | `getClient()` | Raw client escape - `pg.Pool` for node-postgres, `Sql` for postgres-js |
435
435
  | `end()` | Closes the underlying client/pool |
436
436
  | `IRelationalConnection.execute({ statement })` | Runs a control statement verbatim (never parameterized - `BEGIN TRANSACTION ISOLATION LEVEL $1` is not valid SQL) |
437
- | `IRelationalConnection.release({ destroy? })` | Returns the connection to the pool, or discards it when `destroy: true` - required after a failed `COMMIT`/`ROLLBACK`, since the session may still hold an open transaction |
437
+ | `IRelationalConnection.release({ destroy? })` | Returns the connection to the pool, or discards it when `destroy: true`. Required after a failed `COMMIT`/`ROLLBACK`, since the session may still hold an open transaction |
438
438
 
439
439
  Two concrete drivers ship today, both satisfying `IRelationalDriver` and both proven by the same conformance suite:
440
440
 
@@ -443,12 +443,12 @@ Two concrete drivers ship today, both satisfying `IRelationalDriver` and both pr
443
443
  | `NodePostgresDriver` | `pg` | Rejects a client without `connect()` **and** `totalCount` (pool accounting) - catches a bare `pg.Client` |
444
444
  | `PostgresJsDriver` | `postgres` | Rejects a client without `reserve()` **and** `unsafe()` - catches a `pg.Pool` passed to the wrong driver |
445
445
 
446
- - **Driver asymmetry, deliberate.** After a failed `COMMIT`, `pg` can destroy the poisoned connection (`release(err)`); postgres-js has no destroy semantics (`ReservedSql.release()` takes no argument), so it returns the connection to the pool regardless.
446
+ - **Driver asymmetry, deliberate.** After a failed `COMMIT`, `pg` can destroy the poisoned connection (`release(err)`). postgres-js has no destroy semantics - `ReservedSql.release()` takes no argument - so it returns the connection to the pool regardless.
447
447
  - **Both drivers accept the same call.** `IRelationalConnection.release({ destroy: true })` is accepted by both drivers and honored by one. See [Postgres Drivers & Supabase](/guides/core-concepts/persistent/postgres-drivers) for the full driver comparison and Supabase's transaction-pooler requirements.
448
448
 
449
449
  ## Connector types
450
450
 
451
- `Source ->` [`packages/core/src/connectors/postgres/datasources/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/datasources/common/types.ts)
451
+ `Source ->` [`packages/core-server/src/connectors/postgres/datasources/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/datasources/common/types.ts)
452
452
 
453
453
  | Type | Description |
454
454
  |---|---|
@@ -460,7 +460,7 @@ Two concrete drivers ship today, both satisfying `IRelationalDriver` and both pr
460
460
 
461
461
  ### `DataSourceDrivers`
462
462
 
463
- `Source ->` [`packages/core/src/base/datasources/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/datasources/common/types.ts)
463
+ `Source ->` [`packages/core-server/src/base/datasources/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/datasources/common/types.ts)
464
464
 
465
465
  An identity-only const-class - the engine actually used is chosen by which driver **class** `@datasource({ driver })` names, not by this constant. `DataSourceDrivers` is read by nothing else in `src`; it never routes connector configuration.
466
466
 
@@ -484,7 +484,7 @@ DataSourceDrivers.isValid('node-postgres') // true
484
484
  ```
485
485
 
486
486
  > [!NOTE]
487
- > `NODE_POSTGRES`/`POSTGRES_JS` remain valid `TDataSourceDriver` string values, but `@datasource({ driver })` on a **relational** datasource no longer accepts them - it takes the `NodePostgresDriver`/`PostgresJsDriver` class instead (see [Postgres Drivers & Supabase](/guides/core-concepts/persistent/postgres-drivers)). Search connectors (`TYPESENSE`, `MEILISEARCH`) still take the driver-name string form, because `extends TypesenseDataSource` already names the engine and is what carries the client into the bundle.
487
+ > `NODE_POSTGRES`/`POSTGRES_JS` remain valid `TDataSourceDriver` string values. But `@datasource({ driver })` on a **relational** datasource no longer accepts them - it takes the `NodePostgresDriver`/`PostgresJsDriver` class instead (see [Postgres Drivers & Supabase](/guides/core-concepts/persistent/postgres-drivers)). Search connectors (`TYPESENSE`, `MEILISEARCH`) still take the driver-name string form. `extends TypesenseDataSource` already names the engine, and that is what carries the client into the bundle.
488
488
 
489
489
  ## Transaction support
490
490
 
@@ -499,28 +499,29 @@ Only engines that declare `getCapabilities().transactions === true` implement re
499
499
  3. On a failed `BEGIN`, destroys the connection (`release({ destroy: true })`) and rethrows - it is never leaked back to the pool in an unknown state
500
500
  4. Returns an `IDatabaseTransaction` object exposing `isActive`, `commit()`, `rollback()`, and the connection-scoped `connector`
501
501
 
502
- - **Shared `finish()`.** `commit()`/`rollback()` share one internal `finish()` that flips `isActive` to `false` **before** issuing the statement, so a commit racing a rollback cannot double-release the same connection - then runs `COMMIT`/`ROLLBACK`.
502
+ - **Shared `finish()`.** `commit()`/`rollback()` share one internal `finish()`. It flips `isActive` to `false` **before** issuing the statement, so a commit racing a rollback cannot double-release the same connection, then runs `COMMIT`/`ROLLBACK`.
503
503
  - **Outcome handling.** On success the connection is released back to the pool; on failure it is destroyed and the error is rethrown.
504
504
 
505
505
  > [!WARNING] `commit()`/`rollback()` throw on failure
506
506
  > - **A failed `COMMIT` or `ROLLBACK` throws** - a failed `COMMIT` never resolves as success.
507
- > - **The poisoned connection is destroyed**, not returned to the pool, where the driver supports it: `node-postgres` can discard a connection; `postgres-js` has no destroy semantics and pools it anyway.
507
+ > - **The poisoned connection is destroyed**, not returned to the pool, where the driver supports it. `node-postgres` can discard a connection; `postgres-js` has no destroy semantics and pools it anyway.
508
508
  > - **Nest `rollback()` in its own `try...catch`.** It can throw and is normally called from a `catch`, so nesting keeps the rollback error from replacing the original cause.
509
- > - **Calling `rollback()` twice is safe.** After a transaction already ended by failure, `rollback()` is a silent no-op (already torn down), so the canonical `catch { await tx.rollback(); throw error; }` pattern always works. See [Transactions](/guides/core-concepts/persistent/transactions) and [Postgres Drivers & Supabase](/guides/core-concepts/persistent/postgres-drivers).
509
+ > - **Calling `rollback()` twice is safe.** After a transaction already ended by failure, `rollback()` is a silent no-op, because it's already torn down.
510
+ > - **The canonical shape.** `catch { await tx.rollback(); throw error; }` always works. See [Transactions](/guides/core-concepts/persistent/transactions) and [Postgres Drivers & Supabase](/guides/core-concepts/persistent/postgres-drivers).
510
511
 
511
512
  ### Neutral vs. PostgreSQL transaction types
512
513
 
513
514
  `src/base` declares the engine-neutral shape; the PostgreSQL connector narrows it with connection details.
514
515
 
515
516
  ```typescript
516
- // packages/core/src/base/datasources/common/types.ts - engine-neutral
517
+ // packages/core-server/src/base/datasources/common/types.ts - engine-neutral
517
518
  interface ITransaction {
518
519
  isActive: boolean;
519
520
  commit(): Promise<void>;
520
521
  rollback(): Promise<void>;
521
522
  }
522
523
 
523
- // packages/core/src/connectors/postgres/datasources/common/types.ts - PostgreSQL
524
+ // packages/core-server/src/connectors/postgres/datasources/common/types.ts - PostgreSQL
524
525
  interface IDatabaseTransaction<Schema extends TAnyDataSourceSchema = TAnyDataSourceSchema>
525
526
  extends ITransaction {
526
527
  connector: TRelationalConnector<Schema>;
@@ -585,7 +586,7 @@ try {
585
586
  > [!TIP]
586
587
  > For most use cases, prefer `repository.beginTransaction()`, which provides a higher-level API. See [Repositories](/references/base/repositories/#run-inside-a-transaction).
587
588
 
588
- This architecture ensures datasources are configured consistently and that the fully-initialized Drizzle connector, aware of all schemas and relations, is available to repositories for querying.
589
+ This architecture keeps datasource configuration consistent. The fully-initialized Drizzle connector, aware of all schemas and relations, is available to repositories for querying.
589
590
 
590
591
  ## See also
591
592
 
@@ -43,10 +43,10 @@ A `@repository` binds a model to `PostgresDataSource`, and the schema is auto-di
43
43
 
44
44
  ## How it works
45
45
 
46
- - **Driver is a class, not a string.** `@datasource({ driver })` names `NodePostgresDriver` or `PostgresJsDriver` as a class reference - never a driver-name string. Only a real class reference carries `pg`/`postgres` into the application's bundle, so both stay genuinely optional peer dependencies.
46
+ - **Driver is a class, not a string.** `@datasource({ driver })` names `NodePostgresDriver` or `PostgresJsDriver` as a class reference, never a driver-name string. Only a class reference carries `pg`/`postgres` into the bundle, keeping both packages optional.
47
47
  - **`configure()` has exactly one job.** Build the raw client and assign it to `this.client` (a `pg.Pool` for node-postgres, or a postgres-js `Sql`). It never touches `this.connector` directly.
48
- - **The driver wires lazily.** The first time `getConnector()` or `beginTransaction()` is called, the base class reads the class named in `@datasource({ driver })`, instantiates it over `this.client`, and builds `this.connector` from it.
49
- - **Base vs. connector split.** IGNIS splits datasources into an engine-neutral root (`AbstractDataSource` - no SQL, no Drizzle, no pool) and per-engine connectors (`BasePostgresDataSource` for PostgreSQL, a parallel class for typesense). See [Connectors](/references/base/connectors) for the full architecture.
48
+ - **The driver wires lazily.** The first time `getConnector()` or `beginTransaction()` is called, the base class reads the class named in `@datasource({ driver })`. It instantiates that class over `this.client` and builds `this.connector` from it.
49
+ - **Base vs. connector split.** IGNIS splits datasources into an engine-neutral root (`AbstractDataSource` - no SQL, no Drizzle, no pool) and per-engine connectors. `BasePostgresDataSource` is the PostgreSQL connector; typesense has a parallel class. See [Connectors](/references/base/connectors) for the full architecture.
50
50
  - **Naming.** The PostgreSQL connector's canonical class is `BaseRelationalDataSource`; `BasePostgresDataSource` and `BaseDataSource` are compatibility aliases re-exporting the same class.
51
51
 
52
52
  ## Common tasks
@@ -139,7 +139,7 @@ try {
139
139
 
140
140
  ### Share one datasource across repositories
141
141
 
142
- One `PostgresDataSource` instance is shared by every repository bound to it - `@repository` auto-injects the datasource, and `getSchema()` merges the tables and relations of every model bound to it.
142
+ One `PostgresDataSource` instance is shared by every repository bound to it. `@repository` auto-injects the datasource, and `getSchema()` merges the tables and relations of every model bound to it.
143
143
 
144
144
  ```typescript
145
145
  @repository({ model: User, dataSource: PostgresDataSource })
@@ -161,5 +161,5 @@ export class ConfigurationRepository extends DefaultCRUDRepository<typeof Config
161
161
 
162
162
  **Files:**
163
163
 
164
- - [`packages/core/src/base/datasources/abstract.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/datasources/abstract.ts) - neutral `AbstractDataSource`
165
- - [`packages/core/src/connectors/postgres/datasources/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/datasources/base.ts) - PostgreSQL `BasePostgresDataSource`
164
+ - [`packages/core-server/src/base/datasources/abstract.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/datasources/abstract.ts) - neutral `AbstractDataSource`
165
+ - [`packages/core-server/src/connectors/postgres/datasources/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/datasources/base.ts) - PostgreSQL `BasePostgresDataSource`
@@ -13,9 +13,9 @@ Technical reference for the DI system in IGNIS - managing resource lifecycles an
13
13
  - `packages/inversion/src/modules/registry/index.ts` - Base `MetadataRegistry`
14
14
  - `packages/inversion/src/modules/metadata/injectors.ts` - Base `@inject` decorator
15
15
  - `packages/inversion/src/common/types.ts` - `BindingScopes`, `BindingValueTypes`, `BindingKeys`, `IProvider`
16
- - `packages/core/src/helpers/inversion/container.ts` - Extended `Container` with `ApplicationLogger`
17
- - `packages/core/src/helpers/inversion/registry.ts` - Extended `MetadataRegistry` (singleton, with model/repository/datasource mixins)
18
- - `packages/core/src/base/metadata/injectors.ts` - Core `@inject` (wired to extended registry)
16
+ - `packages/core-server/src/helpers/inversion/container.ts` - Extended `Container` with `ApplicationLogger`
17
+ - `packages/core-server/src/helpers/inversion/registry.ts` - Extended `MetadataRegistry` (singleton, with model/repository/datasource mixins)
18
+ - `packages/core-server/src/base/metadata/injectors.ts` - Core `@inject` (wired to extended registry)
19
19
 
20
20
  ## Quick Reference
21
21
 
@@ -41,7 +41,7 @@ Before reading this document, you should understand:
41
41
 
42
42
  Heart of the DI system - registry managing all application resources.
43
43
 
44
- **File:** `packages/inversion/src/modules/container/index.ts` (Base) & `packages/core/src/helpers/inversion/container.ts` (Extended)
44
+ **File:** `packages/inversion/src/modules/container/index.ts` (Base) & `packages/core-server/src/helpers/inversion/container.ts` (Extended)
45
45
 
46
46
  The base `Container` extends `BaseHelper` (which provides `scope` and `identifier` properties). The core `Container` extends the base and adds a `Logger` instance.
47
47
 
@@ -73,7 +73,8 @@ const container = new Container({ scope: 'MyApp' }); // scope is optional, defau
73
73
 
74
74
  When `container.instantiate(MyClass)` is called:
75
75
 
76
- 1. **Constructor injection** - Reads `@inject` metadata from the class by parameter index (the `Reflect`-stored array is already index-keyed, so there is no sort step), resolves each dependency from the container, and passes them as constructor arguments. If any index in range has no `@inject` metadata, `instantiate()` throws immediately rather than passing `undefined`.
76
+ 1. **Constructor injection** - Reads `@inject` metadata from the class by parameter index. The `Reflect`-stored array is already index-keyed, so there is no sort step; the container resolves each dependency and passes them as constructor arguments.
77
+ - If any index in range has no `@inject` metadata, `instantiate()` throws immediately rather than passing `undefined`.
77
78
  2. **Property injection** - After the instance is created, reads property metadata, resolves each dependency, and assigns them directly to the instance properties.
78
79
 
79
80
  ```typescript
@@ -90,16 +91,16 @@ class UserController {
90
91
  ```
91
92
 
92
93
  > [!IMPORTANT]
93
- > **Every constructor parameter of a container-instantiated class must carry `@inject`.** Mixing decorated and undecorated parameters is forbidden - `@inject` stores its metadata at the parameter's index, so an undecorated parameter leaves a hole in that array, and there is no channel through which the container could supply it anyway (it would resolve to `undefined`). `instantiate()` refuses the shape by class name and parameter index instead of silently dereferencing the hole:
94
+ > **Every constructor parameter of a container-instantiated class must carry `@inject`.** Mixing decorated and undecorated parameters is forbidden. `@inject` stores its metadata at the parameter's index, so an undecorated parameter leaves a hole in that array. There is no channel through which the container could supply it anyway - it would resolve to `undefined`. `instantiate()` refuses the shape by class name and parameter index instead of silently dereferencing the hole:
94
95
  >
95
96
  > ```
96
97
  > [NoteController] Constructor parameter 0 has no @inject | Every parameter of a container-instantiated
97
98
  > class must be decorated - the container cannot supply an undecorated one
98
99
  > ```
99
100
  >
100
- > The check lives in `instantiate()`, not in the `@inject` decorator itself: parameter decorators run right-to-left, so when `@inject` on parameter 1 runs, parameter 0 has not been visited yet and nothing at that point can know whether it will end up decorated.
101
+ > The check lives in `instantiate()`, not in the `@inject` decorator itself. Parameter decorators run right-to-left, so when `@inject` on parameter 1 runs, parameter 0 has not been visited yet - nothing at that point can know whether it will end up decorated.
101
102
  >
102
- > This does not apply to `@repository`-decorated classes whose constructor appears undecorated at index 0 - the `@repository` decorator programmatically writes that inject metadata (`registry.setInjectMetadata({ target, index: 0, ... })`) even though no literal `@inject` appears in source. See [Repositories](./repositories/).
103
+ > This does not apply to `@repository`-decorated classes whose constructor appears undecorated at index 0. The `@repository` decorator programmatically writes that inject metadata (`registry.setInjectMetadata({ target, index: 0, ... })`), even though no literal `@inject` appears in source. See [Repositories](./repositories/).
103
104
 
104
105
  ## `Binding` Class
105
106
 
@@ -205,7 +206,7 @@ This is also used internally by `container.get()` and `container.getBinding()` w
205
206
 
206
207
  The `@inject` decorator marks where dependencies should be injected - either on constructor parameters or class properties.
207
208
 
208
- **File:** `packages/inversion/src/modules/metadata/injectors.ts` (base) & `packages/core/src/base/metadata/injectors.ts` (core wrapper)
209
+ **File:** `packages/inversion/src/modules/metadata/injectors.ts` (base) & `packages/core-server/src/base/metadata/injectors.ts` (core wrapper)
209
210
 
210
211
  ### Signature
211
212
 
@@ -261,7 +262,7 @@ import { inject } from '@venizia/ignis';
261
262
 
262
263
  ## Registering a Class
263
264
 
264
- No class decorator is needed to make a class injectable. A class becomes resolvable once a binding exists for it - created either by boot auto-discovery, by a framework helper (`app.controller()`, `app.service()`, `@repository`), or explicitly with `container.bind()`. Scope is configured on the binding, never on the class:
265
+ No class decorator is needed to make a class injectable. A class becomes resolvable once a binding exists for it. That binding can come from boot auto-discovery, from a framework helper (`app.controller()`, `app.service()`, `@repository`), or explicitly from `container.bind()`. Scope is configured on the binding, never on the class:
265
266
 
266
267
  ```typescript
267
268
  class UserService extends BaseService {
@@ -305,7 +306,7 @@ A singleton exported as `metadataRegistry`. Extends `BaseHelper`.
305
306
 
306
307
  ### Core MetadataRegistry
307
308
 
308
- **File:** `packages/core/src/helpers/inversion/registry.ts`
309
+ **File:** `packages/core-server/src/helpers/inversion/registry.ts`
309
310
 
310
311
  Extends the base with controller, repository, model, and datasource metadata support via mixins. Accessed as a singleton via `MetadataRegistry.getInstance()`.
311
312
 
@@ -6,7 +6,7 @@ difficulty: intermediate
6
6
 
7
7
  # Using Filters in Your Application
8
8
 
9
- A `filter` typically starts as a JSON-encoded query string on an HTTP request and ends as a Drizzle query - here is what happens at each layer in between.
9
+ A `filter` starts as a JSON-encoded query string on an HTTP request and ends as a Drizzle query. Here is where to hook into each layer in between.
10
10
 
11
11
  ```
12
12
  HTTP Request GET /products?filter={"where":{"status":"active"},"limit":10}
@@ -22,11 +22,9 @@ Repository Layer applyDefaultFilter() merges the @model default filter,
22
22
  FilterBuilder converts Filter -> Drizzle query options, executes
23
23
  ```
24
24
 
25
- ## Controller layer
25
+ ## Generate a CRUD controller from an entity
26
26
 
27
- ### `ControllerFactory` (recommended)
28
-
29
- `ControllerFactory.defineCrudController` generates a full CRUD controller, including filter parsing and validation, from an entity and a repository binding:
27
+ For a standard `GET`/`POST`/`PATCH`/`DELETE` resource, generate the controller instead of writing filter parsing by hand. `ControllerFactory.defineCrudController` builds it from an entity and a repository binding:
30
28
 
31
29
  ```typescript
32
30
  // src/controllers/product.controller.ts
@@ -65,7 +63,7 @@ export class ProductController extends _Controller {
65
63
  }
66
64
  ```
67
65
 
68
- **Filter-bearing endpoints generated:**
66
+ This generates every filter-bearing endpoint the resource needs:
69
67
 
70
68
  | Method | Endpoint | Query param |
71
69
  |---|---|---|
@@ -74,17 +72,34 @@ export class ProductController extends _Controller {
74
72
  | GET | `/products/find-one` | `filter` |
75
73
  | GET | `/products/count` | `where` |
76
74
 
77
- - **`isStrict.requestSchema`** controls whether these query params are Zod-required or optional.
78
- - **`isStrict.path`** controls trailing-slash strictness.
79
- - **Write endpoints** (`POST /`, `PATCH /{id}`, `DELETE /{id}`, ...) are also generated by the factory, and take no filter.
75
+ Set `isStrict.requestSchema: true` to make these query params Zod-required; set `isStrict.path: true` to reject trailing-slash variants of the route. Write endpoints (`POST /`, `PATCH /{id}`, `DELETE /{id}`, ...) come from the same factory call and take no filter.
76
+
77
+ ## Map a request query string to a parsed filter
78
+
79
+ `FilterSchema` and `WhereSchema` both accept a JSON string or a plain object, so the same schema validates a Hono query param (always a string) and a filter built in code:
80
80
 
81
- ### Custom controller with manual filter handling
81
+ | Request | Parsed filter |
82
+ |---|---|
83
+ | `GET /products?filter={"where":{"status":"active"}}` | `{ where: { status: 'active' } }` |
84
+ | `GET /products?filter={"limit":10,"skip":20}` | `{ limit: 10, skip: 20 }` |
85
+ | `GET /products?filter={"where":{"price":{"gte":100,"lte":500}},"order":["price ASC"]}` | `{ where: { price: { gte: 100, lte: 500 } }, order: ['price ASC'] }` |
86
+ | `GET /products/count?where={"role":"admin"}` | `{ role: 'admin' }` |
87
+
88
+ `WhereSchema` is independent of `FilterSchema` - it backs the `count` endpoint, which takes `where` directly rather than a full filter.
82
89
 
83
- For a route outside the generated CRUD set, accept `FilterSchema` directly:
90
+ ## Write a custom route that accepts a filter
91
+
92
+ For a route outside the generated CRUD set, use `FilterQuerySchema` - the same query shape the generated `find` route takes:
84
93
 
85
94
  ```typescript
86
95
  import { z } from '@hono/zod-openapi';
87
- import { BaseRestController, controller, FilterSchema, inject, jsonResponse } from '@venizia/ignis';
96
+ import {
97
+ BaseRestController,
98
+ controller,
99
+ FilterQuerySchema,
100
+ inject,
101
+ jsonResponse,
102
+ } from '@venizia/ignis';
88
103
 
89
104
  @controller({ path: '/products' })
90
105
  export class ProductController extends BaseRestController {
@@ -100,7 +115,7 @@ export class ProductController extends BaseRestController {
100
115
  configs: {
101
116
  path: '/search',
102
117
  method: 'get',
103
- request: { query: z.object({ filter: FilterSchema }) },
118
+ request: { query: FilterQuerySchema },
104
119
  responses: jsonResponse({ schema: z.array(z.object({ id: z.string() })) }),
105
120
  },
106
121
  handler: async context => {
@@ -113,23 +128,29 @@ export class ProductController extends BaseRestController {
113
128
  }
114
129
  ```
115
130
 
116
- ## Filter schema validation
131
+ ### The two query shapes
117
132
 
118
- `FilterSchema` and `WhereSchema` are both `z.union([<object shape>, <JSON string>.transform(JSON.parse)])` - they accept either format, so a Hono query param (always a string) and a filter built in code both validate:
133
+ Reach for these instead of rebuilding the same object at every route.
119
134
 
120
- ```typescript
121
- // Object format (built in code)
122
- { where: { status: 'active' }, limit: 10 }
135
+ | Schema | Query shape | Use it for |
136
+ |---|---|---|
137
+ | `FilterQuerySchema` | `{ filter?: TFilter }` | Any route that takes a full filter |
138
+ | `WhereQuerySchema` | `{ where?: TWhere }` | Any route that takes conditions and no pagination |
139
+
140
+ Both are plain Zod objects, so a route that takes more than one parameter extends rather than rebuilds:
123
141
 
124
- // JSON string format (from a URL query string)
125
- '{"where":{"status":"active"},"limit":10}'
142
+ ```typescript
143
+ request: { query: WhereQuerySchema.extend({ q: z.string().max(255).optional() }) },
126
144
  ```
127
145
 
128
- `WhereSchema` is independent of `FilterSchema` - it backs the `count` endpoint, which takes `where` directly rather than a full filter.
146
+ Neither needs an extra `.optional()`. `FilterSchema` already carries one, so `FilterSchema.optional()` is the same schema written longer, and a trailing `.partial()` on a single optional key does nothing.
129
147
 
130
- ## Service layer
148
+ > [!NOTE]
149
+ > The generated `updateBy` and `deleteBy` routes deliberately do not use `WhereQuerySchema`. They require `where`, because a missing one rewrites or deletes every row in the table.
131
150
 
132
- A service can rewrite the filter before it reaches the repository - useful when a constraint is a caller/session concern rather than a per-model constant (see [Default Filter](./default-filter) for the model-level alternative):
151
+ ## Rewrite a filter before it reaches the repository
152
+
153
+ Add a service between the controller and the repository when a constraint is a caller or session concern rather than a per-model constant - a tenant ID pulled from the request context, for example. Use [Default Filter](./default-filter) instead when the constraint applies to every caller of the model:
133
154
 
134
155
  ```typescript
135
156
  @service()
@@ -147,7 +168,9 @@ export class ProductService {
147
168
  }
148
169
  ```
149
170
 
150
- ## HTTP request examples
171
+ ## Build a filter query string from a client
172
+
173
+ Encode the filter as JSON and pass it as the `filter` query param, whichever HTTP client you use:
151
174
 
152
175
  **cURL:**
153
176
  ```bash
@@ -166,9 +189,9 @@ const response = await fetch(`/api/products?filter=${encodeURIComponent(JSON.str
166
189
  const response = await axios.get('/api/products', { params: { filter: JSON.stringify(filter) } });
167
190
  ```
168
191
 
169
- ## Debugging a filter
192
+ ## Debug what a filter compiles to
170
193
 
171
- `buildQuery` compiles a filter into Drizzle query options without executing it - the fastest way to check what a filter actually resolves to:
194
+ Call `buildQuery` to compile a filter into Drizzle query options without executing it - the fastest way to check what a filter actually resolves to:
172
195
 
173
196
  ```typescript
174
197
  const queryOptions = repository.buildQuery({ filter: complexFilter });
@@ -180,12 +203,13 @@ console.log('Generated query options:', queryOptions);
180
203
  ## See also
181
204
 
182
205
  - [Filter System Overview](./) - the `filter` shape and every `where` operator family
183
- - [Default Filter](./default-filter) - `shouldSkipDefaultFilter`, and the model-level alternative to the service-layer pattern above
206
+ - [Default Filter](./default-filter) - `shouldSkipDefaultFilter`, and the model-level alternative to the service-layer rewrite above
184
207
  - [Use Case Gallery](./use-cases) - more filter shapes with their generated SQL
185
208
  - [Advanced Repository Features](../repositories/advanced.md) - transactions, locking, and the full `log`/`lock` options
186
209
 
187
210
  **Files:**
188
211
 
189
- - [`packages/core/src/base/controllers/factory/controller.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/controllers/factory/controller.ts) - `ControllerFactory.defineCrudController`
190
- - [`packages/core/src/base/repositories/query-schemas/filter.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/repositories/query-schemas/filter.ts) - `FilterSchema`, `TFilter`, `TInclusion`
191
- - [`packages/core/src/connectors/postgres/repositories/core/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/core/base.ts) - `RelationalBaseRepository.buildQuery`
212
+ - [`packages/kernel/src/base/controllers/factory/controller.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/kernel/src/base/controllers/factory/controller.ts) - `ControllerFactory.defineCrudController`
213
+ - [`packages/kernel/src/base/repositories/query-schemas/index.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/kernel/src/base/repositories/query-schemas/index.ts) - `FilterSchema`, `WhereSchema`, `FilterQuerySchema`, `WhereQuerySchema`
214
+ - [`packages/filter/src/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/filter/src/common/types.ts) - `TFilter`, `TInclusion`
215
+ - [`packages/connectors/src/relational/core/repositories/core/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/connectors/src/relational/core/repositories/core/base.ts) - `RelationalBaseRepository.buildQuery`