@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
@@ -0,0 +1,296 @@
1
+ # SQLite
2
+
3
+ IGNIS ships a **SQLite connector** at `@venizia/ignis/sqlite`, driven by libsql. It is the second engine on the engine-neutral relational tier, so `@model`, `@repository`, `@datasource`, filters, transactions and soft delete all mean the same thing they mean on Postgres.
4
+
5
+ They do not all *do* the same thing. Read this table first - it is what a Postgres user needs before anything else.
6
+
7
+ | Capability | PostgreSQL | SQLite | What happens |
8
+ | :--- | :--- | :--- | :--- |
9
+ | `ilike` | native `ILIKE` | none | Maps onto `LIKE`, which already folds ASCII case |
10
+ | `like` | case-**sensitive** | case-**insensitive** | Silently matches more rows than on Postgres |
11
+ | `regexp`, `iregexp` | native | none | Throws `501` `core.not_supported` |
12
+ | `contains`, `containedBy`, `overlaps` | array operators | no array type | Throws `501` `core.not_supported` |
13
+ | `lock` (`SELECT ... FOR UPDATE`) | row locks | file locks only | Throws `501` `core.not_supported` |
14
+ | Isolation levels | three (`IsolationLevels`) | none | `isolationLevel` throws; pass `beginMode` instead |
15
+ | Storage classes | ~28 types | five | `jsonb` -> json-mode `text`, `bytea` -> `blob`, `boolean` -> 0/1 `integer` |
16
+ | JSON paths | `col #>> '{a,b}'` | `json_extract(col, '$."a"."b"')` | Same filter syntax, different SQL |
17
+ | Timestamps | `timestamptz` | no date type | ISO 8601 UTC strings in a `text` column |
18
+ | NULL sort order | NULL sorts **high** | NULL sorts **low** | `order: ['score ASC']` puts NULLs last on Postgres, first here |
19
+
20
+ Every refusal throws `501 Not Implemented` with `normalized.code: 'core.not_supported'`. None of them silently emits different SQL.
21
+
22
+ The two divergences that do not throw are the dangerous ones. `like` widens, and `nlike` narrows - on SQLite `{ nlike: 'alpha' }` **drops** the row holding `Alpha`. NULL ordering inverts. Both are pinned per engine by the conformance suite.
23
+
24
+ > [!IMPORTANT] Optional peer dependency
25
+ > `@libsql/client` is an optional peer, and the connector never reaches the `@venizia/ignis` root barrel. Import from the sub-path.
26
+ >
27
+ > ```bash
28
+ > bun add @libsql/client
29
+ > ```
30
+
31
+ ## What does work
32
+
33
+ - **Every CRUD verb**, including `.returning()` on insert, update and delete. MySQL cannot do that, so nothing here is emulated with a read-then-write.
34
+ - **Real transactions**, with `commit()`, `rollback()` and the same transaction object your services already pass around.
35
+ - **The whole filter vocabulary** minus the four operators above - `and`/`or`, `between`, `inq`, `nin`, `gt`, `like`, null handling, nested logical groups.
36
+ - **JSON path filtering and JSON path updates**, through `json_extract` and `json_set`.
37
+ - **Relations, hidden properties, default filters, soft delete, `count`, `skip`/`limit`.**
38
+ - **One driver for four deployments**: `:memory:`, a local file, a remote Turso database and an embedded replica.
39
+
40
+ ## Wiring the datasource
41
+
42
+ ```typescript
43
+ import { createClient } from '@libsql/client';
44
+ import type { Client } from '@libsql/client';
45
+ import { datasource } from '@venizia/ignis';
46
+ import type { TAnyDataSourceSchema, ValueOrPromise } from '@venizia/ignis';
47
+ import { BaseSqliteDataSource } from '@venizia/ignis/sqlite';
48
+ import type { ISqliteDataSourceSettings } from '@venizia/ignis/sqlite';
49
+ import { LibSqlDriver } from '@venizia/ignis/sqlite/libsql';
50
+
51
+ @datasource({ driver: LibSqlDriver })
52
+ export class SqliteDataSource extends BaseSqliteDataSource<
53
+ ISqliteDataSourceSettings,
54
+ TAnyDataSourceSchema,
55
+ {},
56
+ Client
57
+ > {
58
+ constructor() {
59
+ super({
60
+ name: SqliteDataSource.name,
61
+ config: { url: process.env.APP_ENV_SQLITE_URL ?? 'file:./data.db' },
62
+ });
63
+ }
64
+
65
+ override configure(): ValueOrPromise<void> {
66
+ this.client = createClient(this.settings);
67
+ }
68
+ }
69
+ ```
70
+
71
+ There is no `getConnectionString()` override. SQLite inherits one that returns `settings.url` - the libsql URL **is** the connection string. Postgres leaves the method abstract because no framework code can guess a `postgresql://` URL.
72
+
73
+ | Setting | Type | Meaning |
74
+ | :--- | :--- | :--- |
75
+ | `url` | `string` | `:memory:`, `file:./data.db`, `libsql://<host>`, or an embedded-replica URL |
76
+ | `authToken` | `string` | Turso credential; omit for a local file |
77
+
78
+ ## Models
79
+
80
+ Same decorators, `sqliteTable` instead of `pgTable`, and the enrichers come from `@venizia/ignis/sqlite`:
81
+
82
+ ```typescript
83
+ import { model } from '@venizia/ignis';
84
+ import {
85
+ BaseSqliteEntity,
86
+ generateIdColumnDefs,
87
+ generateTzColumnDefs,
88
+ } from '@venizia/ignis/sqlite';
89
+ import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
90
+
91
+ @model({ type: 'entity' })
92
+ export class Product extends BaseSqliteEntity<typeof Product.schema> {
93
+ static override schema = sqliteTable('Product', {
94
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }),
95
+ ...generateTzColumnDefs(),
96
+ name: text('name').notNull(),
97
+ isActive: integer('is_active', { mode: 'boolean' }).notNull().default(true),
98
+ metadata: text('metadata', { mode: 'json' }).$type<Record<string, unknown>>(),
99
+ });
100
+ }
101
+ ```
102
+
103
+ Three column choices differ from the Postgres twin:
104
+
105
+ - **JSON is a json-mode `text` column.** Declare the mode. A JSON path on a plain `text` column is rejected rather than compiled into a `json_extract` that quietly returns `NULL`.
106
+ - **Booleans are `integer(..., { mode: 'boolean' })`** - SQLite stores 0 and 1.
107
+ - **Timestamps are ISO 8601 strings.** `generateTzColumnDefs()` handles this, defaulting to `(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`. Comparisons stay lexicographic, so ordering by a timestamp still works.
108
+
109
+ `generateIdColumnDefs` accepts `dataType: 'string'` and `dataType: 'number'`. There is no `'big-number'` - an integer primary key in SQLite is already the 64-bit rowid.
110
+
111
+ ## Repositories
112
+
113
+ `DefaultSqliteRepository` is the base most repositories want. `ReadableSqliteRepository`, `PersistableSqliteRepository` and `SoftDeletableSqliteRepository` sit beside it, mirroring the Postgres chain.
114
+
115
+ ```typescript
116
+ import { repository } from '@venizia/ignis';
117
+ import { DefaultSqliteRepository } from '@venizia/ignis/sqlite';
118
+
119
+ @repository({ model: Product, dataSource: SqliteDataSource })
120
+ export class ProductRepository extends DefaultSqliteRepository<typeof Product.schema> {}
121
+ ```
122
+
123
+ Reads look exactly like Postgres reads:
124
+
125
+ ```typescript
126
+ await this.productRepository.find({
127
+ filter: {
128
+ where: { and: [{ isActive: true }, { name: { like: 'wid%' } }] },
129
+ order: ['createdAt DESC'],
130
+ limit: 20,
131
+ },
132
+ });
133
+ ```
134
+
135
+ Remember that `like` is case-insensitive here, so `{ like: 'wid%' }` also matches `Widget`. Non-ASCII is not folded, so `'ÉCOLE' LIKE 'é%'` is still false. Never set `PRAGMA case_sensitive_like=ON` - it would break `ilike` too.
136
+
137
+ JSON paths use the same dot notation as Postgres and compile to SQLite syntax:
138
+
139
+ ```typescript
140
+ { where: { 'metadata.tier': { eq: 'gold' } } }
141
+ // SQL: json_extract("metadata", '$."tier"') = ?
142
+
143
+ { where: { 'metadata.score': { gt: 50 } } }
144
+ // SQL: json_extract("metadata", '$."score"') > ? -- no cast; json_extract is already typed
145
+ ```
146
+
147
+ Postgres needs a numeric cast because `#>>` always returns text. SQLite does not, because `json_extract` hands back a JSON number as `INTEGER` or `REAL`. See [JSON/JSONB Filtering](/references/base/filter-system/json-filtering) for the path grammar.
148
+
149
+ ## Transactions
150
+
151
+ SQLite has no isolation levels. Every SQLite transaction is already serializable, so the axis is a **locking mode**:
152
+
153
+ | `beginMode` | Statement | Takes the write lock |
154
+ | :--- | :--- | :--- |
155
+ | `SqliteBeginModes.IMMEDIATE` (default) | `BEGIN IMMEDIATE` | At `BEGIN` |
156
+ | `SqliteBeginModes.DEFERRED` | `BEGIN DEFERRED` | At the first write |
157
+ | `SqliteBeginModes.EXCLUSIVE` | `BEGIN EXCLUSIVE` | At `BEGIN`, blocking readers too |
158
+
159
+ ```typescript
160
+ import { SqliteBeginModes } from '@venizia/ignis/sqlite';
161
+
162
+ const transaction = await this.dataSource.beginTransaction({
163
+ beginMode: SqliteBeginModes.IMMEDIATE,
164
+ });
165
+
166
+ try {
167
+ await this.productRepository.create({ data: { name: 'widget' }, options: { transaction } });
168
+ await transaction.commit();
169
+ } catch (error) {
170
+ // Nested: a first rollback that itself fails throws, and would replace the original error.
171
+ try {
172
+ await transaction.rollback();
173
+ } catch (rollbackError) {
174
+ this.logger.error('Rollback failed | %s', rollbackError);
175
+ }
176
+
177
+ throw error;
178
+ }
179
+ ```
180
+
181
+ `commit()` and `rollback()` throw on failure, exactly as they do on Postgres. See [Transactions](./transactions) for the full contract.
182
+
183
+ The default is `IMMEDIATE`, not SQLite's own `DEFERRED`. A deferred transaction takes its write lock at the first write, and that upgrade fails outright with `SQLITE_BUSY` when another writer got there first. `IMMEDIATE` waits on the busy timeout instead.
184
+
185
+ Passing `isolationLevel` throws rather than being ignored. Ignoring it would leave you believing `SERIALIZABLE` was honoured.
186
+
187
+ A `beginMode` outside those three throws too. The mode is written straight into the `BEGIN` statement, which the driver runs verbatim, so it has to come from the closed set. Matching is exact - `'immediate'` is refused, because nothing upper-cases it for you.
188
+
189
+ ### One connection, so writes serialise
190
+
191
+ `beginTransaction()` borrows the single libsql connection from a 1-slot pool. A second transaction waits. SQLite allows one writer at a time regardless, so this costs no throughput SQLite was going to give.
192
+
193
+ Reads are the part to watch. A query on the pooled connector while a transaction is open runs **inside** that transaction. Pass `options.transaction` when the work belongs to the transaction, and route work that must stay outside through the driver's `acquire()`.
194
+
195
+ > [!WARNING] Transactions need a local database
196
+ > `beginTransaction()` throws `501` when the libsql client is not a `file:` or `:memory:` one. A remote client opens a stream per statement and closes it, so `BEGIN` would neither hold nor error - the transaction would silently not exist. Use a local file or an embedded replica.
197
+
198
+ ### The acquire timeout
199
+
200
+ A transaction that never commits holds the one slot forever. Without a bound, every later `beginTransaction()` in the process hangs silently. So the wait is bounded:
201
+
202
+ | Option | Type | Default | Meaning |
203
+ | :--- | :--- | :--- | :--- |
204
+ | `acquireTimeoutMs` | `number` | `LibSqlDriver.DEFAULT_ACQUIRE_TIMEOUT_MS` (30000) | Max ms to wait for the connection before rejecting |
205
+ | `maxWaitingClients` | `number` | unlimited | Max queued waiters; over the limit, `acquire()` rejects immediately |
206
+ | `scope` | `string` | `'LibSqlDriver'` | Logger scope |
207
+ | `client` | `Client` | required | The libsql client the driver borrows |
208
+
209
+ The timeout bounds the **wait for the connection**, never the transaction itself. It fires only under contention, so a migration or a bulk seed running alone never trips it. When it does fire you get a named error, not a dead process:
210
+
211
+ ```
212
+ [LibSqlDriver][acquire] Could not borrow the single libsql connection
213
+ | [acquire] Acquire timed out after 30000ms.
214
+ | An unreleased transaction still holds it - commit/rollback it, or raise `acquireTimeoutMs`
215
+ ```
216
+
217
+ To change any of these, construct the driver yourself with `useDriver()`. That assigns the driver and builds the connector in one step, and bypasses `@datasource({ driver })`:
218
+
219
+ ```typescript
220
+ override configure(): void {
221
+ const client = createClient(this.settings);
222
+
223
+ this.useDriver({
224
+ driver: new LibSqlDriver({ client, acquireTimeoutMs: 120_000 }),
225
+ schema: this.getSchema(),
226
+ });
227
+ }
228
+ ```
229
+
230
+ `size` is not an option. One slot **is** the mutual exclusion the single connection lacks, so it is pinned at 1.
231
+
232
+ ## Schema and migrations
233
+
234
+ drizzle-kit, with the `sqlite` dialect:
235
+
236
+ ```typescript
237
+ // drizzle.config.ts
238
+ import { defineConfig } from 'drizzle-kit';
239
+
240
+ export default defineConfig({
241
+ dialect: 'sqlite',
242
+ schema: './src/models/**/*.model.ts',
243
+ out: './migrations',
244
+ dbCredentials: { url: 'file:./data.db' },
245
+ });
246
+ ```
247
+
248
+ Use `dialect: 'turso'` instead for a remote database - it adds `authToken` to `dbCredentials`.
249
+
250
+ ```bash
251
+ bun run drizzle-kit generate
252
+ ```
253
+
254
+ Apply them at boot, before the first repository call:
255
+
256
+ ```typescript
257
+ import { drizzle } from 'drizzle-orm/libsql';
258
+ import { migrate } from 'drizzle-orm/libsql/migrator';
259
+
260
+ export const applyMigrations = async (opts: { dataSource: SqliteDataSource }): Promise<void> => {
261
+ const client = opts.dataSource.getClient();
262
+
263
+ await migrate(drizzle({ client }), { migrationsFolder: './migrations' });
264
+ };
265
+ ```
266
+
267
+ ## Out of scope in this release
268
+
269
+ Three components declare their models as `pgTable` and are Postgres-only for now. A SQLite datasource cannot back them:
270
+
271
+ | Component | Why |
272
+ | :--- | :--- |
273
+ | **Authentication** | `User`, `Role`, `Permission` and `PolicyDefinition` are `pgTable` models |
274
+ | **Authorization** | The Casbin adapters type their connector as Drizzle's `PgDatabase` |
275
+ | **StaticAssetComponent** | Its base model is a `pgTable` with `jsonb` and Postgres indexes |
276
+
277
+ Everything else - your own models, repositories, services, controllers, REST routes - works on either engine.
278
+
279
+ ## Runnable example
280
+
281
+ `examples/sqlite-quickstart/` is this page as an app: a `sqliteTable` model with a `text` id and `text({ mode: 'json' })` metadata, `generateTzColumnDefs` for the timestamp default, and the same repository and controller the PGlite example uses.
282
+
283
+ ```bash
284
+ cd examples/sqlite-quickstart
285
+ bun install
286
+ bun run start # http://localhost:3000/api/notes
287
+ ```
288
+
289
+ ## See Also
290
+
291
+ - [PGlite](./pglite) - the other embedded engine, with real Postgres semantics
292
+ - [Postgres Drivers & Supabase](./postgres-drivers) - the driver seam
293
+ - [DataSources](./datasources) - schema auto-discovery and `configure()`
294
+ - [Transactions](./transactions) - the transaction object and connection safety
295
+ - [JSON/JSONB Filtering](/references/base/filter-system/json-filtering) - the JSON path grammar
296
+ - [Connectors](/references/base/connectors) - the engine-neutral contract
@@ -3,7 +3,7 @@
3
3
  IGNIS supports explicit transaction objects that can be passed across multiple services and repositories, allowing for complex, multi-step business logic to be atomic.
