@venizia/ignis-docs 0.2.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 (174) hide show
  1. package/README.md +14 -14
  2. package/content/best-practices/api-usage-examples.md +39 -19
  3. package/content/best-practices/architectural-patterns.md +24 -13
  4. package/content/best-practices/architecture-decisions.md +29 -16
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
  6. package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
  7. package/content/best-practices/code-style-standards/control-flow.md +33 -1
  8. package/content/best-practices/code-style-standards/documentation.md +28 -8
  9. package/content/best-practices/code-style-standards/function-patterns.md +15 -4
  10. package/content/best-practices/code-style-standards/index.md +7 -2
  11. package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
  12. package/content/best-practices/code-style-standards/route-definitions.md +9 -5
  13. package/content/best-practices/code-style-standards/tooling.md +14 -1
  14. package/content/best-practices/code-style-standards/type-safety.md +39 -6
  15. package/content/best-practices/common-pitfalls.md +59 -35
  16. package/content/best-practices/contribution-workflow.md +8 -4
  17. package/content/best-practices/data-modeling.md +78 -62
  18. package/content/best-practices/deployment-strategies.md +56 -19
  19. package/content/best-practices/error-handling.md +247 -153
  20. package/content/best-practices/index.md +6 -0
  21. package/content/best-practices/performance-optimization.md +26 -13
  22. package/content/best-practices/security-guidelines.md +35 -9
  23. package/content/best-practices/testing-strategies.md +7 -2
  24. package/content/best-practices/troubleshooting-tips.md +27 -17
  25. package/content/extensions/components/api-reference.md +109 -323
  26. package/content/extensions/components/authentication/api.md +489 -602
  27. package/content/extensions/components/authentication/errors.md +135 -498
  28. package/content/extensions/components/authentication/index.md +89 -801
  29. package/content/extensions/components/authentication/usage.md +231 -955
  30. package/content/extensions/components/authorization/api.md +991 -644
  31. package/content/extensions/components/authorization/errors.md +204 -208
  32. package/content/extensions/components/authorization/getting-started.md +227 -0
  33. package/content/extensions/components/authorization/index.md +88 -795
  34. package/content/extensions/components/authorization/usage.md +219 -528
  35. package/content/extensions/components/health-check.md +77 -243
  36. package/content/extensions/components/index.md +24 -90
  37. package/content/extensions/components/mail/api.md +553 -285
  38. package/content/extensions/components/mail/errors.md +76 -62
  39. package/content/extensions/components/mail/index.md +111 -463
  40. package/content/extensions/components/mail/usage.md +139 -173
  41. package/content/extensions/components/request-tracker.md +70 -173
  42. package/content/extensions/components/socket-io/api.md +462 -785
  43. package/content/extensions/components/socket-io/errors.md +49 -51
  44. package/content/extensions/components/socket-io/index.md +69 -372
  45. package/content/extensions/components/socket-io/usage.md +212 -105
  46. package/content/extensions/components/static-asset/api.md +461 -141
  47. package/content/extensions/components/static-asset/errors.md +121 -53
  48. package/content/extensions/components/static-asset/index.md +84 -606
  49. package/content/extensions/components/static-asset/usage.md +184 -299
  50. package/content/extensions/components/template/index.md +3 -3
  51. package/content/extensions/components/websocket/api.md +311 -404
  52. package/content/extensions/components/websocket/errors.md +47 -56
  53. package/content/extensions/components/websocket/index.md +75 -407
  54. package/content/extensions/components/websocket/usage.md +120 -338
  55. package/content/extensions/helpers/cron/index.md +52 -160
  56. package/content/extensions/helpers/crypto/index.md +67 -480
  57. package/content/extensions/helpers/crypto/reference.md +528 -0
  58. package/content/extensions/helpers/env/index.md +64 -178
  59. package/content/extensions/helpers/error/index.md +286 -196
  60. package/content/extensions/helpers/index.md +61 -47
  61. package/content/extensions/helpers/inversion/index.md +76 -550
  62. package/content/extensions/helpers/inversion/reference.md +530 -0
  63. package/content/extensions/helpers/kafka/admin.md +23 -3
  64. package/content/extensions/helpers/kafka/compile-binary.md +83 -52
  65. package/content/extensions/helpers/kafka/consumer.md +65 -28
  66. package/content/extensions/helpers/kafka/examples.md +46 -253
  67. package/content/extensions/helpers/kafka/index.md +55 -617
  68. package/content/extensions/helpers/kafka/producer.md +156 -22
  69. package/content/extensions/helpers/kafka/schema-registry.md +59 -79
  70. package/content/extensions/helpers/logger/hf-logger.md +220 -0
  71. package/content/extensions/helpers/logger/index.md +78 -552
  72. package/content/extensions/helpers/logger/pino.md +105 -0
  73. package/content/extensions/helpers/logger/reference.md +937 -0
  74. package/content/extensions/helpers/network/api.md +276 -195
  75. package/content/extensions/helpers/network/index.md +87 -524
  76. package/content/extensions/helpers/queue/index.md +80 -897
  77. package/content/extensions/helpers/queue/reference.md +494 -0
  78. package/content/extensions/helpers/redis/index.md +86 -640
  79. package/content/extensions/helpers/redis/reference.md +784 -0
  80. package/content/extensions/helpers/secrets/index.md +136 -0
  81. package/content/extensions/helpers/socket-io/api.md +325 -204
  82. package/content/extensions/helpers/socket-io/index.md +79 -429
  83. package/content/extensions/helpers/storage/api.md +584 -464
  84. package/content/extensions/helpers/storage/index.md +80 -573
  85. package/content/extensions/helpers/types/index.md +75 -495
  86. package/content/extensions/helpers/types/reference.md +689 -0
  87. package/content/extensions/helpers/uid/index.md +176 -189
  88. package/content/extensions/helpers/websocket/api.md +366 -214
  89. package/content/extensions/helpers/websocket/index.md +68 -503
  90. package/content/extensions/helpers/worker-thread/index.md +62 -396
  91. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  92. package/content/extensions/index.md +38 -39
  93. package/content/extensions/src-details/mcp-server.md +96 -548
  94. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  95. package/content/guides/core-concepts/persistent/index.md +5 -1
  96. package/content/guides/core-concepts/persistent/models.md +1 -1
  97. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  98. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  99. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  100. package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
  101. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  102. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  103. package/content/guides/core-concepts/secrets-vault.md +177 -0
  104. package/content/guides/core-concepts/services.md +1 -1
  105. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  106. package/content/guides/get-started/philosophy.md +135 -670
  107. package/content/guides/get-started/setup.md +53 -74
  108. package/content/guides/migrations/redis-helpers-migration.md +5 -4
  109. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  110. package/content/guides/migrations/unified-connectors-migration.md +8 -7
  111. package/content/guides/tutorials/ecommerce-api.md +3 -8
  112. package/content/guides/tutorials/realtime-chat.md +1 -1
  113. package/content/references/base/application.md +64 -22
  114. package/content/references/base/components.md +3 -3
  115. package/content/references/base/connectors.md +79 -136
  116. package/content/references/base/controllers.md +12 -12
  117. package/content/references/base/datasources-reference.md +600 -0
  118. package/content/references/base/datasources.md +84 -444
  119. package/content/references/base/dependency-injection.md +25 -46
  120. package/content/references/base/filter-system/application-usage.md +95 -123
  121. package/content/references/base/filter-system/array-operators.md +33 -43
  122. package/content/references/base/filter-system/comparison-operators.md +55 -63
  123. package/content/references/base/filter-system/default-filter.md +144 -350
  124. package/content/references/base/filter-system/fields-order-pagination.md +116 -148
  125. package/content/references/base/filter-system/index.md +114 -253
  126. package/content/references/base/filter-system/json-filtering.md +52 -181
  127. package/content/references/base/filter-system/list-operators.md +28 -44
  128. package/content/references/base/filter-system/logical-operators.md +69 -114
  129. package/content/references/base/filter-system/null-operators.md +41 -98
  130. package/content/references/base/filter-system/pattern-matching.md +48 -51
  131. package/content/references/base/filter-system/quick-reference.md +93 -196
  132. package/content/references/base/filter-system/range-operators.md +22 -38
  133. package/content/references/base/filter-system/tips.md +70 -133
  134. package/content/references/base/filter-system/use-cases.md +173 -232
  135. package/content/references/base/grpc-controllers.md +53 -17
  136. package/content/references/base/index.md +5 -3
  137. package/content/references/base/middlewares.md +43 -28
  138. package/content/references/base/models-reference.md +886 -0
  139. package/content/references/base/models.md +81 -1452
  140. package/content/references/base/providers.md +8 -8
  141. package/content/references/base/repositories/advanced.md +281 -419
  142. package/content/references/base/repositories/index.md +84 -644
  143. package/content/references/base/repositories/mixins.md +25 -21
  144. package/content/references/base/repositories/relations.md +194 -375
  145. package/content/references/base/repositories/soft-deletable.md +67 -55
  146. package/content/references/base/secrets.md +267 -0
  147. package/content/references/base/services.md +8 -6
  148. package/content/references/configuration/environment-variables.md +73 -21
  149. package/content/references/configuration/index.md +51 -31
  150. package/content/references/index.md +1 -1
  151. package/content/references/quick-reference.md +3 -16
  152. package/content/references/utilities/crypto.md +35 -76
  153. package/content/references/utilities/date.md +33 -73
  154. package/content/references/utilities/duration.md +85 -0
  155. package/content/references/utilities/index.md +6 -2
  156. package/content/references/utilities/jsx-reference.md +298 -0
  157. package/content/references/utilities/jsx.md +82 -525
  158. package/content/references/utilities/module.md +81 -61
  159. package/content/references/utilities/parse.md +34 -64
  160. package/content/references/utilities/performance.md +33 -58
  161. package/content/references/utilities/promise.md +28 -62
  162. package/content/references/utilities/request.md +58 -218
  163. package/content/references/utilities/retry.md +139 -0
  164. package/content/references/utilities/schema.md +43 -137
  165. package/content/references/utilities/statuses-reference.md +361 -0
  166. package/content/references/utilities/statuses.md +63 -667
  167. package/dist/mcp-server/index.js +0 -0
  168. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  169. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  171. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  172. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  173. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  174. package/package.json +24 -23
