@venizia/ignis-docs 0.0.8-3 → 0.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.
Files changed (213) hide show
  1. package/README.md +7 -7
  2. package/{wiki → content}/best-practices/api-usage-examples.md +15 -12
  3. package/{wiki → content}/best-practices/architectural-patterns.md +70 -78
  4. package/{wiki → content}/best-practices/architecture-decisions.md +91 -60
  5. package/{wiki → content}/best-practices/code-style-standards/advanced-patterns.md +56 -44
  6. package/{wiki → content}/best-practices/code-style-standards/constants-configuration.md +11 -11
  7. package/{wiki → content}/best-practices/code-style-standards/control-flow.md +5 -2
  8. package/{wiki → content}/best-practices/code-style-standards/documentation.md +13 -13
  9. package/{wiki → content}/best-practices/code-style-standards/function-patterns.md +9 -10
  10. package/{wiki → content}/best-practices/code-style-standards/index.md +1 -1
  11. package/{wiki → content}/best-practices/code-style-standards/naming-conventions.md +10 -8
  12. package/{wiki → content}/best-practices/code-style-standards/route-definitions.md +30 -12
  13. package/{wiki → content}/best-practices/code-style-standards/tooling.md +8 -5
  14. package/{wiki → content}/best-practices/code-style-standards/type-safety.md +13 -12
  15. package/{wiki → content}/best-practices/common-pitfalls.md +56 -37
  16. package/{wiki → content}/best-practices/contribution-workflow.md +13 -14
  17. package/{wiki → content}/best-practices/data-modeling.md +44 -20
  18. package/{wiki → content}/best-practices/deployment-strategies.md +28 -27
  19. package/{wiki → content}/best-practices/error-handling.md +48 -24
  20. package/{wiki → content}/best-practices/index.md +5 -5
  21. package/{wiki → content}/best-practices/performance-optimization.md +36 -28
  22. package/{wiki → content}/best-practices/security-guidelines.md +52 -23
  23. package/{wiki → content}/best-practices/testing-strategies.md +65 -51
  24. package/{wiki → content}/best-practices/troubleshooting-tips.md +24 -24
  25. package/{wiki/extensions/components/swagger.md → content/extensions/components/api-reference.md} +40 -31
  26. package/{wiki → content}/extensions/components/authentication/api.md +19 -19
  27. package/{wiki → content}/extensions/components/authentication/errors.md +7 -7
  28. package/{wiki → content}/extensions/components/authentication/index.md +10 -8
  29. package/{wiki → content}/extensions/components/authentication/usage.md +101 -6
  30. package/{wiki → content}/extensions/components/authorization/api.md +45 -25
  31. package/{wiki → content}/extensions/components/authorization/errors.md +6 -6
  32. package/{wiki → content}/extensions/components/authorization/index.md +11 -10
  33. package/{wiki → content}/extensions/components/authorization/usage.md +21 -21
  34. package/{wiki → content}/extensions/components/health-check.md +1 -1
  35. package/{wiki → content}/extensions/components/index.md +5 -5
  36. package/{wiki → content}/extensions/components/mail/errors.md +15 -15
  37. package/{wiki → content}/extensions/components/mail/index.md +1 -2
  38. package/{wiki → content}/extensions/components/mail/usage.md +1 -1
  39. package/{wiki → content}/extensions/components/request-tracker.md +1 -1
  40. package/{wiki → content}/extensions/components/socket-io/api.md +9 -9
  41. package/{wiki → content}/extensions/components/socket-io/errors.md +5 -5
  42. package/{wiki → content}/extensions/components/socket-io/index.md +8 -8
  43. package/{wiki → content}/extensions/components/socket-io/usage.md +1 -1
  44. package/{wiki → content}/extensions/components/static-asset/api.md +17 -4
  45. package/{wiki → content}/extensions/components/static-asset/errors.md +4 -4
  46. package/{wiki → content}/extensions/components/static-asset/index.md +26 -28
  47. package/{wiki → content}/extensions/components/static-asset/usage.md +13 -12
  48. package/{wiki → content}/extensions/components/template/index.md +2 -2
  49. package/{wiki → content}/extensions/components/template/setup-page.md +1 -1
  50. package/{wiki → content}/extensions/components/websocket/api.md +3 -3
  51. package/{wiki → content}/extensions/components/websocket/errors.md +5 -5
  52. package/{wiki → content}/extensions/components/websocket/index.md +5 -5
  53. package/{wiki → content}/extensions/components/websocket/usage.md +3 -3
  54. package/{wiki → content}/extensions/helpers/cron/index.md +2 -2
  55. package/{wiki → content}/extensions/helpers/crypto/index.md +1 -1
  56. package/{wiki → content}/extensions/helpers/env/index.md +27 -12
  57. package/content/extensions/helpers/error/index.md +283 -0
  58. package/{wiki → content}/extensions/helpers/index.md +2 -3
  59. package/{wiki → content}/extensions/helpers/inversion/index.md +15 -7
  60. package/{wiki → content}/extensions/helpers/kafka/examples.md +1 -1
  61. package/{wiki → content}/extensions/helpers/logger/index.md +32 -2
  62. package/{wiki → content}/extensions/helpers/network/index.md +6 -0
  63. package/{wiki → content}/extensions/helpers/queue/index.md +14 -17
  64. package/content/extensions/helpers/redis/index.md +713 -0
  65. package/{wiki → content}/extensions/helpers/socket-io/index.md +14 -10
  66. package/{wiki → content}/extensions/helpers/storage/api.md +44 -8
  67. package/{wiki → content}/extensions/helpers/storage/index.md +43 -7
  68. package/{wiki → content}/extensions/helpers/template/index.md +6 -3
  69. package/{wiki → content}/extensions/helpers/types/index.md +11 -8
  70. package/{wiki → content}/extensions/helpers/websocket/api.md +9 -9
  71. package/{wiki → content}/extensions/helpers/websocket/index.md +7 -7
  72. package/{wiki → content}/extensions/helpers/worker-thread/index.md +2 -2
  73. package/{wiki → content}/extensions/index.md +3 -4
  74. package/{wiki → content}/extensions/src-details/mcp-server.md +18 -24
  75. package/{wiki → content}/guides/core-concepts/application/bootstrapping.md +11 -14
  76. package/{wiki → content}/guides/core-concepts/application/index.md +3 -3
  77. package/{wiki → content}/guides/core-concepts/components.md +19 -10
  78. package/{wiki → content}/guides/core-concepts/dependency-injection.md +6 -3
  79. package/{wiki → content}/guides/core-concepts/grpc-controllers.md +6 -5
  80. package/{wiki → content}/guides/core-concepts/persistent/datasources.md +33 -27
  81. package/{wiki → content}/guides/core-concepts/persistent/index.md +16 -5
  82. package/{wiki → content}/guides/core-concepts/persistent/models.md +24 -20
  83. package/content/guides/core-concepts/persistent/postgres-drivers.md +167 -0
  84. package/{wiki → content}/guides/core-concepts/persistent/repositories.md +40 -23
  85. package/content/guides/core-concepts/persistent/search-meilisearch.md +183 -0
  86. package/content/guides/core-concepts/persistent/search-typesense.md +429 -0
  87. package/{wiki → content}/guides/core-concepts/persistent/transactions.md +61 -25
  88. package/{wiki → content}/guides/core-concepts/rest-controllers.md +12 -9
  89. package/content/guides/core-concepts/services.md +389 -0
  90. package/{wiki → content}/guides/get-started/5-minute-quickstart.md +19 -19
  91. package/{wiki → content}/guides/get-started/philosophy.md +36 -36
  92. package/{wiki → content}/guides/get-started/setup.md +3 -3
  93. package/{wiki → content}/guides/index.md +3 -3
  94. package/content/guides/migrations/redis-helpers-migration.md +177 -0
  95. package/{wiki → content}/guides/migrations/scoped-rbac-migration.md +17 -17
  96. package/content/guides/migrations/unified-connectors-migration.md +113 -0
  97. package/{wiki → content}/guides/reference/glossary.md +19 -12
  98. package/{wiki → content}/guides/reference/mcp-docs-server.md +22 -18
  99. package/{wiki → content}/guides/tutorials/building-a-crud-api.md +30 -33
  100. package/{wiki → content}/guides/tutorials/complete-installation.md +17 -17
  101. package/{wiki → content}/guides/tutorials/ecommerce-api.md +158 -119
  102. package/{wiki → content}/guides/tutorials/realtime-chat.md +176 -130
  103. package/content/guides/tutorials/testing.md +264 -0
  104. package/content/index.md +5 -0
  105. package/content/public/apple-touch-icon.png +0 -0
  106. package/content/public/og-image.png +0 -0
  107. package/content/public/site.webmanifest +11 -0
  108. package/{wiki → content}/references/base/application.md +4 -5
  109. package/{wiki → content}/references/base/bootstrapping.md +18 -5
  110. package/{wiki → content}/references/base/components.md +149 -120
  111. package/content/references/base/connectors.md +178 -0
  112. package/{wiki → content}/references/base/controllers.md +41 -30
  113. package/content/references/base/datasources.md +527 -0
  114. package/{wiki → content}/references/base/dependency-injection.md +34 -22
  115. package/{wiki → content}/references/base/filter-system/application-usage.md +17 -14
  116. package/{wiki → content}/references/base/filter-system/array-operators.md +7 -2
  117. package/{wiki → content}/references/base/filter-system/comparison-operators.md +3 -0
  118. package/{wiki → content}/references/base/filter-system/default-filter.md +89 -71
  119. package/{wiki → content}/references/base/filter-system/fields-order-pagination.md +22 -22
  120. package/{wiki → content}/references/base/filter-system/index.md +6 -3
  121. package/{wiki → content}/references/base/filter-system/json-filtering.md +20 -1
  122. package/{wiki → content}/references/base/filter-system/list-operators.md +1 -1
  123. package/{wiki → content}/references/base/filter-system/logical-operators.md +33 -1
  124. package/{wiki → content}/references/base/filter-system/null-operators.md +30 -1
  125. package/{wiki → content}/references/base/filter-system/quick-reference.md +23 -4
  126. package/{wiki → content}/references/base/filter-system/tips.md +5 -5
  127. package/{wiki → content}/references/base/filter-system/use-cases.md +12 -12
  128. package/{wiki → content}/references/base/grpc-controllers.md +13 -13
  129. package/{wiki → content}/references/base/index.md +24 -12
  130. package/{wiki/references/base/middleware.md → content/references/base/middlewares.md} +205 -24
  131. package/{wiki → content}/references/base/models.md +63 -49
  132. package/{wiki → content}/references/base/providers.md +136 -130
  133. package/{wiki → content}/references/base/repositories/advanced.md +59 -58
  134. package/{wiki → content}/references/base/repositories/index.md +115 -91
  135. package/content/references/base/repositories/mixins.md +99 -0
  136. package/{wiki → content}/references/base/repositories/relations.md +54 -64
  137. package/{wiki → content}/references/base/repositories/soft-deletable.md +31 -30
  138. package/content/references/base/services.md +404 -0
  139. package/{wiki → content}/references/configuration/environment-variables.md +46 -30
  140. package/{wiki → content}/references/configuration/index.md +6 -6
  141. package/{wiki → content}/references/index.md +17 -12
  142. package/{wiki → content}/references/quick-reference.md +65 -106
  143. package/content/references/utilities/crypto.md +98 -0
  144. package/{wiki → content}/references/utilities/index.md +3 -3
  145. package/{wiki → content}/references/utilities/jsx.md +6 -4
  146. package/content/references/utilities/module.md +90 -0
  147. package/{wiki → content}/references/utilities/parse.md +4 -14
  148. package/{wiki → content}/references/utilities/promise.md +9 -7
  149. package/{wiki → content}/references/utilities/schema.md +5 -3
  150. package/dist/mcp-server/common/guards.d.ts +8 -0
  151. package/dist/mcp-server/common/guards.d.ts.map +1 -0
  152. package/dist/mcp-server/common/guards.js +14 -0
  153. package/dist/mcp-server/common/guards.js.map +1 -0
  154. package/dist/mcp-server/common/index.d.ts +1 -0
  155. package/dist/mcp-server/common/index.d.ts.map +1 -1
  156. package/dist/mcp-server/common/index.js +1 -0
  157. package/dist/mcp-server/common/index.js.map +1 -1
  158. package/dist/mcp-server/common/paths.d.ts.map +1 -1
  159. package/dist/mcp-server/common/paths.js +2 -2
  160. package/dist/mcp-server/common/paths.js.map +1 -1
  161. package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
  162. package/dist/mcp-server/helpers/docs.helper.js +4 -2
  163. package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
  164. package/dist/mcp-server/helpers/github.helper.js +1 -1
  165. package/dist/mcp-server/index.js +7 -2
  166. package/dist/mcp-server/index.js.map +1 -1
  167. package/dist/mcp-server/tools/base.tool.d.ts +6 -2
  168. package/dist/mcp-server/tools/base.tool.d.ts.map +1 -1
  169. package/dist/mcp-server/tools/base.tool.js.map +1 -1
  170. package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +1 -1
  171. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  172. package/dist/mcp-server/tools/github/search-code.tool.d.ts +1 -1
  173. package/dist/mcp-server/tools/github/search-code.tool.d.ts.map +1 -1
  174. package/dist/mcp-server/tools/github/search-code.tool.js +4 -1
  175. package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
  176. package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts.map +1 -1
  177. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +3 -1
  178. package/dist/mcp-server/tools/github/verify-dependencies.tool.js.map +1 -1
  179. package/package.json +12 -12
  180. package/wiki/extensions/helpers/error/index.md +0 -227
  181. package/wiki/extensions/helpers/redis/index.md +0 -488
  182. package/wiki/extensions/helpers/testing/index.md +0 -510
  183. package/wiki/guides/core-concepts/services.md +0 -119
  184. package/wiki/guides/tutorials/testing.md +0 -722
  185. package/wiki/index.md +0 -183
  186. package/wiki/references/base/datasources.md +0 -454
  187. package/wiki/references/base/middlewares.md +0 -590
  188. package/wiki/references/base/repositories/mixins.md +0 -335
  189. package/wiki/references/base/services.md +0 -201
  190. package/wiki/references/utilities/crypto.md +0 -56
  191. package/wiki/references/utilities/module.md +0 -42
  192. /package/{wiki → content}/extensions/components/mail/api.md +0 -0
  193. /package/{wiki → content}/extensions/components/template/api-page.md +0 -0
  194. /package/{wiki → content}/extensions/components/template/errors-page.md +0 -0
  195. /package/{wiki → content}/extensions/components/template/single-page.md +0 -0
  196. /package/{wiki → content}/extensions/components/template/usage-page.md +0 -0
  197. /package/{wiki → content}/extensions/helpers/kafka/admin.md +0 -0
  198. /package/{wiki → content}/extensions/helpers/kafka/consumer.md +0 -0
  199. /package/{wiki → content}/extensions/helpers/kafka/index.md +0 -0
  200. /package/{wiki → content}/extensions/helpers/kafka/producer.md +0 -0
  201. /package/{wiki → content}/extensions/helpers/kafka/schema-registry.md +0 -0
  202. /package/{wiki → content}/extensions/helpers/network/api.md +0 -0
  203. /package/{wiki → content}/extensions/helpers/socket-io/api.md +0 -0
  204. /package/{wiki → content}/extensions/helpers/template/single-page.md +0 -0
  205. /package/{wiki → content}/extensions/helpers/uid/index.md +0 -0
  206. /package/{wiki → content}/guides/core-concepts/components-guide.md +0 -0
  207. /package/{wiki → content}/public/logo.svg +0 -0
  208. /package/{wiki → content}/references/base/filter-system/pattern-matching.md +0 -0
  209. /package/{wiki → content}/references/base/filter-system/range-operators.md +0 -0
  210. /package/{wiki → content}/references/utilities/date.md +0 -0
  211. /package/{wiki → content}/references/utilities/performance.md +0 -0
  212. /package/{wiki → content}/references/utilities/request.md +0 -0
  213. /package/{wiki → content}/references/utilities/statuses.md +0 -0
