@venizia/ignis-docs 0.1.0 → 0.2.1-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +22 -11
- package/content/best-practices/architecture-decisions.md +29 -16
- package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
- package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
- package/content/best-practices/code-style-standards/control-flow.md +33 -1
- package/content/best-practices/code-style-standards/documentation.md +28 -8
- package/content/best-practices/code-style-standards/function-patterns.md +15 -4
- package/content/best-practices/code-style-standards/index.md +7 -2
- package/content/best-practices/code-style-standards/naming-conventions.md +26 -2
- package/content/best-practices/code-style-standards/route-definitions.md +9 -5
- package/content/best-practices/code-style-standards/tooling.md +14 -1
- package/content/best-practices/code-style-standards/type-safety.md +39 -6
- package/content/best-practices/common-pitfalls.md +59 -35
- package/content/best-practices/contribution-workflow.md +6 -2
- package/content/best-practices/data-modeling.md +83 -67
- package/content/best-practices/deployment-strategies.md +56 -19
- package/content/best-practices/error-handling.md +182 -93
- package/content/best-practices/index.md +6 -0
- package/content/best-practices/performance-optimization.md +31 -17
- package/content/best-practices/security-guidelines.md +35 -9
- package/content/best-practices/testing-strategies.md +7 -2
- package/content/best-practices/troubleshooting-tips.md +27 -17
- package/content/extensions/components/api-reference.md +107 -322
- package/content/extensions/components/authentication/api.md +454 -603
- package/content/extensions/components/authentication/errors.md +121 -498
- package/content/extensions/components/authentication/index.md +88 -801
- package/content/extensions/components/authentication/usage.md +207 -956
- package/content/extensions/components/authorization/api.md +736 -656
- package/content/extensions/components/authorization/errors.md +168 -206
- package/content/extensions/components/authorization/index.md +82 -797
- package/content/extensions/components/authorization/usage.md +194 -527
- package/content/extensions/components/health-check.md +71 -243
- package/content/extensions/components/mail/api.md +504 -287
- package/content/extensions/components/mail/errors.md +73 -61
- package/content/extensions/components/mail/index.md +96 -467
- package/content/extensions/components/mail/usage.md +130 -172
- package/content/extensions/components/request-tracker.md +66 -173
- package/content/extensions/components/socket-io/api.md +195 -13
- package/content/extensions/components/socket-io/errors.md +3 -3
- package/content/extensions/components/socket-io/index.md +50 -337
- package/content/extensions/components/socket-io/usage.md +143 -26
- package/content/extensions/components/static-asset/api.md +410 -142
- package/content/extensions/components/static-asset/errors.md +110 -53
- package/content/extensions/components/static-asset/index.md +79 -608
- package/content/extensions/components/static-asset/usage.md +180 -300
- package/content/extensions/components/websocket/api.md +275 -399
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +74 -407
- package/content/extensions/components/websocket/usage.md +110 -341
- package/content/extensions/helpers/cron/index.md +51 -160
- package/content/extensions/helpers/crypto/index.md +62 -483
- package/content/extensions/helpers/crypto/reference.md +456 -0
- package/content/extensions/helpers/env/index.md +60 -178
- package/content/extensions/helpers/error/index.md +221 -207
- package/content/extensions/helpers/inversion/index.md +65 -556
- package/content/extensions/helpers/inversion/reference.md +522 -0
- package/content/extensions/helpers/kafka/admin.md +20 -1
- package/content/extensions/helpers/kafka/compile-binary.md +98 -0
- package/content/extensions/helpers/kafka/consumer.md +54 -20
- package/content/extensions/helpers/kafka/examples.md +21 -16
- package/content/extensions/helpers/kafka/index.md +80 -607
- package/content/extensions/helpers/kafka/producer.md +134 -5
- package/content/extensions/helpers/kafka/schema-registry.md +45 -70
- package/content/extensions/helpers/logger/hf-logger.md +193 -0
- package/content/extensions/helpers/logger/index.md +64 -563
- package/content/extensions/helpers/logger/pino.md +85 -0
- package/content/extensions/helpers/logger/reference.md +746 -0
- package/content/extensions/helpers/network/api.md +241 -195
- package/content/extensions/helpers/network/index.md +72 -530
- package/content/extensions/helpers/queue/index.md +72 -900
- package/content/extensions/helpers/queue/reference.md +467 -0
- package/content/extensions/helpers/redis/index.md +73 -645
- package/content/extensions/helpers/redis/reference.md +727 -0
- package/content/extensions/helpers/secrets/index.md +66 -0
- package/content/extensions/helpers/socket-io/api.md +305 -203
- package/content/extensions/helpers/socket-io/index.md +66 -432
- package/content/extensions/helpers/storage/api.md +564 -462
- package/content/extensions/helpers/storage/index.md +77 -573
- package/content/extensions/helpers/types/index.md +66 -499
- package/content/extensions/helpers/types/reference.md +650 -0
- package/content/extensions/helpers/uid/index.md +58 -227
- package/content/extensions/helpers/websocket/api.md +329 -216
- package/content/extensions/helpers/websocket/index.md +65 -503
- package/content/extensions/helpers/worker-thread/index.md +58 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/guides/core-concepts/persistent/datasources.md +13 -20
- package/content/guides/core-concepts/persistent/index.md +2 -4
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/postgres-drivers.md +59 -25
- package/content/guides/core-concepts/persistent/search-meilisearch.md +3 -1
- package/content/guides/core-concepts/persistent/search-typesense.md +7 -5
- package/content/guides/core-concepts/persistent/transactions.md +1 -1
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/migrations/redis-helpers-migration.md +1 -1
- package/content/guides/migrations/unified-connectors-migration.md +2 -2
- package/content/guides/tutorials/building-a-crud-api.md +9 -13
- package/content/guides/tutorials/ecommerce-api.md +10 -15
- package/content/guides/tutorials/realtime-chat.md +7 -7
- package/content/references/base/application.md +1 -1
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/datasources-reference.md +599 -0
- package/content/references/base/datasources.md +85 -447
- package/content/references/base/dependency-injection.md +17 -39
- package/content/references/base/filter-system/application-usage.md +69 -121
- package/content/references/base/filter-system/array-operators.md +12 -0
- package/content/references/base/filter-system/comparison-operators.md +12 -0
- package/content/references/base/filter-system/default-filter.md +136 -348
- package/content/references/base/filter-system/fields-order-pagination.md +38 -16
- package/content/references/base/filter-system/index.md +106 -257
- package/content/references/base/filter-system/json-filtering.md +12 -2
- package/content/references/base/filter-system/list-operators.md +16 -2
- package/content/references/base/filter-system/logical-operators.md +13 -0
- package/content/references/base/filter-system/null-operators.md +13 -0
- package/content/references/base/filter-system/pattern-matching.md +12 -0
- package/content/references/base/filter-system/quick-reference.md +11 -2
- package/content/references/base/filter-system/range-operators.md +12 -0
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +156 -233
- package/content/references/base/middlewares.md +35 -21
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +80 -1452
- package/content/references/base/repositories/advanced.md +156 -192
- package/content/references/base/repositories/index.md +77 -650
- package/content/references/base/repositories/mixins.md +22 -18
- package/content/references/base/repositories/relations.md +123 -171
- package/content/references/base/repositories/soft-deletable.md +58 -56
- package/content/references/base/secrets.md +263 -0
- package/content/references/base/services.md +2 -2
- package/content/references/configuration/environment-variables.md +51 -5
- package/content/references/configuration/index.md +49 -31
- package/content/references/quick-reference.md +3 -16
- package/content/references/utilities/crypto.md +35 -76
- package/content/references/utilities/date.md +33 -73
- package/content/references/utilities/index.md +1 -1
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +29 -62
- package/content/references/utilities/parse.md +34 -64
- package/content/references/utilities/performance.md +33 -58
- package/content/references/utilities/promise.md +28 -62
- package/content/references/utilities/request.md +57 -218
- package/content/references/utilities/schema.md +43 -137
- package/content/references/utilities/statuses-reference.md +361 -0
- package/content/references/utilities/statuses.md +63 -667
- package/package.json +8 -8
|
@@ -8,7 +8,7 @@ IGNIS talks to PostgreSQL through a **driver seam**: `IRelationalDriver` owns co
|
|
|
8
8
|
Supabase is unmodified PostgreSQL, so it is not a separate connector: it varies the **driver**, not the SQL dialect. The `@venizia/ignis/postgres/supabase` submodule adds the two things Supabase deployments actually need - pooler presets and an RLS auth-context helper.
|
|
9
9
|
|
|
10
10
|
> [!IMPORTANT] Every database client is optional
|
|
11
|
-
> `pg` and `postgres` are both **optional peer dependencies**. The `@venizia/ignis/postgres` module pulls in neither -
|
|
11
|
+
> `pg` and `postgres` are both **optional peer dependencies**. The `@venizia/ignis/postgres` module pulls in neither - only the driver class you import and name in `@datasource({ driver })` reaches your bundle. Install the one your app uses:
|
|
12
12
|
>
|
|
13
13
|
> ```bash
|
|
14
14
|
> bun add pg # node-postgres
|
|
@@ -19,60 +19,89 @@ Supabase is unmodified PostgreSQL, so it is not a separate connector: it varies
|
|
|
19
19
|
|
|
20
20
|
| Import | Contents | Loads |
|
|
21
21
|
| :--- | :--- | :--- |
|
|
22
|
-
| `@venizia/ignis/postgres` | `BasePostgresDataSource`, `IRelationalDriver`,
|
|
22
|
+
| `@venizia/ignis/postgres` | `BasePostgresDataSource`, `IRelationalDriver`, repository hierarchy | no client library |
|
|
23
23
|
| `@venizia/ignis/postgres/node-postgres` | `NodePostgresDriver` | `pg` |
|
|
24
24
|
| `@venizia/ignis/postgres/postgres-js` | `PostgresJsDriver` | `postgres` |
|
|
25
25
|
| `@venizia/ignis/postgres/supabase` | `PoolerModes`, `buildPostgresJsOptions`, `withAuthContext`, Supabase role re-exports | `drizzle-orm/supabase` |
|
|
26
26
|
|
|
27
|
-
##
|
|
27
|
+
## Naming the Driver Class
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
`@datasource({ driver })` takes the driver **class**, not a driver-name string:
|
|
30
30
|
|
|
31
31
|
```typescript
|
|
32
|
-
import {
|
|
32
|
+
import { datasource, ValueOrPromise } from '@venizia/ignis';
|
|
33
33
|
import { BasePostgresDataSource } from '@venizia/ignis/postgres';
|
|
34
|
-
import {
|
|
34
|
+
import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
|
|
35
35
|
import { Pool } from 'pg';
|
|
36
36
|
|
|
37
|
-
@datasource({ driver:
|
|
37
|
+
@datasource({ driver: NodePostgresDriver })
|
|
38
38
|
export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfigs> {
|
|
39
|
-
configure() {
|
|
40
|
-
this.client = new Pool({ connectionString: this.getConnectionString() });
|
|
41
|
-
this.connector = drizzle({ client: this.client, schema: this.getSchema() });
|
|
39
|
+
override configure(): ValueOrPromise<void> {
|
|
40
|
+
this.client = new Pool({ connectionString: this.getConnectionString() }); // that is all
|
|
42
41
|
}
|
|
43
42
|
}
|
|
44
43
|
```
|
|
45
44
|
|
|
46
|
-
`this.client`
|
|
45
|
+
`configure()` only builds `this.client` - the raw `pg.Pool` (or postgres-js `Sql`) your app's connection settings produce. The base class wires the driver **and** the connector lazily, on first call to `getConnector()` or `beginTransaction()`: it reads the class named in `@datasource({ driver })`, instantiates it over `this.client`, and builds the pooled Drizzle connector from that. `getClient()` hands `this.client` back as the raw-client escape hatch. A datasource that sets neither `this.client` nor a driver (via `useDriver()`, below) throws `No driver and no client` on first use.
|
|
46
|
+
|
|
47
|
+
> [!IMPORTANT] Why a class, not a name
|
|
48
|
+
> A driver-name string cannot carry `pg` or `postgres` into your bundle - it is just text. A dynamic `import('./node-postgres.js')` keyed off that string would defer *execution*, not *packaging*: every bundler statically resolves a literal specifier and packages whatever it points to, so a build that only used node-postgres would still fail with `Could not resolve: "postgres"` the moment postgres-js's import appeared anywhere in the module graph reachable at build time. Naming the class instead makes the driver module a real value reference - the one thing a bundler is forced to keep - which is what lets `pg` and `postgres` stay genuinely optional peers. A bare side-effect import (`import '@venizia/ignis/postgres/node-postgres'`) would not work either: `@venizia/ignis` declares `sideEffects: false`, so a bundler is free to drop an import whose exports go unused.
|
|
49
|
+
>
|
|
50
|
+
> Two tests pin this from different angles: `packages/core/src/__tests__/connectors/postgres/no-eager-driver-import.test.ts` proves no barrel **loads** a driver package in a fresh process (the runtime module graph), and `packages/core/src/__tests__/connectors/postgres/bundle/optional-peers.test.ts` proves no barrel gets a driver package **packaged** by a real bundler.
|
|
47
51
|
|
|
48
52
|
## Using postgres-js
|
|
49
53
|
|
|
50
|
-
|
|
54
|
+
Same shape, different class:
|
|
51
55
|
|
|
52
56
|
```typescript
|
|
53
|
-
import {
|
|
57
|
+
import { datasource, ValueOrPromise } from '@venizia/ignis';
|
|
54
58
|
import { BasePostgresDataSource } from '@venizia/ignis/postgres';
|
|
55
59
|
import { PostgresJsDriver } from '@venizia/ignis/postgres/postgres-js';
|
|
56
60
|
import postgres from 'postgres';
|
|
57
61
|
import type { Sql } from 'postgres';
|
|
58
62
|
|
|
59
|
-
@datasource({ driver:
|
|
63
|
+
@datasource({ driver: PostgresJsDriver })
|
|
60
64
|
export class PostgresDataSource extends BasePostgresDataSource<
|
|
61
65
|
IDataSourceConfigs,
|
|
62
66
|
typeof schema,
|
|
63
67
|
{},
|
|
64
68
|
Sql // getClient() is now honestly typed as postgres-js's Sql, not pg.Pool
|
|
65
69
|
> {
|
|
66
|
-
configure() {
|
|
70
|
+
override configure(): ValueOrPromise<void> {
|
|
71
|
+
this.client = postgres(this.getConnectionString());
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The fourth type parameter (`Client`) defaults to `pg.Pool`; declare it when the raw client escape hatch (`getClient()`) should carry the real type.
|
|
77
|
+
|
|
78
|
+
## Driver Constructors Validate Their Client
|
|
79
|
+
|
|
80
|
+
Both shipped drivers throw immediately if constructed with the wrong shape of client, instead of failing later inside a query:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
new NodePostgresDriver({ client: pool }); // client must expose connect() AND totalCount (pool accounting)
|
|
84
|
+
new PostgresJsDriver({ client: sql }); // client must expose reserve() AND unsafe()
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`NodePostgresDriver` rejects a bare `pg.Client` - it exposes `connect()` too, but has no pool accounting and cannot hand out a dedicated connection per transaction. `PostgresJsDriver` rejects a `pg.Pool` the same way. You will not normally construct these yourself: `wireDriverFromMetadata()` does it for you from `this.client`, so this validation fires the first time a datasource wired the wrong client behind the wrong `@datasource({ driver })` class.
|
|
88
|
+
|
|
89
|
+
## Custom or Third-Party Drivers: `useDriver()`
|
|
90
|
+
|
|
91
|
+
For a driver IGNIS does not ship, wire it explicitly with `useDriver()` - it assigns the driver **and** builds the pooled connector in one step, so the half-wired state (driver set, connector forgotten) cannot exist:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfigs> {
|
|
95
|
+
override configure(): ValueOrPromise<void> {
|
|
67
96
|
this.useDriver({
|
|
68
|
-
driver: new
|
|
97
|
+
driver: new MyCustomDriver({ client: myClient }),
|
|
69
98
|
schema: this.getSchema(),
|
|
70
99
|
});
|
|
71
100
|
}
|
|
72
101
|
}
|
|
73
102
|
```
|
|
74
103
|
|
|
75
|
-
|
|
104
|
+
`useDriver()` bypasses `@datasource({ driver })` entirely - you never need to name a class in the decorator when you wire the driver yourself in `configure()`.
|
|
76
105
|
|
|
77
106
|
> [!WARNING] postgres-js cannot destroy a poisoned connection
|
|
78
107
|
> After a failed `COMMIT` or `ROLLBACK`, node-postgres **destroys** the connection instead of pooling it - the session may still hold an open transaction that the next borrower would inherit. postgres-js has no destroy semantics (`ReservedSql.release()` takes no argument), so the connection is returned to the pool anyway. This asymmetry is real and IGNIS does not paper over it; it is pinned by the driver's own tests.
|
|
@@ -117,18 +146,23 @@ Supabase exposes three ways in, and one of them silently breaks prepared stateme
|
|
|
117
146
|
The transaction pooler (Supavisor) rebinds the backend per transaction, so a server-side prepared statement created on one backend simply is not there next time. `buildPostgresJsOptions` encodes this so you cannot forget it:
|
|
118
147
|
|
|
119
148
|
```typescript
|
|
120
|
-
import {
|
|
149
|
+
import { datasource } from '@venizia/ignis';
|
|
150
|
+
import { BasePostgresDataSource } from '@venizia/ignis/postgres';
|
|
121
151
|
import { PostgresJsDriver } from '@venizia/ignis/postgres/postgres-js';
|
|
152
|
+
import { buildPostgresJsOptions, PoolerModes } from '@venizia/ignis/postgres/supabase';
|
|
122
153
|
import postgres from 'postgres';
|
|
123
154
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
155
|
+
@datasource({ driver: PostgresJsDriver })
|
|
156
|
+
export class SupabaseDataSource extends BasePostgresDataSource<IDataSourceConfigs> {
|
|
157
|
+
override configure() {
|
|
158
|
+
this.client = postgres(connectionString, {
|
|
159
|
+
...buildPostgresJsOptions({ mode: PoolerModes.TRANSACTION, max: 10 }),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
129
163
|
```
|
|
130
164
|
|
|
131
|
-
`prepare: false` is emitted only for `TRANSACTION` mode; `max` is forwarded only when you pass it, so postgres-js's own default survives.
|
|
165
|
+
`prepare: false` is emitted only for `TRANSACTION` mode; `max` is forwarded only when you pass it, so postgres-js's own default survives. Naming `PostgresJsDriver` in `@datasource` is what wires it - `configure()` only needs to build the client, same as node-postgres.
|
|
132
166
|
|
|
133
167
|
### Row Level Security
|
|
134
168
|
|
|
@@ -164,4 +198,4 @@ The submodule also re-exports Drizzle's Supabase helpers (`anonRole`, `authentic
|
|
|
164
198
|
|
|
165
199
|
## Adding a Driver
|
|
166
200
|
|
|
167
|
-
One file under `src/connectors/postgres/drivers/`, implementing the four verbs above, plus a fake client and a test that runs the shared conformance suite (`run({ driver,
|
|
201
|
+
One file under `src/connectors/postgres/drivers/`, implementing the four verbs above, plus a fake client and a test that runs the shared conformance suite (`run({ driver, buildDriverProbe })` in `src/__tests__/connectors/postgres/drivers/conformance/`). Register a sub-path export and an optional peer dependency; never re-export the driver from the drivers barrel - that is what would make its package load eagerly for everyone.
|
|
@@ -61,10 +61,12 @@ The `searchable` and `filterable` flags are **load-bearing here** and are silent
|
|
|
61
61
|
import { datasource } from '@venizia/ignis';
|
|
62
62
|
import { MeilisearchDataSource } from '@venizia/ignis/meilisearch';
|
|
63
63
|
|
|
64
|
-
@datasource(
|
|
64
|
+
@datasource()
|
|
65
65
|
export class ArticleSearchDataSource extends MeilisearchDataSource {}
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
No `driver` in the decorator, and that is not an omission. A relational datasource has to name one, because a single `BasePostgresDataSource` runs on either `pg` or `postgres` and something must pick. A search datasource has already picked: `extends MeilisearchDataSource` **is** the engine reference, and it is what carries the `meilisearch` package into your bundle. Naming the engine twice would just be a second chance to disagree with yourself.
|
|
69
|
+
|
|
68
70
|
Configured through `IMeilisearchDataSourceSettings`:
|
|
69
71
|
|
|
70
72
|
```typescript
|
|
@@ -176,11 +176,11 @@ await connector.linkSynonymSets({ collection: 'articles', synonymSets: ['article
|
|
|
176
176
|
|
|
177
177
|
```typescript
|
|
178
178
|
// src/datasources/search.datasource.ts
|
|
179
|
-
import {
|
|
179
|
+
import { datasource } from '@venizia/ignis';
|
|
180
180
|
import { TypesenseDataSource } from '@venizia/ignis/typesense';
|
|
181
181
|
import { applicationEnvironment, int } from '@venizia/ignis-helpers';
|
|
182
182
|
|
|
183
|
-
@datasource(
|
|
183
|
+
@datasource()
|
|
184
184
|
export class SearchDataSource extends TypesenseDataSource {
|
|
185
185
|
constructor() {
|
|
186
186
|
super({
|
|
@@ -201,6 +201,8 @@ export class SearchDataSource extends TypesenseDataSource {
|
|
|
201
201
|
}
|
|
202
202
|
```
|
|
203
203
|
|
|
204
|
+
No `driver` in the decorator, and that is not an omission. A relational datasource has to name one, because a single `BasePostgresDataSource` runs on either `pg` or `postgres` and something must pick. A search datasource has already picked: `extends TypesenseDataSource` **is** the engine reference, and it is what carries the `typesense` package into your bundle. Naming the engine twice would just be a second chance to disagree with yourself.
|
|
205
|
+
|
|
204
206
|
`TypesenseDataSource` extends `BaseSearchDataSource` (adds auto-discovery/provisioning) which extends `AbstractSearchDataSource` (engine contract: `getDriver()`, `getQueryDialect()`, `compileCollection()`, `ensureCollection()`) which extends the engine-neutral `AbstractDataSource`. Since `TypesenseDataSource` never overrides `beginTransaction()`, it inherits the neutral `NotSupported` default - see [Connectors](/references/base/connectors).
|
|
205
207
|
|
|
206
208
|
On `configure()`, the datasource auto-provisions every discovered collection (`ensureCollection()` per definition, plus any declared `synonyms`) unless constructed with `autoProvision: false`.
|
|
@@ -252,8 +254,8 @@ export class ArticleRepository extends DefaultSearchRepository<TArticleDocument>
|
|
|
252
254
|
|
|
253
255
|
### Write error semantics (shared by both search engines)
|
|
254
256
|
|
|
255
|
-
- **`create()` with a duplicate id throws `409` (`
|
|
256
|
-
- **`updateById()` against a missing id throws `404` (`
|
|
257
|
+
- **`create()` with a duplicate id throws `409` (`normalized.code: 'core.search_engine.already_exists'`)** on both Typesense and Meilisearch - a duplicate is a conflict, never a silent overwrite. If you want last-write-wins, call `upsert()` explicitly. (On Meilisearch the pre-check is not atomic - see the [Meilisearch guide](./search-meilisearch); use `upsert()` when concurrent same-id creates are possible.)
|
|
258
|
+
- **`updateById()` against a missing id throws `404` (`normalized.code: 'core.search_engine.not_found'`)** on both engines. This is the deliberate divergence from the PostgreSQL connector, where a missing-id `updateById` is a silent `{ count: 0 }`.
|
|
257
259
|
- **`deleteById()` against a missing id is silent** (`{ count: 0, data: null }`), matching the relational connectors.
|
|
258
260
|
|
|
259
261
|
## Searching with `search({ mode })`
|
|
@@ -411,7 +413,7 @@ The typesense connector has no transaction or row-level-locking model. Passing a
|
|
|
411
413
|
await articleRepository.updateById({
|
|
412
414
|
id: '123',
|
|
413
415
|
data: { title: 'New' },
|
|
414
|
-
options: { transaction: tx }, // throws: 501,
|
|
416
|
+
options: { transaction: tx }, // throws: 501, normalized.code 'core.not_supported'
|
|
415
417
|
});
|
|
416
418
|
```
|
|
417
419
|
|
|
@@ -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` (`
|
|
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
|
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Secrets & Vault
|
|
3
|
+
description: Load configuration and credentials from a vault, hydrate them at boot, and rotate database credentials without a restart
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Secrets & Vault
|
|
8
|
+
|
|
9
|
+
IGNIS can load configuration and credentials from a vault instead of reading `process.env` directly. This guide shows how to enable it, how to consume secrets in your code, and how to rotate dynamic database credentials into a live pool without restarting the server.
|
|
10
|
+
|
|
11
|
+
> [!TIP] You do not have to do anything to keep working as before
|
|
12
|
+
> Until you override `registerSecrets()`, IGNIS uses the `system-envs` provider, which reads `process.env`. Everything below is opt-in.
|
|
13
|
+
|
|
14
|
+
## The Mental Model
|
|
15
|
+
|
|
16
|
+
Secrets reach your app through two paths:
|
|
17
|
+
|
|
18
|
+
- **Hydrate** - at boot, IGNIS fetches secrets and merges them into `process.env` / `Envs`. Existing code that reads `process.env.APP_ENV_*` keeps working with no change.
|
|
19
|
+
- **Provider** - the resolved provider is bound in the container at `@app/config`. Inject it wherever you need on-demand reads, dynamic credentials, or rotation.
|
|
20
|
+
|
|
21
|
+
You configure both in one place: a `registerSecrets()` override on your application class.
|
|
22
|
+
|
|
23
|
+
## Enabling a Provider
|
|
24
|
+
|
|
25
|
+
Override `registerSecrets()` on your `Application extends BaseApplication`.
|
|
26
|
+
|
|
27
|
+
### HashiCorp Vault
|
|
28
|
+
|
|
29
|
+
Recommended for enterprise: KV v2 static secrets plus dynamic database credentials with automatic rotation.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { SecretProviders, VaultAuthMethods } from '@venizia/ignis-helpers';
|
|
33
|
+
|
|
34
|
+
export class Application extends BaseApplication {
|
|
35
|
+
override registerSecrets() {
|
|
36
|
+
return {
|
|
37
|
+
provider: SecretProviders.HASHICORP_VAULT,
|
|
38
|
+
|
|
39
|
+
config: {
|
|
40
|
+
endpoint: 'https://vault.internal:8200',
|
|
41
|
+
// Dev: a raw token. Production: AppRole or Kubernetes.
|
|
42
|
+
auth: { method: VaultAuthMethods.APP_ROLE, roleId, secretId },
|
|
43
|
+
// or: { method: VaultAuthMethods.TOKEN, token }
|
|
44
|
+
// or: { method: VaultAuthMethods.KUBERNETES, role: 'my-app' }
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// Static KV v2 secrets -> merged into Envs once at boot.
|
|
48
|
+
hydrate: [
|
|
49
|
+
{ path: 'secret/data/myapp/config' }, // keys land as-is
|
|
50
|
+
{ path: 'secret/data/myapp/db', prefix: 'APP_ENV_DS_' }, // optional prefix
|
|
51
|
+
// { path: 'secret/data/myapp/db', keys: { password: 'APP_ENV_DS_PASSWORD' } },
|
|
52
|
+
],
|
|
53
|
+
|
|
54
|
+
// Dynamic engine secrets -> renewed and rotated automatically.
|
|
55
|
+
lease: [
|
|
56
|
+
{ key: 'datasources.PostgresDataSource', path: 'database/creds/app-role' },
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`node-vault` is an optional peer. Install it in the application that uses this provider:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
bun add node-vault
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Applications that do not use this provider never need it - not even when compiling a binary with
|
|
70
|
+
`Bun.build`. If your application uses this provider **and** compiles a binary, ship `node-vault` in
|
|
71
|
+
`node_modules` next to the binary, or inject a ready-made `client` through the helper options.
|
|
72
|
+
|
|
73
|
+
### Dotenv Vault
|
|
74
|
+
|
|
75
|
+
An encrypted `.env.vault` file decrypted at runtime with a per-environment `DOTENV_KEY`. Static only.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { SecretProviders } from '@venizia/ignis-helpers';
|
|
79
|
+
|
|
80
|
+
export class Application extends BaseApplication {
|
|
81
|
+
override registerSecrets() {
|
|
82
|
+
return {
|
|
83
|
+
provider: SecretProviders.DOTENV_VAULT,
|
|
84
|
+
config: { path: '.env.vault', dotenvKey: process.env.DOTENV_KEY },
|
|
85
|
+
hydrate: [{ path: 'ignored' }], // decrypts the file and merges it into Envs
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
bun add @dotenvx/dotenvx
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### System Env (default)
|
|
96
|
+
|
|
97
|
+
The default; shown here only to be explicit. Reads `process.env`, no dependencies.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
override registerSecrets() {
|
|
101
|
+
return { provider: SecretProviders.SYSTEM_ENVS };
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Addressing and Merging
|
|
106
|
+
|
|
107
|
+
Each `hydrate` entry resolves a path to a flat key-value object that is merged into `Envs` and `process.env`:
|
|
108
|
+
|
|
109
|
+
- **Convention** - store the keys in the vault already named `APP_ENV_...` and they merge as-is, no mapping needed.
|
|
110
|
+
- **`prefix`** - prepend a string to every merged key.
|
|
111
|
+
- **`keys`** - an explicit `vaultKey -> envKey` map for precise control (wins over `prefix`).
|
|
112
|
+
|
|
113
|
+
When the provider is live, vault values take precedence over `process.env`.
|
|
114
|
+
|
|
115
|
+
## Reading Secrets in Your Code
|
|
116
|
+
|
|
117
|
+
Inject the provider bound at `@app/config`.
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import type { ISecretsHelper } from '@venizia/ignis-helpers';
|
|
121
|
+
|
|
122
|
+
@service()
|
|
123
|
+
export class BillingService {
|
|
124
|
+
constructor(
|
|
125
|
+
@inject({ key: '@app/config' }) private secrets: ISecretsHelper,
|
|
126
|
+
) {}
|
|
127
|
+
|
|
128
|
+
async run() {
|
|
129
|
+
// A single keyed value (TTL-cached):
|
|
130
|
+
const apiKey = await this.secrets.get({ path: 'secret/data/myapp/stripe', key: 'apiKey' });
|
|
131
|
+
|
|
132
|
+
// Or the whole bundle at a path:
|
|
133
|
+
const bundle = await this.secrets.getBundle({ path: 'secret/data/myapp/stripe' });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Code that only relies on hydrated values keeps reading `process.env.APP_ENV_*` and needs no injection at all.
|
|
139
|
+
|
|
140
|
+
## Rotating Database Credentials
|
|
141
|
+
|
|
142
|
+
Dynamic secrets let Vault mint short-lived database credentials that expire and rotate. IGNIS renews them and, on rotation, rebuilds the connection pool gracefully.
|
|
143
|
+
|
|
144
|
+
You do **not** write any rotation code for PostgreSQL. Two steps enable it:
|
|
145
|
+
|
|
146
|
+
1. Configure a dynamic database engine in Vault so a read against `database/creds/<role>` returns fresh credentials with a TTL.
|
|
147
|
+
2. Add a `lease` entry whose `key` is the DI binding key of your datasource (`datasources.<ClassName>`), as in the HashiCorp example above.
|
|
148
|
+
|
|
149
|
+
From there IGNIS:
|
|
150
|
+
|
|
151
|
+
- renews the lease before it expires;
|
|
152
|
+
- when Vault issues fresh credentials, calls the datasource's `onSecretRotated()`, which swaps the new credentials in, builds a new pool, and drains the old one so in-flight transactions finish;
|
|
153
|
+
- keeps the app running - no restart.
|
|
154
|
+
|
|
155
|
+
> [!WARNING] Your datasource configure() must read from this.settings
|
|
156
|
+
> Rotation writes the new credentials onto `this.settings` and re-runs `configure()`. If your `configure()` builds the pool from a hard-coded connection string instead, rotation rebuilds with stale credentials. Build the pool from `this.settings`:
|
|
157
|
+
> ```typescript
|
|
158
|
+
> override configure(): void {
|
|
159
|
+
> this.client = new Pool(this.settings);
|
|
160
|
+
> }
|
|
161
|
+
> ```
|
|
162
|
+
|
|
163
|
+
## Behaviour When the Vault Is Down
|
|
164
|
+
|
|
165
|
+
The failure policy is keyed on `NODE_ENV`:
|
|
166
|
+
|
|
167
|
+
- **Development** (`local`, `debug`, `development`, `dev`, `sit`) - a vault failure logs a warning and falls back to `process.env`, so you can work offline.
|
|
168
|
+
- **Everything else** - a vault failure throws and crashes the boot. The app never starts with missing or empty secrets.
|
|
169
|
+
|
|
170
|
+
## Testing Against a Real Vault
|
|
171
|
+
|
|
172
|
+
A local end-to-end setup (Vault dev-mode plus a throwaway PostgreSQL) is described in `docs/superpowers/vault-integration-local-testing.md` in the repository.
|
|
173
|
+
|
|
174
|
+
## See Also
|
|
175
|
+
|
|
176
|
+
- [Secrets & Vault Reference](/references/base/secrets) - full API, machinery, and const-classes
|
|
177
|
+
- [DataSources](/guides/core-concepts/persistent/datasources) - the pool that rotation rebuilds
|
|
@@ -57,7 +57,7 @@ export class UserService extends BaseService {
|
|
|
57
57
|
|
|
58
58
|
## Registering a Service
|
|
59
59
|
|
|
60
|
-
Registration is always imperative - call `this.service(ClassName)` inside an application lifecycle method
|
|
60
|
+
Registration is always imperative - call `this.service(ClassName)` inside an application lifecycle method, which handles the binding. There is no class-level decorator for this.
|
|
61
61
|
|
|
62
62
|
```typescript
|
|
63
63
|
import { BaseApplication } from '@venizia/ignis';
|
|
@@ -155,7 +155,7 @@ If you wrapped `subscribe(...)` in a `try/catch` expecting it to throw, that bra
|
|
|
155
155
|
|
|
156
156
|
These are new and do not break anything:
|
|
157
157
|
|
|
158
|
-
- **`RedisSentinelHelper`** - Redis Sentinel topology with automatic failover. See the [Redis Helper reference](/extensions/helpers/redis/) (Sentinel section).
|
|
158
|
+
- **`RedisSentinelHelper`** - Redis Sentinel topology with automatic failover. See the [Redis Helper full reference](/extensions/helpers/redis/reference#construction-sentinel) (Sentinel section).
|
|
159
159
|
- **`createRedisHelper({ mode })`** - a factory that builds the right helper from `RedisModes.SINGLE | CLUSTER | SENTINEL`, so you can pick a topology from config.
|
|
160
160
|
- **`RedisModes`** and **`RedisSentinelRoles`** - const-classes for the enumerable mode/role strings, with matching `TRedisMode` / `TRedisSentinelRole` types.
|
|
161
161
|
- **Expanded data API** - key lifecycle and counters (`exists`, `expire`, `expireAt`, `ttl`, `persist`, `incr`, `decr`, `incrBy`, `decrBy`), hash completion (`hGet`, `hDel`, `hExists`, `hKeys`, `hVals`, `hIncrBy`, `hLen`), sets (`sAdd`, `sRem`, `sMembers`, `sIsMember`, `sCard`), and lists (`lPush`, `rPush`, `lPop`, `rPop`, `lRange`, `lLen`).
|
|
@@ -16,7 +16,7 @@ IGNIS core was restructured around ONE engine-neutral repository family:
|
|
|
16
16
|
- `src/base` now holds a single `AbstractRepository` / `AbstractDataSource` / `AbstractEntity` family. Every engine implements it under `src/connectors/{postgres,typesense}`.
|
|
17
17
|
- Postgres remains re-exported from the root barrel - **your imports keep working unchanged**. Every connector is also addressable explicitly: `@venizia/ignis/postgres`, `@venizia/ignis/typesense` (typesense is subpath-only; its client is an optional peer).
|
|
18
18
|
- Canonical class names are the paradigm-family names (`BaseRelationalDataSource`, `BaseRelationalEntity`, `DefaultRelationalRepository`, ...); the engine name appears only at the concrete datasource (`TypesenseDataSource`) and the query dialect (`PostgresQueryOperators`). The historical names (`BaseDataSource`, `BaseEntity`, `BasePostgresDataSource`, `BasePostgresEntity`, `RDBQueryOperators`, `DefaultCRUDRepository`, ...) all remain as alias re-exports of the SAME classes - `instanceof`, metadata, and bindings are unaffected. No action required; prefer the family names in new code.
|
|
19
|
-
- New capabilities model: `dataSource.getCapabilities()` and a standardized NotSupported error (HTTP 501,
|
|
19
|
+
- New capabilities model: `dataSource.getCapabilities()` and a standardized NotSupported error (HTTP 501, `normalized.code` `core.not_supported`) for engine gaps (e.g. transactions on search engines).
|
|
20
20
|
- New engine: the **Typesense search branch** (`BaseSearchEntity`, `defineSearchCollection`, typed `TSearchDocument`, `DefaultSearchRepository`). It does not affect existing postgres code.
|
|
21
21
|
|
|
22
22
|
## Required migrations (in order)
|
|
@@ -78,7 +78,7 @@ Run this once after the version bump, before trusting the first `tsc` run.
|
|
|
78
78
|
|
|
79
79
|
### 4. Auth endpoint error contract (only if you assert on it)
|
|
80
80
|
|
|
81
|
-
The three auth endpoints backed by unimplemented service methods (`refreshToken`, `getUserInformation`) now return the standardized NotSupported error: HTTP 501 with
|
|
81
|
+
The three auth endpoints backed by unimplemented service methods (`refreshToken`, `getUserInformation`) now return the standardized NotSupported error: HTTP 501 with `normalized.code` `core.not_supported` and message `[AuthController] <feature> is not supported.` (previously a plain `Method not implemented`). BANA was scanned: no code asserts on the old string - listed for completeness.
|
|
82
82
|
|
|
83
83
|
## Strongly recommended (not compile-blocking)
|
|
84
84
|
|
|
@@ -179,7 +179,7 @@ import {
|
|
|
179
179
|
datasource,
|
|
180
180
|
ValueOrPromise,
|
|
181
181
|
} from '@venizia/ignis';
|
|
182
|
-
import {
|
|
182
|
+
import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
|
|
183
183
|
import { Pool } from 'pg';
|
|
184
184
|
|
|
185
185
|
interface IDSConfigs {
|
|
@@ -195,15 +195,14 @@ interface IDSConfigs {
|
|
|
195
195
|
*
|
|
196
196
|
* How it works:
|
|
197
197
|
* 1. @repository decorator binds model to datasource
|
|
198
|
-
* 2.
|
|
199
|
-
* 3.
|
|
198
|
+
* 2. getSchema() auto-discovers all bound models when the driver/connector are wired
|
|
199
|
+
* 3. Naming NodePostgresDriver in @datasource is what wires the driver and Drizzle connector
|
|
200
200
|
*/
|
|
201
|
-
@datasource({ driver:
|
|
201
|
+
@datasource({ driver: NodePostgresDriver })
|
|
202
202
|
export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
|
|
203
203
|
constructor() {
|
|
204
204
|
super({
|
|
205
205
|
name: PostgresDataSource.name,
|
|
206
|
-
// Driver is read from @datasource decorator - no need to pass here!
|
|
207
206
|
config: {
|
|
208
207
|
host: process.env.APP_ENV_POSTGRES_HOST ?? 'localhost',
|
|
209
208
|
port: +(process.env.APP_ENV_POSTGRES_PORT ?? 5432),
|
|
@@ -216,11 +215,8 @@ export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
|
|
|
216
215
|
}
|
|
217
216
|
|
|
218
217
|
override configure(): ValueOrPromise<void> {
|
|
219
|
-
// getSchema() auto-discovers models from @repository bindings
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
// Log discovered schema for debugging
|
|
223
|
-
const schemaKeys = Object.keys(schema);
|
|
218
|
+
// getSchema() auto-discovers models from @repository bindings; log it for debugging
|
|
219
|
+
const schemaKeys = Object.keys(this.getSchema());
|
|
224
220
|
this.logger.debug(
|
|
225
221
|
'[configure] Auto-discovered schema | Schema + Relations (%s): %o',
|
|
226
222
|
schemaKeys.length,
|
|
@@ -228,9 +224,9 @@ export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
|
|
|
228
224
|
);
|
|
229
225
|
|
|
230
226
|
// The client must land on this.client - a local would leave beginTransaction() with nothing
|
|
231
|
-
// to resolve a driver from, and it would throw `No driver and no client`.
|
|
227
|
+
// to resolve a driver from, and it would throw `No driver and no client`. NodePostgresDriver
|
|
228
|
+
// named in @datasource above is what wires the driver and Drizzle connector from it.
|
|
232
229
|
this.client = new Pool(this.settings);
|
|
233
|
-
this.connector = drizzle({ client: this.client, schema });
|
|
234
230
|
}
|
|
235
231
|
}
|
|
236
232
|
```
|
|
@@ -239,7 +235,7 @@ export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
|
|
|
239
235
|
- Schema is auto-discovered from `@repository` decorators - no manual registration needed
|
|
240
236
|
- Uses `getSchema()` for lazy schema resolution (resolves when all models are loaded)
|
|
241
237
|
- Uses environment variables for connection config
|
|
242
|
-
-
|
|
238
|
+
- `configure()` only assigns `this.client` - the base class wires the driver and connector from `@datasource({ driver })`; implements `getConnectionString()` for URL generation
|
|
243
239
|
|
|
244
240
|
> **Deep Dive:** See [DataSources Reference](/references/base/datasources) for advanced configuration and multiple database support.
|
|
245
241
|
|
|
@@ -357,7 +357,7 @@ import {
|
|
|
357
357
|
datasource,
|
|
358
358
|
ValueOrPromise,
|
|
359
359
|
} from '@venizia/ignis';
|
|
360
|
-
import {
|
|
360
|
+
import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
|
|
361
361
|
import { Pool } from 'pg';
|
|
362
362
|
|
|
363
363
|
interface IDSConfigs {
|
|
@@ -368,7 +368,7 @@ interface IDSConfigs {
|
|
|
368
368
|
password: string;
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
-
@datasource({ driver:
|
|
371
|
+
@datasource({ driver: NodePostgresDriver })
|
|
372
372
|
export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
|
|
373
373
|
constructor() {
|
|
374
374
|
super({
|
|
@@ -384,18 +384,18 @@ export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
|
|
|
384
384
|
}
|
|
385
385
|
|
|
386
386
|
override configure(): ValueOrPromise<void> {
|
|
387
|
-
const schema = this.getSchema();
|
|
387
|
+
const schema = Object.keys(this.getSchema());
|
|
388
388
|
|
|
389
389
|
this.logger.debug(
|
|
390
390
|
'[configure] Auto-discovered schema | Schema + Relations (%s): %o',
|
|
391
|
-
|
|
392
|
-
|
|
391
|
+
schema.length,
|
|
392
|
+
schema,
|
|
393
393
|
);
|
|
394
394
|
|
|
395
395
|
// The client must land on this.client - a local would leave beginTransaction() with nothing
|
|
396
|
-
// to resolve a driver from, and it would throw `No driver and no client`.
|
|
396
|
+
// to resolve a driver from, and it would throw `No driver and no client`. NodePostgresDriver
|
|
397
|
+
// named in @datasource above is what wires the driver and Drizzle connector from it.
|
|
397
398
|
this.client = new Pool(this.settings);
|
|
398
|
-
this.connector = drizzle({ client: this.client, schema });
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
401
|
```
|
|
@@ -526,12 +526,11 @@ export class OrderRepository extends DefaultCRUDRepository<typeof Order.schema>
|
|
|
526
526
|
|
|
527
527
|
```typescript
|
|
528
528
|
// src/services/product.service.ts
|
|
529
|
-
import {
|
|
529
|
+
import { inject } from '@venizia/ignis';
|
|
530
530
|
import { BaseService } from '@venizia/ignis';
|
|
531
531
|
import { ProductRepository } from '../repositories/product.repository';
|
|
532
532
|
import { getError } from '@venizia/ignis-helpers';
|
|
533
533
|
|
|
534
|
-
@injectable({})
|
|
535
534
|
export class ProductService extends BaseService {
|
|
536
535
|
constructor(
|
|
537
536
|
@inject({ key: 'repositories.ProductRepository' })
|
|
@@ -595,7 +594,7 @@ export class ProductService extends BaseService {
|
|
|
595
594
|
|
|
596
595
|
```typescript
|
|
597
596
|
// src/services/cart.service.ts
|
|
598
|
-
import {
|
|
597
|
+
import { inject } from '@venizia/ignis';
|
|
599
598
|
import { BaseService } from '@venizia/ignis';
|
|
600
599
|
import { CartRepository } from '../repositories/cart.repository';
|
|
601
600
|
import { ProductService } from './product.service';
|
|
@@ -606,7 +605,6 @@ interface ICartItem {
|
|
|
606
605
|
quantity: number;
|
|
607
606
|
}
|
|
608
607
|
|
|
609
|
-
@injectable({})
|
|
610
608
|
export class CartService extends BaseService {
|
|
611
609
|
constructor(
|
|
612
610
|
@inject({ key: 'repositories.CartRepository' })
|
|
@@ -733,7 +731,7 @@ export class CartService extends BaseService {
|
|
|
733
731
|
|
|
734
732
|
```typescript
|
|
735
733
|
// src/services/order.service.ts
|
|
736
|
-
import {
|
|
734
|
+
import { inject } from '@venizia/ignis';
|
|
737
735
|
import { BaseService } from '@venizia/ignis';
|
|
738
736
|
import { OrderRepository } from '../repositories/order.repository';
|
|
739
737
|
import { CartService } from './cart.service';
|
|
@@ -758,7 +756,6 @@ interface ICreateOrderInput {
|
|
|
758
756
|
billingAddress?: IOrderAddress;
|
|
759
757
|
}
|
|
760
758
|
|
|
761
|
-
@injectable({})
|
|
762
759
|
export class OrderService extends BaseService {
|
|
763
760
|
constructor(
|
|
764
761
|
@inject({ key: 'repositories.OrderRepository' })
|
|
@@ -906,12 +903,10 @@ export class OrderService extends BaseService {
|
|
|
906
903
|
|
|
907
904
|
```typescript
|
|
908
905
|
// src/services/payment.service.ts
|
|
909
|
-
import { injectable } from '@venizia/ignis';
|
|
910
906
|
import { BaseService } from '@venizia/ignis';
|
|
911
907
|
import Stripe from 'stripe';
|
|
912
908
|
import { applicationEnvironment } from '@venizia/ignis-helpers';
|
|
913
909
|
|
|
914
|
-
@injectable({})
|
|
915
910
|
export class PaymentService extends BaseService {
|
|
916
911
|
private _stripe: Stripe;
|
|
917
912
|
|