4
4
 
5
5
  > [!NOTE] PostgreSQL-only capability
6
- > Real transactions are a **PostgreSQL connector** capability - `BasePostgresDataSource.getCapabilities()` returns `{ transactions: true }` and its `beginTransaction()` opens a real database transaction, as documented below. The typesense connector inherits the engine-neutral `AbstractDataSource` default: calling `beginTransaction()` on it throws a `501 Not Implemented` (`normalized.code: 'core.not_supported'`) via the shared `throwNotSupported` utility. See [Connectors](/references/base/connectors) for the capabilities model.
6
+ > Real transactions are a **PostgreSQL connector** capability - `BasePostgresDataSource.getCapabilities()` returns `{ transactions: true }` and its `beginTransaction()` opens a real database transaction, as documented below. The typesense connector inherits the engine-neutral `AbstractDataSource` default. Calling `beginTransaction()` on it throws a `501 Not Implemented` (`normalized.code: 'core.not_supported'`) via the shared `throwNotSupported` utility. See [Connectors](/references/base/connectors) for the capabilities model.
7
7
 
8
8
  ## Using Transactions
9
9
 
@@ -51,18 +51,18 @@ try {
51
51
  > `COMMIT` can genuinely fail - a deadlock, a serialization failure under `SERIALIZABLE`, a dropped
52
52
  > connection, a deferred constraint firing at commit time. When it does, `commit()` throws rather
53
53
  > than resolving, so you never report success on a write that was never persisted. The connection is
54
- > then destroyed instead of being returned to the pool, because it may still hold an open
54
+ > then destroyed instead of being returned to the pool. It may still hold an open
55
55
  > transaction that the next borrower would inherit.
56
56
  >
57
57
  > `rollback()` behaves the same way when it is the FIRST verb to fail. One deliberate exception
58
- > keeps the everyday `catch { await tx.rollback(); throw error; }` pattern safe: calling
58
+ > keeps the everyday `catch { await tx.rollback(); throw error; }` pattern safe. Calling
59
59
  > `rollback()` on a transaction that already ended BY FAILURE (a failed `COMMIT` or a failed prior
60
- > `ROLLBACK`) is a silent no-op - nothing was committed and the connection is already destroyed, so
60
+ > `ROLLBACK`) is a silent no-op. Nothing was committed and the connection is already destroyed, so
61
61
  > the rollback's goal is achieved and your original error survives. The nested-try form above is