@@ -1,510 +0,0 @@
1
- # Testing
2
-
3
- Structured test framework built on Node.js's native `node:test` module that organizes tests into plans, cases, and handlers with lifecycle hooks and shared context.
4
-
5
- ## Quick Reference
6
-
7
- | Item | Value |
8
- |------|-------|
9
- | **Package** | `@venizia/ignis-helpers` |
10
- | **Classes** | `TestPlan`, `BaseTestPlan`, `TestCase`, `TestCaseHandler`, `BaseTestCaseHandler`, `TestDescribe`, `AppTestDescribe`, `TestCaseDecisions` |
11
- | **Extends** | `BaseTestPlan` (uses `Logger` + `MemoryStorageHelper`, does not extend `BaseHelper`) |
12
- | **Runtimes** | Both |
13
-
14
- #### Import Paths
15
-
16
- ```typescript
17
- import {
18
- TestPlan,
19
- BaseTestPlan,
20
- TestCase,
21
- TestCaseHandler,
22
- BaseTestCaseHandler,
23
- TestDescribe,
24
- AppTestDescribe,
25
- TestCaseDecisions,
26
- } from '@venizia/ignis-helpers';
27
-
28
- import type {
29
- ITestContext,
30
- ITestPlan,
31
- ITestPlanOptions,
32
- ITestHooks,
33
- TTestHook,
34
- ITestCase,
35
- ITestCaseHandler,
36
- ITestCaseInput,
37
- ITestCaseHandlerOptions,
38
- ITestCaseOptions,
39
- TTestCaseDecision,
40
- } from '@venizia/ignis-helpers';
41
- ```
42
-
43
- ## Creating an Instance
44
-
45
- A test suite is assembled from three layers: a **TestCaseHandler** (execution + validation logic), a **TestCase** (metadata wrapper), and a **TestPlan** (orchestrator with hooks and shared context). The plan is then executed via **TestDescribe**.
46
-
47
- ```typescript
48
- import {
49
- TestPlan,
50
- TestDescribe,
51
- TestCase,
52
- TestCaseHandler,
53
- TestCaseDecisions,
54
- } from '@venizia/ignis-helpers';
55
- import type { ITestContext, TTestCaseDecision } from '@venizia/ignis-helpers';
56
-
57
- // 1. Define a handler
58
- class MyTestHandler extends TestCaseHandler {
59
- async execute() {
60
- return { result: 'some-value' };
61
- }
62
-
63
- getValidator() {
64
- return (opts: { result: string }): TTestCaseDecision => {
65
- if (opts.result === 'some-value') {
66
- return TestCaseDecisions.SUCCESS;
67
- }
68
- return TestCaseDecisions.FAIL;
69
- };
70
- }
71
- }
72
-
73
- // 2. Create a test plan
74
- const myTestPlan = TestPlan.newInstance({
75
- scope: 'My Feature',
76
- hooks: {
77
- before: async (testPlan) => console.log('Starting tests for:', testPlan.scope),
78
- after: async () => console.log('Finished tests.'),
79
- },
80
- testCases: [
81
- TestCase.withOptions({
82
- code: 'MY-FEATURE-001',
83
- description: 'It should return the correct value',
84
- expectation: 'The result should be "some-value"',
85
- handler: new MyTestHandler({ context: {} as any }),
86
- }),
87
- ],
88
- });
89
-
90
- // 3. Run the test plan
91
- TestDescribe.withTestPlan({ testPlan: myTestPlan }).run();
92
- ```
93
-
94
- ## Usage
95
-
96
- ### Shared Context
97
-
98
- `TestPlan` implements `ITestContext`, providing `bind()` and `getSync()` methods backed by a `MemoryStorageHelper` registry. Use this to share data between lifecycle hooks and test case handlers.
99
-
100
- ```typescript
101
- import {
102
- TestPlan,
103
- TestDescribe,
104
- TestCase,
105
- TestCaseHandler,
106
- TestCaseDecisions,
107
- } from '@venizia/ignis-helpers';
108
- import type { ITestPlan, TTestCaseDecision } from '@venizia/ignis-helpers';
109
-
110
- class SecureApiHandler extends TestCaseHandler<{ token: string }> {
111
- async execute() {
112
- const token = this.context.getSync<string>({ key: 'token' });
113
- const response = await app.request('/api/secure-data', {
114
- headers: { Authorization: `Bearer ${token}` },
115
- });
116
- return { status: response.status };
117
- }
118
-
119
- getValidator() {
120
- return (opts: { status: number }): TTestCaseDecision => {
121
- return opts.status === 200
122
- ? TestCaseDecisions.SUCCESS
123
- : TestCaseDecisions.FAIL;
124
- };
125
- }
126
- }
127
-
128
- const authTestPlan = TestPlan.newInstance<{ token: string }>({
129
- scope: 'Authentication',
130
- hooks: {
131
- before: async (testPlan: ITestPlan<{ token: string }>) => {
132
- const token = await generateTestToken();
133
- testPlan.bind({ key: 'token', value: token });
134
- },
135
- },
136
- testCases: [
137
- TestCase.withOptions({
138
- code: 'AUTH-001',
139
- description: 'Secure endpoint returns 200 with valid token',
140
- expectation: 'Response status is 200',
141
- handler: new SecureApiHandler({ context: {} as any }),
142
- }),
143
- ],
144
- });
145
-
146
- TestDescribe.withTestPlan({ testPlan: authTestPlan }).run();
147
- ```
148
-
149
- ### Test Case Resolver
150
-
151
- Instead of (or in addition to) providing `testCases` directly, supply a `testCaseResolver` function that dynamically generates test cases at plan construction time. The resolver receives the plan context. Both `testCases` and `testCaseResolver` results are concatenated.
152
-
153
- ```typescript
154
- const plan = TestPlan.newInstance({
155
- scope: 'Dynamic Tests',
156
- testCaseResolver: ({ context }) => {
157
- return endpoints.map((endpoint) =>
158
- TestCase.withOptions({
159
- code: `EP-${endpoint.name}`,
160
- description: `Test ${endpoint.name}`,
161
- expectation: 'Returns 200',
162
- handler: new EndpointHandler({ context }),
163
- }),
164
- );
165
- },
166
- });
167
- ```
168
-
169
- ### Handler Arguments
170
-
171
- Handlers support `args` (static) and `argResolver` (dynamic) for injecting test-specific input data. If both are omitted, `getArguments()` returns `null`. If both are provided, `args` takes priority.
172
-
173
- ```typescript
174
- class CreateUserHandler extends TestCaseHandler<{}, { name: string }> {
175
- async execute() {
176
- const args = this.getArguments(); // { name: 'Alice' }
177
- return await userService.create(args!);
178
- }
179
-
180
- getValidator() {
181
- return (user: { id: string; name: string }): TTestCaseDecision => {
182
- return user.name === 'Alice'
183
- ? TestCaseDecisions.SUCCESS
184
- : TestCaseDecisions.FAIL;
185
- };
186
- }
187
- }
188
-
189
- // Static args
190
- new CreateUserHandler({ context: {} as any, args: { name: 'Alice' } });
191
-
192
- // Dynamic args via resolver
193
- new CreateUserHandler({
194
- context: {} as any,
195
- argResolver: () => ({ name: 'Alice' }),
196
- });
197
- ```
198
-
199
- ### Lifecycle Hooks
200
-
201
- Hooks are registered via `ITestPlanOptions.hooks` and executed by `TestDescribe` using `node:test`'s `before`, `beforeEach`, `after`, and `afterEach` functions.
202
-
203
- | Hook | When | Purpose |
204
- |------|------|---------|
205
- | `before` | Before all tests | Setup (e.g., start server, seed database) |
206
- | `beforeEach` | Before each test | Reset state |
207
- | `afterEach` | After each test | Cleanup per test |
208
- | `after` | After all tests | Cleanup (e.g., close connections) |
209
-
210
- > [!NOTE]
211
- > Hook callbacks receive the full `ITestPlan` instance (not just the context), giving access to `bind()`, `getSync()`, `getTestCases()`, `getHooks()`, and `getRegistry()`.
212
-
213
- ```typescript
214
- const plan = TestPlan.newInstance<{ db: Database }>({
215
- scope: 'With Hooks',
216
- hooks: {
217
- before: async (testPlan) => {
218
- const db = await connectDatabase();
219
- testPlan.bind({ key: 'db', value: db });
220
- },
221
- afterEach: async (testPlan) => {
222
- const db = testPlan.getSync<Database>({ key: 'db' });
223
- await db.truncateAll();
224
- },
225
- after: async (testPlan) => {
226
- const db = testPlan.getSync<Database>({ key: 'db' });
227
- await db.close();
228
- },
229
- },
230
- testCases: [/* ... */],
231
- });
232
- ```
233
-
234
- ### Modifying Test Cases After Construction
235
-
236
- `BaseTestPlan` exposes `withTestCases()` for replacing the test case array after construction. This returns `this` for chaining.
237
-
238
- ```typescript
239
- const plan = TestPlan.newInstance({ scope: 'Mutable' });
240
- plan.withTestCases({
241
- testCases: [
242
- TestCase.withOptions({
243
- code: 'TC-001',
244
- description: 'Added after construction',
245
- expectation: 'Should pass',
246
- handler: myHandler,
247
- }),
248
- ],
249
- });
250
- ```
251
-
252
- > [!WARNING]
253
- > `withTestCases()` fully replaces the existing test case array rather than appending to it.
254
-
255
- ### TestCaseDecisions
256
-
257
- Test case validators must return one of these decision constants:
258
-
259
- | Decision | Value | Meaning |
260
- |----------|-------|---------|
261
- | `SUCCESS` | `'200_SUCCESS'` | Test passed |
262
- | `FAIL` | `'000_FAIL'` | Test failed |
263
- | `UNKNOWN` | `'000_UNKNOWN'` | No decision reached (treated as failure by `_execute()`) |
264
-
265
- The `_execute()` method on `TestCaseHandler` calls `assert.equal(validateRs, TestCaseDecisions.SUCCESS)`, so any value other than `'200_SUCCESS'` causes the test to fail.
266
-
267
- ## API Summary
268
-
269
- ### Class Hierarchy
270
-
271
- ```
272
- BaseTestCaseHandler (abstract)
273
- +-- TestCaseHandler (abstract) -- execute(), getValidator(), validate()
274
- +-- Your concrete handler
275
-
276
- BaseTestPlan (abstract)
277
- +-- TestPlan -- newInstance()
278
-
279
- TestDescribe -- withTestPlan(), run()
280
- +-- AppTestDescribe
281
- ```
282
-
283
- ### ITestPlanOptions
284
-
285
- | Option | Type | Default | Description |
286
- |--------|------|---------|-------------|
287
- | `scope` | `string` | -- | Name for the test suite (used as the `describe()` label). Required. |
288
- | `hooks` | `ITestHooks<R>` | `{}` | Lifecycle hooks (`before`, `beforeEach`, `after`, `afterEach`). |
289
- | `testCases` | `Array<ITestCase<R>>` | `[]` | Static list of test cases. |
290
- | `testCaseResolver` | `(opts: { context: ITestContext<R> }) => Array<ITestCase<R>>` | `undefined` | Dynamic test case generator, receives the plan context. |
291
-
292
- ### BaseTestPlan / TestPlan Methods
293
-
294
- | Method | Returns | Description |
295
- |--------|---------|-------------|
296
- | `TestPlan.newInstance(opts)` | `TestPlan<R>` | Static factory method. |
297
- | `withTestCases({ testCases })` | `this` | Replace the plan's test case array. |
298
- | `getTestCases()` | `Array<ITestCase<R>>` | Get all registered test cases. |
299
- | `getHooks()` | `ITestHooks<R>` | Get all lifecycle hooks. |
300
- | `getHook({ key })` | `TTestHook<R> \| null` | Get a specific hook by name. |
301
- | `getRegistry()` | `MemoryStorageHelper<R>` | Get the backing context registry. |
302
- | `getContext()` | `ITestContext<R>` | Returns `this` (the plan is the context). |
303
- | `bind({ key, value })` | `void` | Store a value in the context registry. |
304
- | `getSync({ key })` | `T` | Retrieve a value from the context registry. |
305
- | `execute()` | `void` | Run all test cases via `node:test` `it()` blocks. |
306
-
307
- ### ITestCaseOptions
308
-
309
- | Option | Type | Default | Description |
310
- |--------|------|---------|-------------|
311
- | `code` | `string` | -- | Unique test case identifier (e.g., `'AUTH-001'`). Required, must be non-empty. |
312
- | `name` | `string` | `undefined` | Optional short name for the test case. |
313
- | `description` | `string` | -- | What the test case does. Required, must be non-empty. |
314
- | `expectation` | `string` | `undefined` | Expected outcome description. Validated as required and non-empty by constructor. |
315
- | `handler` | `TestCaseHandler<R, I>` | -- | The handler that executes and validates the test. Required. |
316
-
317
- ### TestCase Methods
318
-
319
- | Method | Returns | Description |
320
- |--------|---------|-------------|
321
- | `TestCase.withOptions(opts)` | `TestCase<R, I>` | Static factory. Validates `code`, `description`, `expectation` are non-empty. |
322
- | `run()` | `Promise<void>` | Delegates to `handler._execute()`. |
323
-
324
- ### ITestCaseHandlerOptions
325
-
326
- | Option | Type | Default | Description |
327
- |--------|------|---------|-------------|
328
- | `scope` | `string` | `'TestCaseHandler'` | Logger scope. |
329
- | `context` | `ITestContext<R>` | -- | The test plan context for shared state. Required. |
330
- | `args` | `I \| null` | `null` | Static arguments for the handler. |
331
- | `argResolver` | `(...args: any[]) => I \| null` | `undefined` | Dynamic argument resolver, called once at construction. |
332
- | `validator` | `(opts: any) => ValueOrPromise<TTestCaseDecision>` | `undefined` | Validator function. Overrides `getValidator()` if provided. |
333
-
334
- ### TestCaseHandler Methods
335
-
336
- | Method | Returns | Description |
337
- |--------|---------|-------------|
338
- | `execute()` | `ValueOrPromise<any>` | **Abstract.** Perform the action under test. |
339
- | `getValidator()` | `((opts) => ValueOrPromise<TTestCaseDecision>) \| null` | **Abstract.** Return a validator function or `null`. |
340
- | `validate(opts)` | `ValueOrPromise<TTestCaseDecision>` | Runs the validator (from `this.validator` or `getValidator()`). |
341
- | `getArguments()` | `I \| null` | Returns the handler's `args`. |
342
- | `_execute()` | `Promise<void>` | Internal. Calls `execute()`, then `validate()`, then `assert.equal(result, SUCCESS)`. |
343
-
344
- ### TestDescribe Methods
345
-
346
- | Method | Returns | Description |
347
- |--------|---------|-------------|
348
- | `TestDescribe.withTestPlan({ testPlan })` | `TestDescribe<R>` | Static factory method. |
349
- | `run()` | `void` | Wraps the test plan in a `node:test` `describe()` block with all lifecycle hooks wired up. Throws if `testPlan` is not set. |
350
-
351
- ### Type Definitions
352
-
353
- #### ITestContext
354
-
355
- ```typescript
356
- interface ITestContext<R extends object> {
357
- scope: string;
358
- getRegistry: () => MemoryStorageHelper<R>;
359
- bind: <T>(opts: { key: string; value: T }) => void;
360
- getSync: <E = AnyType>(opts: { key: keyof R }) => E;
361
- }
362
- ```
363
-
364
- #### ITestPlan
365
-
366
- ```typescript
367
- interface ITestPlan<R extends object = {}> extends ITestContext<R> {
368
- getTestCases: () => Array<ITestCase<R>>;
369
- getContext: () => ITestContext<R>;
370
- getHooks: () => ITestHooks<R>;
371
- getHook: (opts: { key: keyof ITestHooks<R> }) => TTestHook<R> | null;
372
- execute: () => ValueOrPromise<void>;
373
- }
374
- ```
375
-
376
- #### ITestHooks / TTestHook
377
-
378
- ```typescript
379
- type TTestHook<R extends object> = (testPlan: ITestPlan<R>) => ValueOrPromise<void>;
380
-
381
- interface ITestHooks<R extends object> {
382
- before?: TTestHook<R>;
383
- beforeEach?: TTestHook<R>;
384
- after?: TTestHook<R>;
385
- afterEach?: TTestHook<R>;
386
- }
387
- ```
388
-
389
- #### ITestCase
390
-
391
- ```typescript
392
- interface ITestCase<R extends object = {}, I extends object = {}> {
393
- code: string;
394
- name?: string;
395
- description: string;
396
- expectation?: string;
397
- handler: ITestCaseHandler<R, I>;
398
- run: () => ValueOrPromise<void>;
399
- }
400
- ```
401
-
402
- #### ITestCaseHandler
403
-
404
- ```typescript
405
- interface ITestCaseHandler<R extends object = {}, I extends object = {}> {
406
- context: ITestContext<R>;
407
- args: I | null;
408
- validator?: (args: AnyObject) => ValueOrPromise<TTestCaseDecision>;
409
- }
410
- ```
411
-
412
- #### TTestCaseDecision
413
-
414
- ```typescript
415
- type TTestCaseDecision = '000_UNKNOWN' | '000_FAIL' | '200_SUCCESS';
416
- ```
417
-
418
- ## Troubleshooting
419
-
420
- ### "[validate] Invalid test case validator!"
421
-
422
- **Cause:** `TestCaseHandler.validate()` is called but neither a `validator` was passed in the constructor options nor does `getValidator()` return a function.
423
-
424
- **Fix:** Implement `getValidator()` to return a validation function, or pass a `validator` in the handler options:
425
-
426
- ```typescript
427
- // Option 1: Implement getValidator()
428
- class MyHandler extends TestCaseHandler {
429
- execute() { return { ok: true }; }
430
- getValidator() {
431
- return (opts: { ok: boolean }) =>
432
- opts.ok ? TestCaseDecisions.SUCCESS : TestCaseDecisions.FAIL;
433
- }
434
- }
435
-
436
- // Option 2: Pass validator in constructor options
437
- new MyHandler({
438
- context: {} as any,
439
- validator: (opts) => opts.ok ? TestCaseDecisions.SUCCESS : TestCaseDecisions.FAIL,
440
- });
441
- ```
442
-
443
- ### "[TestCase] Invalid value for key: \<key\> | value: \<value\> | Opts: ..."
444
-
445
- **Cause:** `TestCase.withOptions()` validates that `code`, `description`, and `expectation` are all non-empty strings. If any is missing or empty, this error is thrown.
446
-
447
- **Fix:** Ensure all three required fields are provided:
448
-
449
- ```typescript
450
- // Wrong -- missing expectation
451
- TestCase.withOptions({
452
- code: 'TC-001',
453
- description: 'Some test',
454
- handler: myHandler,
455
- });
456
-
457
- // Correct
458
- TestCase.withOptions({
459
- code: 'TC-001',
460
- description: 'Some test',
461
- expectation: 'Should return 200',
462
- handler: myHandler,
463
- });
464
- ```
465
-
466
- ### "[run] Invalid test plan!"
467
-
468
- **Cause:** `TestDescribe.run()` was called but `this.testPlan` is falsy. This happens if the `TestDescribe` instance was constructed without a valid test plan.
469
-
470
- **Fix:** Ensure a valid `ITestPlan` is provided via the constructor or `withTestPlan()`:
471
-
472
- ```typescript
473
- const describe = TestDescribe.withTestPlan({ testPlan: myTestPlan });
474
- describe.run();
475
- ```
476
-
477
- ### Tests run but always fail with assertion error
478
-
479
- **Cause:** The `_execute()` method on `TestCaseHandler` asserts that the validation result equals `TestCaseDecisions.SUCCESS` (`'200_SUCCESS'`). If your validator returns `undefined`, `null`, or a string that is not exactly `'200_SUCCESS'`, the assertion fails.
480
-
481
- **Fix:** Ensure your validator always returns one of the `TestCaseDecisions` constants and that the success path returns `TestCaseDecisions.SUCCESS` explicitly:
482
-
483
- ```typescript
484
- getValidator() {
485
- return (opts: { value: number }): TTestCaseDecision => {
486
- // Always return an explicit decision constant
487
- return opts.value > 0
488
- ? TestCaseDecisions.SUCCESS
489
- : TestCaseDecisions.FAIL;
490
- };
491
- }
492
- ```
493
-
494
- ### "Failed to execute test handler | Error: ..."
495
-
496
- **Cause:** An unhandled exception was thrown inside `execute()` or `validate()` within `_execute()`. The error is caught and logged, but `validateRs` remains `TestCaseDecisions.UNKNOWN`, causing the subsequent `assert.equal` to fail.
497
-
498
- **Fix:** Check the logged error message for the root cause. Common issues include missing context values (calling `getSync()` for a key that was never `bind()`-ed) or network/database errors in the handler's `execute()` method.
499
-
500
- ## See Also
501
-
502
- - **Related Concepts:**
503
- - [Dependency Injection](/guides/core-concepts/dependency-injection) -- Testing with DI
504
- - [Application](/guides/core-concepts/application/) -- Application lifecycle in tests
505
-
506
- - **Other Helpers:**
507
- - [Helpers Index](../index) -- All available helpers
508
-
509
- - **External Resources:**
510
- - [Node.js Test Runner](https://nodejs.org/api/test.html) -- Native `node:test` module documentation
@@ -1,119 +0,0 @@
1
- # Services
2
-
3
- Services contain your application's business logic, orchestrating data flow and executing use cases.
4
-
5
- > **Deep Dive:** See [Services Reference](../../references/base/services.md) for advanced patterns.
6
-
7
- ## The Business Logic Layer
8
-
9
- Services contain the core business logic of your application. They orchestrate the flow of data and execute the application's use cases. A service's primary responsibilities are:
10
-
11
- - **Encapsulating Business Rules**: Centralizing logic such as calculations, data validation, and process workflows.
12
- - **Coordinating Operations**: Using repositories to fetch and persist data, or orchestrating other services to execute complex business workflows.
13
- - **Reusing Business Logic**: Services can inject other services to reuse established logic, ensuring "Don't Repeat Yourself" (DRY) principles are maintained across the application.
14
- - **Isolating Controllers**: Keeping controllers thin by handling all the complex logic, so controllers are only responsible for handling the HTTP request and response.
15
-
16
- ### Creating a Service
17
-
18
- To create a service, extend the `BaseService` class and inject the repositories or other services it depends on.
19
-
20
- ```typescript
21
- import { BaseService, inject } from '@venizia/ignis';
22
- import { ConfigurationRepository } from '../repositories';
23
- import { UserRepository } from '../repositories';
24
- import { LoggingService } from './logging.service'; // Example of another service
25
- import { TConfiguration } from '../models/entities';
26
-
27
- export class ConfigurationService extends BaseService {
28
- constructor(
29
- @inject({ key: 'repositories.ConfigurationRepository' })
30
- private configurationRepository: ConfigurationRepository,
31
- @inject({ key: 'repositories.UserRepository' })
32
- private userRepository: UserRepository,
33
- @inject({ key: 'services.LoggingService' })
34
- private loggingService: LoggingService, // Injecting another service for reuse
35
- ) {
36
- super({ scope: ConfigurationService.name });
37
- }
38
-
39
- async createConfigurationForUser(opts: {
40
- userId: string;
41
- data: Partial<TConfiguration>;
42
- }): Promise<TConfiguration> {
43
- // Call another service logic
44
- await this.loggingService.audit({ message: `Creating config for user: ${opts.userId}` });
45
-
46
- // Business logic: Check if the user exists
47
- const user = await this.userRepository.findById({ id: opts.userId });
48
- // ...
49
- ```
50
-
51
- ### BaseService API
52
-
53
- `BaseService` is intentionally minimal. It extends `BaseHelper` to provide scoped logging:
54
-
55
- ```typescript
56
- export abstract class BaseService extends BaseHelper implements IService {
57
- constructor(opts: { scope: string }) {
58
- super({ scope: opts.scope });
59
- }
60
- }
61
- ```
62
-
63
- There is no built-in CRUD service -- implement business logic directly in your service methods. This keeps the service layer focused on your domain-specific operations rather than generic data access patterns (which belong in repositories).
64
-
65
- ## How Services Fit into the Architecture
66
-
67
- Services act as the primary layer for business logic, sitting between controllers and repositories. While controllers are the typical entry point, **services can also inject and call other services**. This enables powerful logic reuse and allows you to build complex use cases by composing smaller, specialized services.
68
-
69
- ```mermaid
70
- graph LR
71
- A[Client Request] --> B(Controller);
72
- B --> C1{Service A};
73
- C1 --> C2{Service B};
74
- C1 --> D1[Repository A];
75
- C2 --> D2[Repository B];
76
- D1 --> E((Database));
77
- D2 --> E;
78
-
79
- subgraph "Presentation Layer"
80
- B
81
- end
82
- subgraph "Business Logic Layer"
83
- C1
84
- C2
85
- end
86
- subgraph "Data Access Layer"
87
- D1
88
- D2
89
- end
90
- subgraph "Data Store"
91
- E
92
- end
93
- ```
94
-
95
- This layered architecture makes your application:
96
-
97
- - **More Organized:** Each layer has a clear and distinct responsibility.
98
- - **Easier to Test:** You can test your business logic in isolation by providing mock repositories to your services, without needing a live database.
99
- - **More Flexible:** You can change your database or data access implementation (e.g., switch from PostgreSQL to MySQL) by only changing the repository and datasource layer, with no changes to your business logic.
100
-
101
- ## See Also
102
-
103
- - **Related Concepts:**
104
- - [Controllers](/guides/core-concepts/rest-controllers) - Call services to handle requests
105
- - [Repositories](/guides/core-concepts/persistent/repositories) - Data access layer used by services
106
- - [Dependency Injection](/guides/core-concepts/dependency-injection) - Injecting dependencies into services
107
-
108
- - **References:**
109
- - [BaseService API](/references/base/services) - Complete API reference
110
- - [Providers](/references/base/providers) - Factory pattern for runtime instantiation
111
- - [Logger Helper](/extensions/helpers/logger/) - Logging in services
112
-
113
- - **Best Practices:**
114
- - [Architectural Patterns](/best-practices/architectural-patterns) - Service layer design
115
- - [Testing](/guides/tutorials/testing) - Unit testing services
116
-
117
- - **Tutorials:**
118
- - [Building a CRUD API](/guides/tutorials/building-a-crud-api) - Service examples
119
- - [E-commerce API](/guides/tutorials/ecommerce-api) - Complex business logic