@gunshi/docs 0.27.0-beta.4
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/LICENSE +20 -0
- package/package.json +52 -0
- package/src/guide/advanced/advanced-lazy-loading.md +312 -0
- package/src/guide/advanced/command-hooks.md +469 -0
- package/src/guide/advanced/context-extensions.md +545 -0
- package/src/guide/advanced/custom-rendering.md +945 -0
- package/src/guide/advanced/docs-gen.md +594 -0
- package/src/guide/advanced/internationalization.md +677 -0
- package/src/guide/advanced/type-system.md +561 -0
- package/src/guide/essentials/auto-usage.md +281 -0
- package/src/guide/essentials/composable.md +332 -0
- package/src/guide/essentials/declarative.md +724 -0
- package/src/guide/essentials/getting-started.md +252 -0
- package/src/guide/essentials/lazy-async.md +408 -0
- package/src/guide/essentials/plugin-system.md +472 -0
- package/src/guide/essentials/type-safe.md +154 -0
- package/src/guide/introduction/setup.md +68 -0
- package/src/guide/introduction/what-is-gunshi.md +68 -0
- package/src/guide/plugin/decorators.md +545 -0
- package/src/guide/plugin/dependencies.md +519 -0
- package/src/guide/plugin/extensions.md +317 -0
- package/src/guide/plugin/getting-started.md +298 -0
- package/src/guide/plugin/guidelines.md +940 -0
- package/src/guide/plugin/introduction.md +294 -0
- package/src/guide/plugin/lifecycle.md +432 -0
- package/src/guide/plugin/list.md +37 -0
- package/src/guide/plugin/testing.md +843 -0
- package/src/guide/plugin/type-system.md +529 -0
- package/src/index.md +44 -0
- package/src/release/v0.27.md +722 -0
- package/src/showcase.md +11 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
# Command Hooks
|
|
2
|
+
|
|
3
|
+
Gunshi provides powerful lifecycle hooks that allow you to intercept and control command execution at various stages.
|
|
4
|
+
|
|
5
|
+
These hooks enable advanced scenarios like logging, monitoring, validation, and error handling.
|
|
6
|
+
|
|
7
|
+
## Understanding Command Lifecycle
|
|
8
|
+
|
|
9
|
+
The command execution lifecycle in Gunshi follows these stages:
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
graph TD
|
|
13
|
+
Start([Start]) --> A[User Input]
|
|
14
|
+
A --> B[Parse Arguments]
|
|
15
|
+
B --> C[Apply Plugins]
|
|
16
|
+
C --> D[Resolve Command]
|
|
17
|
+
D --> E[Resolve Arguments]
|
|
18
|
+
E --> F[Create Command Context]
|
|
19
|
+
F --> G[onBeforeCommand Hook]
|
|
20
|
+
G --> H{Success?}
|
|
21
|
+
H -->|Yes| I[Execute Command]
|
|
22
|
+
H -->|Error| K[onErrorCommand Hook]
|
|
23
|
+
I --> J{Success?}
|
|
24
|
+
J -->|Yes| L[onAfterCommand Hook]
|
|
25
|
+
J -->|Error| K
|
|
26
|
+
L --> M{Success?}
|
|
27
|
+
M -->|Yes| N[Return Result]
|
|
28
|
+
M -->|Error| K
|
|
29
|
+
K --> O[Throw Error]
|
|
30
|
+
N --> P([End])
|
|
31
|
+
O --> P
|
|
32
|
+
|
|
33
|
+
style G fill:#468c56,color:white
|
|
34
|
+
style L fill:#468c56,color:white
|
|
35
|
+
style K fill:#468c56,color:white
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Available Hooks
|
|
39
|
+
|
|
40
|
+
Gunshi provides three main lifecycle hooks:
|
|
41
|
+
|
|
42
|
+
- **`onBeforeCommand`**: Executes before the command runs
|
|
43
|
+
- **`onAfterCommand`**: Executes after successful command completion
|
|
44
|
+
- **`onErrorCommand`**: Executes when a command throws an error
|
|
45
|
+
|
|
46
|
+
## Basic Hook Usage
|
|
47
|
+
|
|
48
|
+
### Setting Up Hooks
|
|
49
|
+
|
|
50
|
+
The following example demonstrates how to configure lifecycle hooks when initializing your CLI application.
|
|
51
|
+
|
|
52
|
+
In this setup, we define three hooks that will execute at different stages of the command lifecycle:
|
|
53
|
+
|
|
54
|
+
```ts [cli.ts]
|
|
55
|
+
import { cli } from 'gunshi'
|
|
56
|
+
|
|
57
|
+
await cli(
|
|
58
|
+
process.argv.slice(2),
|
|
59
|
+
{
|
|
60
|
+
name: 'server',
|
|
61
|
+
run: () => {
|
|
62
|
+
console.log('Starting server...')
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'my-app',
|
|
67
|
+
version: '1.0.0',
|
|
68
|
+
|
|
69
|
+
// Define lifecycle hooks
|
|
70
|
+
onBeforeCommand: ctx => {
|
|
71
|
+
console.log(`About to run: ${ctx.name}`)
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
onAfterCommand: (ctx, result) => {
|
|
75
|
+
console.log(`Command ${ctx.name} completed successfully`)
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
onErrorCommand: (ctx, error) => {
|
|
79
|
+
console.error(`Command ${ctx.name} failed:`, error)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
<!-- eslint-disable markdown/no-missing-label-refs -->
|
|
86
|
+
|
|
87
|
+
> [!TIP]
|
|
88
|
+
> The example fully code is [here](https://github.com/kazupon/gunshi/tree/main/playground/advanced/command-hooks).
|
|
89
|
+
|
|
90
|
+
<!-- eslint-enable markdown/no-missing-label-refs -->
|
|
91
|
+
|
|
92
|
+
### Hook Parameters
|
|
93
|
+
|
|
94
|
+
Each lifecycle hook receives specific parameters that provide context about the command execution.
|
|
95
|
+
|
|
96
|
+
The `CommandContext` parameter is read-only and contains all command metadata, while `onAfterCommand` also receives the command result and `onErrorCommand` receives the thrown error:
|
|
97
|
+
|
|
98
|
+
<!-- eslint-skip -->
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
{
|
|
102
|
+
// Before command execution
|
|
103
|
+
onBeforeCommand?: (ctx: Readonly<CommandContext>) => Awaitable<void>
|
|
104
|
+
|
|
105
|
+
// After successful execution
|
|
106
|
+
onAfterCommand?: (ctx: Readonly<CommandContext>, result: string | undefined) => Awaitable<void>
|
|
107
|
+
|
|
108
|
+
// On command error
|
|
109
|
+
onErrorCommand?: (ctx: Readonly<CommandContext>, error: Error) => Awaitable<void>
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
<!-- eslint-disable markdown/no-missing-label-refs -->
|
|
114
|
+
|
|
115
|
+
> [!NOTE]
|
|
116
|
+
> The `CommandContext` object provides comprehensive information about command execution. For the complete CommandContext API reference including all properties and types, see the [CommandContext interface documentation](/api/default/interfaces/CommandContext.md).
|
|
117
|
+
|
|
118
|
+
<!-- eslint-enable markdown/no-missing-label-refs -->
|
|
119
|
+
|
|
120
|
+
With an understanding of how hooks work and their parameters, let's explore how they differ from and interact with plugin decorators.
|
|
121
|
+
|
|
122
|
+
## Hooks vs Decorators
|
|
123
|
+
|
|
124
|
+
Gunshi provides two distinct mechanisms for controlling command execution:
|
|
125
|
+
|
|
126
|
+
1. **CLI-level Hooks**: Lifecycle hooks that run **before and after** command execution
|
|
127
|
+
- `onBeforeCommand`: Pre-execution processing (logging, validation, initialization)
|
|
128
|
+
- `onAfterCommand`: Post-success processing (cleanup, metrics recording)
|
|
129
|
+
- `onErrorCommand`: Error handling (error logging, rollback)
|
|
130
|
+
|
|
131
|
+
2. **Plugin Decorators**: **Wrap** the command itself to modify its behavior
|
|
132
|
+
- `decorateCommand`: Wraps command runner to add or modify functionality
|
|
133
|
+
- Multiple plugins can chain decorators (decorator pattern)
|
|
134
|
+
|
|
135
|
+
<!-- eslint-disable markdown/no-missing-label-refs -->
|
|
136
|
+
|
|
137
|
+
> [!TIP]
|
|
138
|
+
> The `decorateCommand` method is a powerful plugin API that allows wrapping command execution to add or modify functionality. It enables plugins to implement cross-cutting concerns like authentication, logging, and transaction management. For comprehensive information about how to use decorators in plugins, see the [Plugin Decorators documentation](/guide/plugin/decorators.md).
|
|
139
|
+
|
|
140
|
+
<!-- eslint-enable markdown/no-missing-label-refs -->
|
|
141
|
+
|
|
142
|
+
### Execution Flow
|
|
143
|
+
|
|
144
|
+
The following diagram illustrates how hooks and decorators interact during command execution:
|
|
145
|
+
|
|
146
|
+
```mermaid
|
|
147
|
+
graph TD
|
|
148
|
+
A[onBeforeCommand Hook] --> B{Success?}
|
|
149
|
+
B -->|Yes| C[Plugin Decorators Chain]
|
|
150
|
+
B -->|Error| G[onErrorCommand Hook]
|
|
151
|
+
|
|
152
|
+
C --> D[Decorated Command Runner]
|
|
153
|
+
D --> E{Success?}
|
|
154
|
+
|
|
155
|
+
E -->|Yes| F[onAfterCommand Hook]
|
|
156
|
+
E -->|Error| G
|
|
157
|
+
|
|
158
|
+
F --> H{Success?}
|
|
159
|
+
H -->|Yes| I[Return Result]
|
|
160
|
+
H -->|Error| G
|
|
161
|
+
|
|
162
|
+
G --> J[Throw Error]
|
|
163
|
+
|
|
164
|
+
style A fill:#468c56,color:white
|
|
165
|
+
style F fill:#468c56,color:white
|
|
166
|
+
style G fill:#468c56,color:white
|
|
167
|
+
style C fill:#7EA6E0,color:white
|
|
168
|
+
style D fill:#7EA6E0,color:white
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Detailed Execution Sequence
|
|
172
|
+
|
|
173
|
+
1. **`onBeforeCommand` Hook** - Pre-execution setup and validation
|
|
174
|
+
2. **Plugin Decorator Chain** - Command wrapping by plugins
|
|
175
|
+
- Applied in reverse order (LIFO - last registered, first executed)
|
|
176
|
+
- Each decorator wraps the next runner in the chain
|
|
177
|
+
3. **Command Runner** - Actual command execution
|
|
178
|
+
4. **`onAfterCommand` Hook** - Post-success processing
|
|
179
|
+
5. **`onErrorCommand` Hook** - Error handling when exceptions occur
|
|
180
|
+
|
|
181
|
+
### Plugin Decorator Example
|
|
182
|
+
|
|
183
|
+
The following example demonstrates how to use the `decorateCommand` method in a plugin to measure command execution time:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
import { plugin } from 'gunshi/plugin'
|
|
187
|
+
|
|
188
|
+
// Using decorateCommand in a plugin
|
|
189
|
+
export default plugin({
|
|
190
|
+
id: 'timing-plugin',
|
|
191
|
+
setup: ctx => {
|
|
192
|
+
// Wrap command execution to measure execution time
|
|
193
|
+
ctx.decorateCommand(baseRunner => {
|
|
194
|
+
return async commandCtx => {
|
|
195
|
+
const start = Date.now()
|
|
196
|
+
try {
|
|
197
|
+
const result = await baseRunner(commandCtx)
|
|
198
|
+
console.log(`Execution time: ${Date.now() - start}ms`)
|
|
199
|
+
return result
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.log(`Failed after: ${Date.now() - start}ms`)
|
|
202
|
+
throw error
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
<!-- eslint-disable markdown/no-missing-label-refs -->
|
|
211
|
+
|
|
212
|
+
> [!NOTE]
|
|
213
|
+
> Plugins don't have CLI-level hooks (`onBeforeCommand`, etc.). Instead, they use the `decorateCommand` method to wrap command execution and add custom logic. This allows plugins to extend and modify command behavior through the decorator pattern.
|
|
214
|
+
|
|
215
|
+
<!-- eslint-enable markdown/no-missing-label-refs -->
|
|
216
|
+
|
|
217
|
+
## Practical Use Cases
|
|
218
|
+
|
|
219
|
+
<!-- eslint-disable markdown/no-missing-label-refs -->
|
|
220
|
+
|
|
221
|
+
> [!TIP]
|
|
222
|
+
> The following examples use plugin extensions through `ctx.extensions`. Extensions are how plugins add functionality to the command context, allowing you to access plugin-provided features like logging, metrics, authentication, and database connections. For comprehensive information about working with extensions, including type-safe patterns and best practices, see the [Context Extensions documentation](./context-extensions.md).
|
|
223
|
+
|
|
224
|
+
<!-- eslint-enable markdown/no-missing-label-refs -->
|
|
225
|
+
|
|
226
|
+
### Logging and Monitoring
|
|
227
|
+
|
|
228
|
+
Implement comprehensive logging across all commands:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { cli } from 'gunshi'
|
|
232
|
+
import logger, { pluginId as loggerId } from '@my/plugin-logger'
|
|
233
|
+
|
|
234
|
+
await cli(process.argv.slice(2), commands, {
|
|
235
|
+
name: 'my-app',
|
|
236
|
+
|
|
237
|
+
// Install logger plugin
|
|
238
|
+
plugins: [logger()],
|
|
239
|
+
|
|
240
|
+
onBeforeCommand: ctx => {
|
|
241
|
+
const logger = ctx.extensions[loggerId]
|
|
242
|
+
// Log command start with arguments
|
|
243
|
+
logger?.info('Command started', {
|
|
244
|
+
command: ctx.name,
|
|
245
|
+
args: ctx.values,
|
|
246
|
+
timestamp: new Date().toISOString()
|
|
247
|
+
})
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
onAfterCommand: (ctx, result) => {
|
|
251
|
+
const logger = ctx.extensions[loggerId]
|
|
252
|
+
// Log successful completion
|
|
253
|
+
logger?.info('Command completed', {
|
|
254
|
+
command: ctx.name,
|
|
255
|
+
duration: Date.now() - logger?.startTime,
|
|
256
|
+
result: typeof result
|
|
257
|
+
})
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
onErrorCommand: (ctx, error) => {
|
|
261
|
+
const logger = ctx.extensions[loggerId]
|
|
262
|
+
// Log errors with full context
|
|
263
|
+
logger?.error('Command failed', {
|
|
264
|
+
command: ctx.name,
|
|
265
|
+
error: error.message,
|
|
266
|
+
stack: error.stack,
|
|
267
|
+
args: ctx.values
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
})
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Performance Monitoring
|
|
274
|
+
|
|
275
|
+
Track command execution times and performance metrics:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import { cli } from 'gunshi'
|
|
279
|
+
import metrics, { pluginId as metricsId } from '@my/plugin-metrics'
|
|
280
|
+
|
|
281
|
+
await cli(process.argv.slice(2), commands, {
|
|
282
|
+
name: 'my-app',
|
|
283
|
+
|
|
284
|
+
// Install metrics plugin
|
|
285
|
+
plugins: [metrics()],
|
|
286
|
+
|
|
287
|
+
onBeforeCommand: ctx => {
|
|
288
|
+
const metrics = ctx.extensions[metricsId]
|
|
289
|
+
// Start tracking command execution
|
|
290
|
+
metrics?.startTracking({
|
|
291
|
+
command: ctx.name,
|
|
292
|
+
args: ctx.values,
|
|
293
|
+
environment: process.env.NODE_ENV
|
|
294
|
+
})
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
onAfterCommand: async (ctx, result) => {
|
|
298
|
+
const metrics = ctx.extensions[metricsId]
|
|
299
|
+
// Record successful completion
|
|
300
|
+
const duration = metrics?.endTracking()
|
|
301
|
+
|
|
302
|
+
// Send metrics to monitoring service
|
|
303
|
+
await metrics?.send({
|
|
304
|
+
command: ctx.name,
|
|
305
|
+
status: 'success',
|
|
306
|
+
duration,
|
|
307
|
+
memoryUsage: process.memoryUsage(),
|
|
308
|
+
resultSize: result?.length || 0
|
|
309
|
+
})
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
onErrorCommand: async (ctx, error) => {
|
|
313
|
+
const metrics = ctx.extensions[metricsId]
|
|
314
|
+
// Record failure metrics
|
|
315
|
+
const duration = metrics?.endTracking()
|
|
316
|
+
|
|
317
|
+
// Send error metrics with additional context
|
|
318
|
+
await metrics?.send({
|
|
319
|
+
command: ctx.name,
|
|
320
|
+
status: 'failed',
|
|
321
|
+
duration,
|
|
322
|
+
error: error.message,
|
|
323
|
+
errorType: error.constructor.name,
|
|
324
|
+
stackTrace: error.stack
|
|
325
|
+
})
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Validation and Guards
|
|
331
|
+
|
|
332
|
+
Use hooks to implement global validation or access control:
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
import { cli } from 'gunshi'
|
|
336
|
+
import auth, { pluginId as authId } from '@my/plugin-auth'
|
|
337
|
+
|
|
338
|
+
await cli(process.argv.slice(2), commands, {
|
|
339
|
+
name: 'my-app',
|
|
340
|
+
|
|
341
|
+
// Install auth plugin
|
|
342
|
+
plugins: [
|
|
343
|
+
auth({
|
|
344
|
+
publicCommands: ['help', 'version', 'login'],
|
|
345
|
+
tokenSource: ['env:AUTH_TOKEN', 'args:token']
|
|
346
|
+
})
|
|
347
|
+
],
|
|
348
|
+
|
|
349
|
+
onBeforeCommand: async ctx => {
|
|
350
|
+
const auth = ctx.extensions[authId]
|
|
351
|
+
|
|
352
|
+
// Skip auth for public commands
|
|
353
|
+
if (auth?.isPublicCommand(ctx.name)) {
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Verify authentication
|
|
358
|
+
const token = auth?.getToken()
|
|
359
|
+
if (!token) {
|
|
360
|
+
throw new Error('Authentication required. Please run "login" first.')
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const user = await auth?.verifyToken(token)
|
|
364
|
+
if (!user) {
|
|
365
|
+
throw new Error('Invalid or expired token. Please login again.')
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Store user info for command use
|
|
369
|
+
await auth?.setCurrentUser(user)
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
onAfterCommand: async ctx => {
|
|
373
|
+
const auth = ctx.extensions[authId]
|
|
374
|
+
// Clean up sensitive data after command execution
|
|
375
|
+
await auth?.clearSession()
|
|
376
|
+
},
|
|
377
|
+
|
|
378
|
+
onErrorCommand: async (ctx, error) => {
|
|
379
|
+
const auth = ctx.extensions[authId]
|
|
380
|
+
// Log security-related errors
|
|
381
|
+
if (error.message.includes('Authentication') || error.message.includes('token')) {
|
|
382
|
+
await auth?.logSecurityEvent({
|
|
383
|
+
type: 'auth_failure',
|
|
384
|
+
command: ctx.name,
|
|
385
|
+
timestamp: new Date().toISOString()
|
|
386
|
+
})
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
})
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Transaction Management
|
|
393
|
+
|
|
394
|
+
Implement database transactions or rollback mechanisms:
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
import { cli } from 'gunshi'
|
|
398
|
+
import database, { pluginId as dbId } from '@my/plugin-database'
|
|
399
|
+
|
|
400
|
+
await cli(process.argv.slice(2), commands, {
|
|
401
|
+
name: 'my-app',
|
|
402
|
+
|
|
403
|
+
// Install database plugin with transaction support
|
|
404
|
+
plugins: [
|
|
405
|
+
database({
|
|
406
|
+
connectionString: process.env.DATABASE_URL,
|
|
407
|
+
transactionalCommands: ['create', 'update', 'delete', 'migrate']
|
|
408
|
+
})
|
|
409
|
+
],
|
|
410
|
+
|
|
411
|
+
onBeforeCommand: async ctx => {
|
|
412
|
+
const db = ctx.extensions[dbId]
|
|
413
|
+
|
|
414
|
+
// Start transaction for data-modifying commands
|
|
415
|
+
if (db?.isTransactionalCommand(ctx.name)) {
|
|
416
|
+
const transaction = await db.beginTransaction()
|
|
417
|
+
|
|
418
|
+
// Store transaction ID for tracking
|
|
419
|
+
await db?.setCurrentTransaction(transaction.id)
|
|
420
|
+
|
|
421
|
+
console.log(`Transaction ${transaction.id} started for command: ${ctx.name}`)
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
|
|
425
|
+
onAfterCommand: async (ctx, result) => {
|
|
426
|
+
const db = ctx.extensions[dbId]
|
|
427
|
+
const transaction = db?.getCurrentTransaction()
|
|
428
|
+
|
|
429
|
+
if (transaction) {
|
|
430
|
+
// Commit on success
|
|
431
|
+
await db.commit(transaction.id)
|
|
432
|
+
console.log(`Transaction ${transaction.id} committed successfully`)
|
|
433
|
+
|
|
434
|
+
// Clean up transaction reference
|
|
435
|
+
await db.clearCurrentTransaction()
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
onErrorCommand: async (ctx, error) => {
|
|
440
|
+
const db = ctx.extensions[dbId]
|
|
441
|
+
const transaction = db?.getCurrentTransaction()
|
|
442
|
+
|
|
443
|
+
if (transaction) {
|
|
444
|
+
// Rollback on error
|
|
445
|
+
await db?.rollback(transaction.id)
|
|
446
|
+
console.error(`Transaction ${transaction.id} rolled back due to error:`, error.message)
|
|
447
|
+
|
|
448
|
+
// Log the failed transaction for audit
|
|
449
|
+
await db?.logTransactionFailure({
|
|
450
|
+
id: transaction.id,
|
|
451
|
+
command: ctx.name,
|
|
452
|
+
error: error.message,
|
|
453
|
+
timestamp: new Date().toISOString()
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
// Clean up transaction reference
|
|
457
|
+
await db?.clearCurrentTransaction()
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
})
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
## Hook Execution Order
|
|
464
|
+
|
|
465
|
+
When multiple hooks and decorators are present, they execute in a specific sequence as shown in the Hooks vs Decorators section above.
|
|
466
|
+
|
|
467
|
+
Understanding this order is crucial for implementing complex behaviors like transaction management or error recovery.
|
|
468
|
+
|
|
469
|
+
For detailed execution flow, refer to the execution diagram in the [Hooks vs Decorators](#hooks-vs-decorators) section.
|