@@ -0,0 +1,528 @@
1
+ ---
2
+ title: Crypto - Full Reference
3
+ description: Complete reference for AES, RSA, ECDH, the shared algorithm base classes, and every option
4
+ difficulty: intermediate
5
+ ---
6
+
7
+ # Crypto - Full Reference
8
+
9
+ Exhaustive reference for `AES`, `RSA`, `ECDH`, the shared `AbstractCryptoAlgorithm`/`BaseCryptoAlgorithm` base classes, and the standalone `hash()` utility. For a readable introduction and the most common tasks, start with the [Crypto overview](/extensions/helpers/crypto/).
10
+
11
+ **Files:**
12
+
13
+ - [`packages/helpers/src/modules/crypto/algorithms/base.algorithm.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/crypto/algorithms/base.algorithm.ts) - `AbstractCryptoAlgorithm`, `BaseCryptoAlgorithm`
14
+ - [`packages/helpers/src/modules/crypto/algorithms/aes.algorithm.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/crypto/algorithms/aes.algorithm.ts) - `AES`, `AESAlgorithmType`
15
+ - [`packages/helpers/src/modules/crypto/algorithms/rsa.algorithm.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/crypto/algorithms/rsa.algorithm.ts) - `RSA`, `RSAAlgorithmType`
16
+ - [`packages/helpers/src/modules/crypto/algorithms/ecdh.algorithm.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/crypto/algorithms/ecdh.algorithm.ts) - `ECDH`, `ECDHAlgorithmType`, `IECDHEncryptedPayload`, `IECDHExtraOptions`
17
+ - [`packages/helpers/src/modules/crypto/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/crypto/common/types.ts) - `ICryptoAlgorithm`
18
+ - [`packages/helpers/src/modules/crypto/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/crypto/common/constants.ts) - `DEFAULT_CIPHER_BITS`, `DEFAULT_PAD_END`
19
+ - [`packages/helpers/src/utilities/crypto.utility.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/utilities/crypto.utility.ts) - `hash()`
20
+
21
+ ## Quick Reference
22
+
23
+ | Class | Extends | Secret type | Async | Runtime API |
24
+ |-------|---------|--------------|-------|-------------|
25
+ | `AES` | `BaseCryptoAlgorithm` | `string` | No | Node `node:crypto` |
26
+ | `RSA` | `BaseCryptoAlgorithm` | `string` (base64 DER key) | No | Node `node:crypto` |
27
+ | `ECDH` | `AbstractCryptoAlgorithm` | `CryptoKey` | Yes | Web Crypto (`crypto.subtle`) |
28
+ | `hash()` | _(standalone function)_ | `string` (HMAC secret, SHA256 only) | No | Node `node:crypto` |
29
+
30
+ ### Import paths
31
+
32
+ ```typescript
33
+ // Algorithm classes
34
+ import { AES, RSA, ECDH } from '@venizia/ignis-helpers';
35
+
36
+ // Hash utility function
37
+ import { hash } from '@venizia/ignis-helpers';
38
+
39
+ // Types
40
+ import type {
41
+ AESAlgorithmType,
42
+ RSAAlgorithmType,
43
+ ECDHAlgorithmType,
44
+ IECDHEncryptedPayload,
45
+ IECDHExtraOptions,
46
+ ICryptoAlgorithm,
47
+ } from '@venizia/ignis-helpers';
48
+ ```
49
+
50
+ All of the above resolve through the root `@venizia/ignis-helpers` barrel, which re-exports `./modules` (and therefore `./modules/crypto`) and `./utilities` in full.
51
+
52
+ ## Type Hierarchy
53
+
54
+ `Source ->` [`base.algorithm.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/crypto/algorithms/base.algorithm.ts)
55
+
56
+ ```
57
+ ICryptoAlgorithm (interface)
58
+ └── AbstractCryptoAlgorithm (extends BaseHelper)
59
+ ├── BaseCryptoAlgorithm (adds normalizeSecretKey, getAlgorithmKeySize)
60
+ │ ├── AES
61
+ │ └── RSA
62
+ └── ECDH (uses CryptoKey objects, not string secrets)
63
+ ```
64
+
65
+ `ICryptoAlgorithm` carries 7 generic type parameters - the algorithm name literal, encrypt/decrypt input and output types, the secret key type, and an extra-options type:
66
+
67
+ ```typescript
68
+ interface ICryptoAlgorithm<
69
+ AlgorithmNameType extends string,
70
+ EncryptInputType = unknown,
71
+ DecryptInputType = unknown,
72
+ SecretKeyType = unknown,
73
+ EncryptReturnType = unknown,
74
+ DecryptReturnType = unknown,
75
+ ExtraOptions = unknown,
76
+ > {
77
+ algorithm: AlgorithmNameType;
78
+ encrypt(opts: { message: EncryptInputType; secret: SecretKeyType; opts?: ExtraOptions }): EncryptReturnType;
79
+ decrypt(opts: { message: DecryptInputType; secret: SecretKeyType; opts?: ExtraOptions }): DecryptReturnType;
80
+ }
81
+ ```
82
+
83
+ `AbstractCryptoAlgorithm` extends `BaseHelper` and declares `encrypt`/`decrypt` as abstract. It adds no behavior of its own. `BaseCryptoAlgorithm` is the concrete base for string-secret algorithms:
84
+
85
+ | Member | Signature | Description |
86
+ |--------|-----------|--------------|
87
+ | constructor | `(opts: { scope: string; algorithm: AlgorithmType })` | Sets `this.algorithm`, calls `validateAlgorithmName` |
88
+ | `validateAlgorithmName` | `(opts: { algorithm: AlgorithmType }) => void` | Throws if `algorithm` is empty/falsy |
89
+ | `normalizeSecretKey` | `(opts: { secret: string; length: number }) => Buffer` | Derives a `length`-byte key with PBKDF2-SHA256, 100,000 iterations. Results are memoised per secret |
90
+ | `getAlgorithmKeySize` | `() => number` | Parses the bit size out of `this.algorithm` (e.g. `256` from `'aes-256-gcm'`), divides by 8 for byte length |
91
+
92
+ `ECDH` extends `AbstractCryptoAlgorithm` directly. It does not inherit `normalizeSecretKey` or `getAlgorithmKeySize` - its secrets are `CryptoKey` objects, not strings.
93
+
94
+ ## AES
95
+
96
+ `Source ->` [`aes.algorithm.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/crypto/algorithms/aes.algorithm.ts)
97
+
98
+ ```typescript
99
+ type AESAlgorithmType = 'aes-256-cbc' | 'aes-256-gcm';
100
+
101
+ const aes = AES.withAlgorithm('aes-256-gcm'); // or 'aes-256-cbc'
102
+ ```
103
+
104
+ | Mode | Authenticated | Notes |
105
+ |------|----------------|-------|
106
+ | `aes-256-cbc` | No | Plain block cipher, no tamper detection |
107
+ | `aes-256-gcm` | Yes | Ciphertext includes a 16-byte GCM auth tag; tampering throws on decrypt |
108
+
109
+ ### `encrypt`
110
+
111
+ ```typescript
112
+ encrypt(opts: { message: string; secret: string; opts?: IAESExtraOptions }): string
113
+ ```
114
+
115
+ | Option (`opts.opts`) | Type | Default | Description |
116
+ |-----------------------|------|---------|-------------|
117
+ | `iv` | `Buffer` | `crypto.randomBytes(16)` | Initialization vector |
118
+ | `inputEncoding` | `crypto.Encoding` | `'utf-8'` | Encoding of `message` |
119
+ | `outputEncoding` | `crypto.Encoding` | `'base64'` | Encoding of the returned ciphertext |
120
+ | `doThrow` | `boolean` | `true` | If `false`, returns the original `message` instead of throwing on error |
121
+
122
+ The secret is normalized via `normalizeSecretKey` to the algorithm's key size (32 bytes for both modes) before being used as the cipher key. The output is a self-describing envelope, concatenated and encoded with `outputEncoding`:
123
+
124
+ ```
125
+ [version(1)][idLen(1)][id(idLen)][iv(16)][authTag(16, gcm only)][ciphertext]
126
+ ```
127
+
128
+ The version byte is `0x01`. The key id is the entry `decrypt` looks up in a keyring - `'0'` when `secret` is a bare string.
129
+
130
+ > [!WARNING] This envelope is not the pre-PBKDF2 one
131
+ > Ciphertext written before this format started with the raw IV and derived its key by padding the secret. `decrypt` rejects it. Read that data with [`LegacyAES`](#legacyaes) instead.
132
+
133
+ ```typescript
134
+ import C from 'node:crypto';
135
+
136
+ const encrypted = aes.encrypt({
137
+ message: 'hello',
138
+ secret: 'my-secret',
139
+ opts: {
140
+ iv: C.randomBytes(16),
141
+ inputEncoding: 'utf-8',
142
+ outputEncoding: 'hex',
143
+ doThrow: false,
144
+ },
145
+ });
146
+ ```
147
+
148
+ ### `decrypt`
149
+
150
+ ```typescript
151
+ decrypt(opts: { message: string; secret: TAESSecret; opts?: IAESDecryptOptions }): string
152
+ ```
153
+
154
+ `decrypt` takes no `iv`. The envelope carries the one `encrypt` used, so passing another would be ignored - the option is absent from `IAESDecryptOptions` and supplying it is a compile error.
155
+
156
+ | Option (`opts.opts`) | Type | Default | Description |
157
+ |-----------------------|------|---------|-------------|
158
+ | `inputEncoding` | `crypto.Encoding` | `'base64'` | Encoding of `message` |
159
+ | `outputEncoding` | `crypto.Encoding` | `'utf-8'` | Encoding of the returned plaintext |
160
+ | `doThrow` | `boolean` | `true` | If `false`, returns the original `message` instead of throwing on error |
161
+
162
+ For `aes-256-gcm`, the next 16 bytes after the IV are read as the auth tag. That tag is passed to `setAuthTag` before the remaining bytes are treated as ciphertext.
163
+
164
+ > [!WARNING]
165
+ > Decrypting `aes-256-gcm` ciphertext with an `aes-256-cbc` instance (or vice versa) throws `Unsupported state or unable to authenticate data`. The two modes produce incompatible byte layouts - always encrypt and decrypt with the same one.
166
+
167
+ ### `encryptFile` / `decryptFile`
168
+
169
+ ```typescript
170
+ encryptFile(opts: { absolutePath: string; secret: string }): string
171
+ decryptFile(opts: { absolutePath: string; secret: string }): string
172
+ ```
173
+
174
+ Both read the file synchronously via `fs.readFileSync`, decode it as UTF-8, then call `encrypt`/`decrypt` on the string content using default extra-options. If `absolutePath` is empty or falsy, both return `''` without touching the filesystem.
175
+
176
+ ```typescript
177
+ const encrypted = aes.encryptFile({ absolutePath: '/path/to/config.json', secret: 'my-secret' });
178
+ const decrypted = aes.decryptFile({ absolutePath: '/path/to/config.json.enc', secret: 'my-secret' });
179
+ ```
180
+
181
+ ### Key rotation with a keyring
182
+
183
+ `secret` accepts a list as well as a string. Encryption always uses the first entry; decryption looks up the id stamped in the envelope.
184
+
185
+ ```typescript
186
+ const KEYRING = [
187
+ { id: '2', secret: process.env.APP_ENV_SECRET_V2! }, // current - everything new is written with this
188
+ { id: '1', secret: process.env.APP_ENV_SECRET_V1! }, // retired - still needed to read old rows
189
+ ];
190
+
191
+ const fresh = aes.encrypt({ message: 'payload', secret: KEYRING }); // tagged id '2'
192
+ const old = aes.decrypt({ message: rowFromLastYear, secret: KEYRING }); // resolved by its own id
193
+ ```
194
+
195
+ Rotating means prepending a new entry, not re-encrypting the estate. Drop an old entry only once nothing carries its id any more - `decrypt` throws `No key in keyring matches ciphertext key id` when it cannot resolve one, and a keyring entry with an empty `secret` is refused by name rather than failing later inside OpenSSL.
196
+
197
+ | `secret` shape | Encrypts with | Envelope key id |
198
+ |---|---|---|
199
+ | `'my-secret'` | that string | `'0'` |
200
+ | `[{ id, secret }, ...]` | the FIRST entry | that entry's `id` |
201
+
202
+ ### LegacyAES
203
+
204
+ `Source ->` [`packages/helpers/src/modules/crypto/algorithms/aes-legacy.algorithm.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/crypto/algorithms/aes-legacy.algorithm.ts)
205
+
206
+ `LegacyAES` reproduces the pre-PBKDF2 behaviour exactly: the key is the secret padded with `'0'` (or truncated) to the algorithm's key size, and the output is `IV [+ GCM auth tag] + ciphertext` with no version header.
207
+
208
+ Reach for it when you hold data written by an earlier IGNIS and do not want to re-encrypt it. The API mirrors `AES`, minus the keyring - `secret` is a plain string.
209
+
210
+ ```typescript
211
+ import { LegacyAES } from '@venizia/ignis-helpers';
212
+
213
+ const legacy = LegacyAES.withAlgorithm('aes-256-cbc');
214
+ const plaintext = legacy.decrypt({ message: rowWrittenBeforeTheUpgrade, secret: APPLICATION_SECRET });
215
+ ```
216
+
217
+ The two formats never cross-decrypt, by design. `AES` rejects a legacy envelope on its version byte; `LegacyAES` fails the auth tag on a new one. Nothing falls back silently in either direction.
218
+
219
+ ### IPayloadCipher - choosing the cipher a component uses
220
+
221
+ A component that encrypts on your behalf takes `IPayloadCipher`, the string-in/string-out slice both classes satisfy:
222
+
223
+ ```typescript
224
+ export interface IPayloadCipher {
225
+ encrypt(opts: { message: string; secret: string }): string;
226
+ decrypt(opts: { message: string; secret: string }): string;
227
+ }
228
+ ```
229
+
230
+ The bearer-token services accept it as `cipher`. An application holding tokens issued before the envelope change keeps them readable by handing over the legacy cipher instead of invalidating every session:
231
+
232
+ ```typescript
233
+ new JWSTokenService({
234
+ jwtSecret,
235
+ getTokenExpiresFn,
236
+ applicationSecret,
237
+ cipher: LegacyAES.withAlgorithm('aes-256-cbc'), // omit it and you get AES
238
+ });
239
+ ```
240
+
241
+ ## RSA
242
+
243
+ `Source ->` [`rsa.algorithm.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/crypto/algorithms/rsa.algorithm.ts)
244
+
245
+ ```typescript
246
+ type RSAAlgorithmType = 'rsa';
247
+
248
+ const rsa = RSA.withAlgorithm(); // no parameters - always algorithm 'rsa'
249
+ ```
250
+
251
+ ### `generateDERKeyPair`
252
+
253
+ ```typescript
254
+ generateDERKeyPair(opts?: { modulus: number }): { publicKey: Buffer; privateKey: Buffer }
255
+ ```
256
+
257
+ | Option | Type | Default | Description |
258
+ |--------|------|---------|-------------|
259
+ | `modulus` | `number` | `2048` | RSA modulus length in bits, passed to `crypto.generateKeyPairSync` |
260
+
261
+ `publicKey` is exported as `{ type: 'spki', format: 'der' }`, `privateKey` as `{ type: 'pkcs8', format: 'der' }`. Both are raw `Buffer`s. Base64-encode them (`.toString('base64')`) to pass as the `secret` string to `encrypt`/`decrypt`.
262
+
263
+ ### `encrypt`
264
+
265
+ ```typescript
266
+ encrypt(opts: { message: string; secret: string; opts?: IRSAExtraOptions }): string
267
+ ```
268
+
269
+ `secret` is the base64-encoded public key (SPKI/DER). Internally builds a public key via `crypto.createPublicKey({ format: 'der', type: 'spki' })` and calls `crypto.publicEncrypt`.
270
+
271
+ | Option (`opts.opts`) | Type | Default | Description |
272
+ |-----------------------|------|---------|-------------|
273
+ | `inputEncoding.key` | `crypto.Encoding` | `'base64'` | Encoding of the `secret` key buffer |
274
+ | `inputEncoding.message` | `crypto.Encoding` | `'utf-8'` | Encoding of `message` |
275
+ | `outputEncoding` | `crypto.Encoding` | `'base64'` | Encoding of the returned ciphertext |
276
+ | `doThrow` | `boolean` | `true` | If `false`, returns the original `message` instead of throwing on error |
277
+
278
+ ### `decrypt`
279
+
280
+ ```typescript
281
+ decrypt(opts: { message: string; secret: string; opts?: IRSAExtraOptions }): string
282
+ ```
283
+
284
+ `secret` is the base64-encoded private key (PKCS8/DER). Internally builds a private key via `crypto.createPrivateKey({ format: 'der', type: 'pkcs8' })` and calls `crypto.privateDecrypt`.
285
+
286
+ | Option (`opts.opts`) | Type | Default | Description |
287
+ |-----------------------|------|---------|-------------|
288
+ | `inputEncoding.key` | `crypto.Encoding` | `'base64'` | Encoding of the `secret` key buffer |
289
+ | `inputEncoding.message` | `crypto.Encoding` | `'base64'` | Encoding of `message` |
290
+ | `outputEncoding` | `crypto.Encoding` | `'utf-8'` | Encoding of the returned plaintext |
291
+ | `doThrow` | `boolean` | `true` | If `false`, returns the original `message` instead of throwing on error |
292
+
293
+ ```typescript
294
+ const rsa = RSA.withAlgorithm();
295
+ const { publicKey, privateKey } = rsa.generateDERKeyPair({ modulus: 4096 });
296
+
297
+ const encrypted = rsa.encrypt({
298
+ message: 'hello',
299
+ secret: publicKey.toString('base64'),
300
+ opts: { outputEncoding: 'hex', doThrow: false },
301
+ });
302
+
303
+ const decrypted = rsa.decrypt({
304
+ message: encrypted,
305
+ secret: privateKey.toString('base64'),
306
+ opts: { inputEncoding: { key: 'base64', message: 'hex' } },
307
+ });
308
+ ```
309
+
310
+ ## ECDH Key Exchange
311
+
312
+ `Source ->` [`ecdh.algorithm.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/crypto/algorithms/ecdh.algorithm.ts)
313
+
314
+ `ECDH` implements ECDH P-256 key exchange with HKDF-derived AES-256-GCM session encryption, entirely on the Web Crypto API (`crypto.subtle`). Every method is `async`.
315
+
316
+ ```typescript
317
+ const ecdh = ECDH.withAlgorithm(); // curve is always 'ecdh-p256'
318
+ const ecdhCustom = ECDH.withAlgorithm({ algorithm: 'ecdh-p256', hkdfInfo: 'my-app-session-keys' });
319
+ ```
320
+
321
+ The options argument is optional. Pass one and `algorithm` becomes required, even though the constructor ignores it and always sets `'ecdh-p256'`.
322
+
323
+ | Constructor option | Type | Required | Description |
324
+ |---------------------|------|----------|-------------|
325
+ | `algorithm` | `'ecdh-p256'` | Only when you pass an options object | Accepted but not branched on - the curve is always P-256 |
326
+ | `hkdfInfo` | `string` | No - defaults to `'ignis-ecdh-p256-aes-256-gcm-v1'` | HKDF info string, UTF-8 encoded, used to isolate key derivation between application contexts |
327
+
328
+ Different `hkdfInfo` values produce **incompatible** derived keys from the same ECDH shared secret.
329
+
330
+ ### Key generation and import
331
+
332
+ | Method | Signature | Description |
333
+ |--------|-----------|--------------|
334
+ | `generateKeyPair` | `() => Promise<{ keyPair: CryptoKeyPair; publicKeyB64: string }>` | `deriveBits`-only key pair (`extractable: false`); `publicKeyB64` is the raw exported public key, base64-encoded (65 bytes for P-256) |
335
+ | `importPublicKey` | `(opts: { rawKeyB64: string }) => Promise<CryptoKey>` | Imports a peer's raw base64 public key |
336
+
337
+ ```typescript
338
+ const { keyPair, publicKeyB64 } = await ecdh.generateKeyPair();
339
+ const peerKey = await ecdh.importPublicKey({ rawKeyB64: peerPublicKeyB64 });
340
+ ```
341
+
342
+ ### `deriveAESKey`
343
+
344
+ ```typescript
345
+ deriveAESKey(opts: {
346
+ privateKey: CryptoKey;
347
+ peerPublicKey: CryptoKey;
348
+ salt?: string;
349
+ }): Promise<{ key: CryptoKey; salt: string }>
350
+ ```
351
+
352
+ Derives shared bits via ECDH (`deriveBits`, 256 bits) and imports them as an HKDF key. It then derives a non-extractable AES-256-GCM `CryptoKey` via HKDF-SHA256, using `salt` and the instance's `hkdfInfo`.
353
+
354
+ | Option | Type | Default | Description |
355
+ |--------|------|---------|-------------|
356
+ | `privateKey` | `CryptoKey` | - | Caller's ECDH private key from `generateKeyPair()` |
357
+ | `peerPublicKey` | `CryptoKey` | - | Peer's public key from `importPublicKey()` |
358
+ | `salt` | `string` | A random 32-byte salt is generated | Base64-encoded HKDF salt. Omit to generate a new random one |
359
+
360
+ > [!IMPORTANT]
361
+ > Both parties must use the **same salt** to derive matching keys. The initiator omits `salt` - a random one is generated and returned. The responder passes that returned `salt` back into their own `deriveAESKey` call. If both sides generate their own salt instead, the two keys never match.
362
+
363
+ ### `encrypt` / `decrypt`
364
+
365
+ ```typescript
366
+ encrypt(opts: { message: string; secret: CryptoKey; opts?: IECDHExtraOptions }): Promise<IECDHEncryptedPayload>
367
+ decrypt(opts: { message: IECDHEncryptedPayload; secret: CryptoKey; opts?: IECDHExtraOptions }): Promise<string>
368
+ ```
369
+
370
+ `secret` is the `CryptoKey` returned by `deriveAESKey`. Uses AES-GCM with a random 12-byte IV per call and a 128-bit auth tag.
371
+
372
+ ```typescript
373
+ interface IECDHEncryptedPayload {
374
+ iv: string; // base64 encoded 12-byte IV
375
+ ct: string; // base64 encoded ciphertext + 128-bit GCM auth tag
376
+ }
377
+
378
+ interface IECDHExtraOptions {
379
+ additionalData?: string;
380
+ }
381
+ ```
382
+
383
+ `opts.additionalData` (AAD) is authenticated but not encrypted. It binds the ciphertext to a context - a channel ID, a session ID - so it can't be replayed into a different one. Decrypt must supply the exact same `additionalData`. A mismatch throws, and so does omitting it when encrypt supplied one.
384
+
385
+ ### Complete flow
386
+
387
+ ```typescript
388
+ const ecdh = ECDH.withAlgorithm();
389
+
390
+ // 1. Both parties generate key pairs
391
+ const alice = await ecdh.generateKeyPair();
392
+ const bob = await ecdh.generateKeyPair();
393
+
394
+ // 2. Exchange public keys (safe to share over any channel)
395
+ const alicePubForBob = await ecdh.importPublicKey({ rawKeyB64: alice.publicKeyB64 });
396
+ const bobPubForAlice = await ecdh.importPublicKey({ rawKeyB64: bob.publicKeyB64 });
397
+
398
+ // 3. Initiator derives the AES key (generates a random salt)
399
+ const { key: aliceKey, salt } = await ecdh.deriveAESKey({
400
+ privateKey: alice.keyPair.privateKey,
401
+ peerPublicKey: bobPubForAlice,
402
+ });
403
+
404
+ // 4. Responder derives the SAME AES key using the initiator's salt
405
+ const { key: bobKey } = await ecdh.deriveAESKey({
406
+ privateKey: bob.keyPair.privateKey,
407
+ peerPublicKey: alicePubForBob,
408
+ salt,
409
+ });
410
+
411
+ // 5. Alice encrypts, Bob decrypts (or vice versa)
412
+ const encrypted = await ecdh.encrypt({ message: 'Hello Bob!', secret: aliceKey });
413
+ const decrypted = await ecdh.decrypt({ message: encrypted, secret: bobKey });
414
+ // => 'Hello Bob!'
415
+ ```
416
+
417
+ ### Security properties
418
+
419
+ | Property | Guarantee |
420
+ |----------|-----------|
421
+ | Confidentiality | AES-256-GCM encryption |
422
+ | Integrity | GCM authentication tag - tampered ciphertext is detected on decrypt |
423
+ | Forward secrecy | Ephemeral, non-extractable key pairs - compromising one session does not compromise others |
424
+ | Key isolation | `hkdfInfo` separates key derivation across application contexts |
425
+ | Context binding | `additionalData` (AAD) prevents cross-context replay |
426
+
427
+ ## Hashing
428
+
429
+ `Source ->` [`crypto.utility.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/utilities/crypto.utility.ts)
430
+
431
+ `hash()` is a standalone function, not a class - it isn't part of the `AES`/`RSA`/`ECDH` hierarchy. It's exported from `packages/helpers/src/utilities`, not `modules/crypto`. It is documented in full on the [Crypto Utility reference](/references/utilities/crypto).
432
+
433
+ ```typescript
434
+ function hash(
435
+ text: string,
436
+ options: {
437
+ algorithm: 'SHA256' | 'MD5';
438
+ secret?: string;
439
+ outputType: C.BinaryToTextEncoding; // 'hex' | 'base64' | 'base64url' | 'latin1'
440
+ },
441
+ ): string;
442
+ ```
443
+
444
+ | Option | Type | Required | Description |
445
+ |--------|------|----------|-------------|
446
+ | `algorithm` | `'SHA256' \| 'MD5'` | Yes | Hashing algorithm |
447
+ | `secret` | `string` | Only for `SHA256` | HMAC secret key; ignored for `MD5` |
448
+ | `outputType` | `'hex' \| 'base64' \| 'base64url' \| 'latin1'` | Yes | Digest output encoding |
449
+
450
+ > [!WARNING]
451
+ > `SHA256` uses `createHmac` and **requires** `secret`. If `secret` is `undefined`, the function short-circuits and returns `text` unchanged - no hash is computed, no error is thrown. `MD5` never needs a secret and always hashes.
452
+
453
+ ```typescript
454
+ import { hash } from '@venizia/ignis-helpers';
455
+
456
+ const md5Hash = hash('some text', { algorithm: 'MD5', outputType: 'hex' });
457
+ const sha256Hmac = hash('some text', { algorithm: 'SHA256', secret: 'a-secret-key', outputType: 'hex' });
458
+ ```
459
+
460
+ ## API Summary
461
+
462
+ | Method | Class | Returns | Description |
463
+ |--------|-------|---------|-------------|
464
+ | `AES.withAlgorithm(algorithm)` | `AES` | `AES` | Create an AES instance with CBC or GCM mode |
465
+ | `encrypt(opts)` | `AES` | `string` | Encrypt a string message |
466
+ | `decrypt(opts)` | `AES` | `string` | Decrypt a ciphertext string |
467
+ | `encryptFile(opts)` | `AES` | `string` | Encrypt file contents to a string |
468
+ | `decryptFile(opts)` | `AES` | `string` | Decrypt file contents to a string |
469
+ | `RSA.withAlgorithm()` | `RSA` | `RSA` | Create an RSA instance |
470
+ | `generateDERKeyPair(opts?)` | `RSA` | `{ publicKey: Buffer; privateKey: Buffer }` | Generate a DER-format key pair |
471
+ | `encrypt(opts)` | `RSA` | `string` | Encrypt with a public key |
472
+ | `decrypt(opts)` | `RSA` | `string` | Decrypt with a private key |
473
+ | `ECDH.withAlgorithm(opts?)` | `ECDH` | `ECDH` | Create an ECDH instance. Passing options requires `algorithm` alongside `hkdfInfo` |
474
+ | `generateKeyPair()` | `ECDH` | `Promise<{ keyPair: CryptoKeyPair; publicKeyB64: string }>` | Generate a P-256 key pair |
475
+ | `importPublicKey(opts)` | `ECDH` | `Promise<CryptoKey>` | Import a peer's base64 public key |
476
+ | `deriveAESKey(opts)` | `ECDH` | `Promise<{ key: CryptoKey; salt: string }>` | Derive an AES-256-GCM key via HKDF |
477
+ | `encrypt(opts)` | `ECDH` | `Promise<IECDHEncryptedPayload>` | Encrypt with a derived AES key |
478
+ | `decrypt(opts)` | `ECDH` | `Promise<string>` | Decrypt with a derived AES key |
479
+ | `hash(text, options)` | _(function)_ | `string` | MD5 digest or HMAC-SHA256 |
480
+
481
+ ## Troubleshooting
482
+
483
+ ### "[validateAlgorithmName] Invalid algorithm name | algorithm: undefined"
484
+
485
+ **Cause:** An empty or undefined `algorithm` was passed to the constructor (or `withAlgorithm()`).
486
+
487
+ **Fix:**
488
+
489
+ ```typescript
490
+ const aes = AES.withAlgorithm('aes-256-gcm'); // not undefined or empty
491
+ const rsa = RSA.withAlgorithm(); // no parameter needed
492
+ const ecdh = ECDH.withAlgorithm(); // no parameter needed
493
+ ```
494
+
495
+ ### "[ECDH.fromBase64] Invalid base64 input"
496
+
497
+ **Cause:** A value passed to an ECDH method - a public key, salt, IV, or ciphertext - is not valid base64. Its length isn't divisible by 4, or it has characters outside `A-Za-z0-9+/=`.
498
+
499
+ **Fix:** Pass base64 strings through exactly as produced by the methods that generated them (`publicKeyB64`, `salt`, `iv`, `ct`). Do not trim, re-encode, or modify them.
500
+
501
+ ### "Unsupported state or unable to authenticate data"
502
+
503
+ **Cause:** Either the ciphertext or auth tag was modified in transit, or encrypt and decrypt used different algorithm modes. The two modes produce incompatible byte layouts.
504
+
505
+ **Fix:** Use the same algorithm mode for both encrypt and decrypt.
506
+
507
+ ### ECDH decrypt throws even though both sides imported each other's public keys
508
+
509
+ **Cause:** Each `deriveAESKey` call without a `salt` generates a new random 32-byte salt. If both sides generate their own, they derive different AES keys.
510
+
511
+ **Fix:** The initiator calls `deriveAESKey` without `salt` and sends the returned `salt` to the responder. The responder passes that exact `salt` into their own `deriveAESKey` call.
512
+
513
+ ### SHA256 hash returns the original text instead of a hash
514
+
515
+ **Cause:** `hash()` with `algorithm: 'SHA256'` requires `secret`. When `secret` is `undefined`, it short-circuits and returns `text` unchanged.
516
+
517
+ **Fix:**
518
+
519
+ ```typescript
520
+ const hashed = hash('text', { algorithm: 'SHA256', secret: 'my-hmac-key', outputType: 'hex' });
521
+ ```
522
+
523
+ ## See also
524
+
525
+ - [Crypto overview](/extensions/helpers/crypto/) - introduction and the most common tasks
526
+ - [Crypto Utility](/references/utilities/crypto) - full `hash()` reference
527
+ - [Authentication Component](/extensions/components/authentication/) - JWT and password verification
528
+ - [Security Guidelines](/best-practices/security-guidelines) - cryptographic best practices