62
62
  > still the safest general pattern, because a FIRST rollback that itself fails does throw.
63
63
  >
64
- > The destroy half is **driver-specific**: node-postgres (`pg`) discards the poisoned connection,
65
- > but postgres-js has no destroy semantics (`ReservedSql.release()` takes no argument), so under the
64
+ > The destroy half is **driver-specific**: node-postgres (`pg`) discards the poisoned connection.
65
+ > But postgres-js has no destroy semantics (`ReservedSql.release()` takes no argument), so under the
66
66
  > postgres-js driver the connection returns to the pool anyway. See
67
67
  > [Postgres Drivers & Supabase](./postgres-drivers) for the full asymmetry.
68
68
 
@@ -78,7 +78,7 @@ try {
78
78
  | `commit()` | `Promise<void>` | Commit and release the connection. **Throws** if `COMMIT` fails, and destroys the connection rather than pooling it |
79
79
  | `rollback()` | `Promise<void>` | Rollback and release the connection. **Throws** if `ROLLBACK` fails, and destroys the connection rather than pooling it |
80
80
 
81
- Calling `commit()` or `rollback()` on an already-ended transaction throws an error, with one exception: `rollback()` after the transaction ended BY FAILURE is a silent no-op (see the warning above).
81
+ Calling `commit()` or `rollback()` on an already-ended transaction throws an error. The one exception: `rollback()` after the transaction ended BY FAILURE is a silent no-op (see the warning above).
82
82
 
83
83
  ## Isolation Levels
84
84
 
@@ -95,9 +95,10 @@ IGNIS supports standard PostgreSQL isolation levels:
95
95
 
96
96
  ## Best Practices
97
97
 
98
- 1. **Always use `try...catch`, and nest the rollback**: `rollback()` throws when `ROLLBACK` fails, so a bare `await tx.rollback()` inside a `catch` would discard the error that sent you there. Wrap it in its own `try...catch`, log the rollback failure, and rethrow the original cause.
99
- 2. **Keep it short**: Long-running transactions hold database connections from the pool and can cause connection exhaustion.
100
- 3. **Pass explicit options**: When calling other services inside a transaction, ensure they accept and use the `transaction` option.
98
+ 1. **Always use `try...catch`, and nest the rollback**: `rollback()` throws when `ROLLBACK` fails. A bare `await tx.rollback()` inside a `catch` would discard the error that sent you there.
99
+ 2. **Wrap the rollback in its own `try...catch`**: log the rollback failure, and rethrow the original cause.
100
+ 3. **Keep it short**: Long-running transactions hold database connections from the pool and can cause connection exhaustion.
101
+ 4. **Pass explicit options**: When calling other services inside a transaction, ensure they accept and use the `transaction` option.
101
102
 
102
103
  ```typescript
103
104
  // Service method supporting transactions
@@ -196,7 +197,7 @@ export class OrderController extends BaseRestController {
196
197
 
197
198
  ## How Transactions Work Internally
198
199
 
199
- When you pass a `transaction` option to a repository method, the repository uses the transaction's `connector` (a Drizzle instance bound to the transaction's dedicated connection) instead of the default datasource connector. This ensures all operations within the transaction use the same database connection and see a consistent view of the data.
200
+ When you pass a `transaction` option to a repository method, the repository uses the transaction's `connector` instead of the default datasource connector. That connector is a Drizzle instance bound to the transaction's dedicated connection. All operations within the transaction therefore use the same database connection and see a consistent view of the data.
200
201
 
201
202
  ```typescript
202
203
  // Inside PostgresBaseRepository (simplified)