@tsed/directus-sdk 8.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.barrelsby.json +5 -0
- package/lib/esm/bootstrap/attach-logger.js +25 -0
- package/lib/esm/bootstrap/runtime.js +11 -0
- package/lib/esm/decorators/cache.js +60 -0
- package/lib/esm/fn/defineEndpoint.js +82 -0
- package/lib/esm/fn/defineHook.js +134 -0
- package/lib/esm/fn/defineOperationApi.js +120 -0
- package/lib/esm/fn/wrapEndpoint.js +80 -0
- package/lib/esm/fn/wrapOperation.js +63 -0
- package/lib/esm/index.js +14 -0
- package/lib/esm/services/DirectusCacheInterceptor.js +184 -0
- package/lib/esm/services/DirectusContextService.js +162 -0
- package/lib/esm/services/DirectusItemsRepository.js +240 -0
- package/lib/types/bootstrap/attach-logger.d.ts +1 -0
- package/lib/types/bootstrap/runtime.d.ts +1 -0
- package/lib/types/decorators/cache.d.ts +57 -0
- package/lib/types/fn/defineEndpoint.d.ts +73 -0
- package/lib/types/fn/defineHook.d.ts +105 -0
- package/lib/types/fn/defineOperationApi.d.ts +114 -0
- package/lib/types/fn/wrapEndpoint.d.ts +35 -0
- package/lib/types/fn/wrapOperation.d.ts +35 -0
- package/lib/types/index.d.ts +14 -0
- package/lib/types/services/DirectusCacheInterceptor.d.ts +154 -0
- package/lib/types/services/DirectusContextService.d.ts +149 -0
- package/lib/types/services/DirectusItemsRepository.d.ts +226 -0
- package/package.json +63 -0
- package/readme.md +493 -0
package/.barrelsby.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import "@tsed/logger-connect";
|
|
2
|
+
import { useLogger } from "@directus/api/logger/index";
|
|
3
|
+
import { attachLogger } from "@tsed/di";
|
|
4
|
+
import { $log } from "@tsed/logger";
|
|
5
|
+
const cmsLogger = useLogger();
|
|
6
|
+
function print(o) {
|
|
7
|
+
if (o?.data?.length && typeof o?.data[0] === "string") {
|
|
8
|
+
return o?.data[0];
|
|
9
|
+
}
|
|
10
|
+
return JSON.stringify(o, null, 2);
|
|
11
|
+
}
|
|
12
|
+
$log.appenders.clear();
|
|
13
|
+
$log.appenders.set("stdout", {
|
|
14
|
+
type: "connect",
|
|
15
|
+
options: {
|
|
16
|
+
logger: {
|
|
17
|
+
info: (o) => cmsLogger.info(print(o)),
|
|
18
|
+
warn: (o) => cmsLogger.warn(print(o)),
|
|
19
|
+
debug: (o) => cmsLogger.debug(print(o)),
|
|
20
|
+
trace: (o) => cmsLogger.trace(print(o)),
|
|
21
|
+
error: (o) => cmsLogger.error(print(o))
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
attachLogger($log);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import "./attach-logger.js";
|
|
2
|
+
import { withOptions } from "@tsed/config";
|
|
3
|
+
import { EnvsConfigSource } from "@tsed/config/envs";
|
|
4
|
+
import { configuration } from "@tsed/di";
|
|
5
|
+
configuration().set({
|
|
6
|
+
extends: [
|
|
7
|
+
withOptions(EnvsConfigSource, {
|
|
8
|
+
name: "envs"
|
|
9
|
+
})
|
|
10
|
+
]
|
|
11
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Intercept } from "@tsed/di";
|
|
2
|
+
import { DirectusCacheInterceptor } from "../services/DirectusCacheInterceptor.js";
|
|
3
|
+
/**
|
|
4
|
+
* Decorator to cache method results using Directus cache system.
|
|
5
|
+
*
|
|
6
|
+
* This decorator uses the Directus cache infrastructure (system cache by default) to store
|
|
7
|
+
* method results. Cached values are retrieved on subsequent calls with the same arguments,
|
|
8
|
+
* avoiding redundant computations or API calls.
|
|
9
|
+
*
|
|
10
|
+
* ### Basic usage
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* import {Injectable} from "@tsed/di";
|
|
14
|
+
* import {Cache} from "@tsed/directus-sdk";
|
|
15
|
+
*
|
|
16
|
+
* @Injectable()
|
|
17
|
+
* export class JiraIssueClient {
|
|
18
|
+
* // Cache search results for 15 minutes (default TTL)
|
|
19
|
+
* @Cache({ ttl: 900000 })
|
|
20
|
+
* search(query: string) {
|
|
21
|
+
* // Expensive API call
|
|
22
|
+
* return this.fetchFromJira(query);
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* // Custom cache key generator
|
|
26
|
+
* @Cache({
|
|
27
|
+
* ttl: 60000,
|
|
28
|
+
* keyGenerator: (userId: string) => `user:${userId}`
|
|
29
|
+
* })
|
|
30
|
+
* getUserById(userId: string) {
|
|
31
|
+
* return this.fetchUser(userId);
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* // Use regular cache instead of system cache
|
|
35
|
+
* @Cache({
|
|
36
|
+
* ttl: 300000,
|
|
37
|
+
* useSystemCache: false,
|
|
38
|
+
* namespace: "custom:namespace"
|
|
39
|
+
* })
|
|
40
|
+
* getCustomData() {
|
|
41
|
+
* return this.fetchCustomData();
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @param options - Cache configuration options
|
|
47
|
+
* @param options.ttl - Time to live in milliseconds (default: 900000ms = 15 minutes)
|
|
48
|
+
* @param options.keyGenerator - Custom function to generate cache keys from method arguments
|
|
49
|
+
* @param options.namespace - Custom namespace for cache keys (default: className:methodName)
|
|
50
|
+
* @param options.useSystemCache - Whether to use system cache (true) or regular cache (false). Default: true
|
|
51
|
+
*
|
|
52
|
+
* @returns A method decorator that intercepts method calls and caches results
|
|
53
|
+
*
|
|
54
|
+
* @decorator
|
|
55
|
+
* @see {@link DirectusCacheInterceptor} for implementation details
|
|
56
|
+
* @see {@link DirectusCacheOptions} for available options
|
|
57
|
+
*/
|
|
58
|
+
export function Cache(options) {
|
|
59
|
+
return Intercept(DirectusCacheInterceptor, options);
|
|
60
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { defineEndpoint as oDefineEndpoint } from "@directus/extensions-sdk";
|
|
2
|
+
import { wrapEndpoint } from "./wrapEndpoint.js";
|
|
3
|
+
/**
|
|
4
|
+
* Defines a Directus endpoint with Ts.ED dependency injection support.
|
|
5
|
+
*
|
|
6
|
+
* This function wraps the Directus SDK's `defineEndpoint` to automatically integrate
|
|
7
|
+
* Ts.ED's DI container, allowing you to use `inject()` to access services within
|
|
8
|
+
* your endpoint handlers. It also provides automatic error handling and logging.
|
|
9
|
+
*
|
|
10
|
+
* ### Basic endpoint with service injection
|
|
11
|
+
* ```ts
|
|
12
|
+
* import {defineEndpoint} from "@tsed/directus-sdk";
|
|
13
|
+
* import {inject} from "@tsed/di";
|
|
14
|
+
*
|
|
15
|
+
* export default defineEndpoint({
|
|
16
|
+
* id: "my-custom-endpoint",
|
|
17
|
+
* handler: (router) => {
|
|
18
|
+
* router.get("/hello", async (req, res) => {
|
|
19
|
+
* const myService = inject(MyService);
|
|
20
|
+
* const result = await myService.doSomething();
|
|
21
|
+
*
|
|
22
|
+
* return res.json(result);
|
|
23
|
+
* });
|
|
24
|
+
* }
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* ### Function-only configuration
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* import {defineEndpoint} from "@tsed/directus-sdk";
|
|
32
|
+
*
|
|
33
|
+
* export default defineEndpoint((router, context) => {
|
|
34
|
+
* router.post("/webhook", async (req, res) => {
|
|
35
|
+
* const myService = inject(MyService);
|
|
36
|
+
* const result = await myService.doSomething();
|
|
37
|
+
* // Handle webhook
|
|
38
|
+
* res.json({ success: true });
|
|
39
|
+
* });
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* ### With multiple routes and error handling
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* import {defineEndpoint} from "@tsed/directus-sdk";
|
|
47
|
+
* import {inject} from "@tsed/di";
|
|
48
|
+
*
|
|
49
|
+
* export default defineEndpoint({
|
|
50
|
+
* id: "api-v1",
|
|
51
|
+
* handler: (router) => {
|
|
52
|
+
* router.get("/users", async (req, res) => {
|
|
53
|
+
* const userService = inject(UserService);
|
|
54
|
+
* const users = await userService.findAll();
|
|
55
|
+
* res.json(users);
|
|
56
|
+
* });
|
|
57
|
+
*
|
|
58
|
+
* router.post("/auth/login", async (req, res) => {
|
|
59
|
+
* const authService = inject(AuthService);
|
|
60
|
+
* const token = await authService.authenticate(req.body);
|
|
61
|
+
* res.json({ token });
|
|
62
|
+
* });
|
|
63
|
+
* }
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
66
|
+
* @param config - The endpoint configuration. Can be either:
|
|
67
|
+
* - A function that receives the router and context
|
|
68
|
+
* - An object with `id` and `handler` properties
|
|
69
|
+
*
|
|
70
|
+
* @returns The configured endpoint ready to be exported from your extension file
|
|
71
|
+
* @see {@link wrapEndpoint} for the underlying wrapper implementation
|
|
72
|
+
* @see {@link https://docs.directus.io/extensions/endpoints.html} Directus Endpoints Documentation
|
|
73
|
+
*/
|
|
74
|
+
export function defineEndpoint(config) {
|
|
75
|
+
if (typeof config === "function") {
|
|
76
|
+
return oDefineEndpoint(wrapEndpoint(config));
|
|
77
|
+
}
|
|
78
|
+
return oDefineEndpoint({
|
|
79
|
+
...config,
|
|
80
|
+
handler: wrapEndpoint(config.handler)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import "../bootstrap/runtime.js";
|
|
2
|
+
import { DIContext, inject, injector, runInContext } from "@tsed/di";
|
|
3
|
+
import { nanoid } from "nanoid";
|
|
4
|
+
import { DirectusContextService } from "../services/DirectusContextService.js";
|
|
5
|
+
/**
|
|
6
|
+
* Defines a Directus hook with Ts.ED dependency injection support.
|
|
7
|
+
*
|
|
8
|
+
* This function wraps your hook callback to provide:
|
|
9
|
+
* - Automatic Ts.ED DI container initialization
|
|
10
|
+
* - Service injection via `inject()` function
|
|
11
|
+
* - Proper error handling and logging
|
|
12
|
+
* - Access to Directus context through `DirectusContextService`
|
|
13
|
+
*
|
|
14
|
+
* Hooks allow you to trigger custom logic when certain events occur in Directus,
|
|
15
|
+
* such as when items are created, updated, or deleted.
|
|
16
|
+
*
|
|
17
|
+
* ### Basic hook with filters
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* import {defineHook} from "@tsed/directus-sdk";
|
|
21
|
+
* import {inject} from "@tsed/di";
|
|
22
|
+
*
|
|
23
|
+
* export default defineHook(({filter, action}) => {
|
|
24
|
+
* const validationService = inject(ValidationService);
|
|
25
|
+
*
|
|
26
|
+
* // Filter runs before the event (can modify data or cancel operation)
|
|
27
|
+
* filter("items.create", async (payload, meta) => {
|
|
28
|
+
* await validationService.validate(payload);
|
|
29
|
+
* return payload;
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* // Action runs after the event (cannot modify data)
|
|
33
|
+
* action("items.create", async (meta) => {
|
|
34
|
+
* console.log("Item created:", meta.key);
|
|
35
|
+
* });
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* ### Hook with service injection and multiple events
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* import {defineHook} from "@tsed/directus-sdk";
|
|
43
|
+
* import {inject} from "@tsed/di";
|
|
44
|
+
* import {logger} from "@tsed/logger";
|
|
45
|
+
*
|
|
46
|
+
* export default defineHook(({filter, action}) => {
|
|
47
|
+
* const productService = inject(ProductService);
|
|
48
|
+
* const notificationService = inject(NotificationService);
|
|
49
|
+
*
|
|
50
|
+
* logger().info("Product hooks loaded");
|
|
51
|
+
*
|
|
52
|
+
* // Ensure unique product rankings on create
|
|
53
|
+
* filter("product_rankings.items.create", async (payload) => {
|
|
54
|
+
* return productService.ensureUniqueRanking(payload);
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* // Ensure unique product rankings on update
|
|
58
|
+
* filter("product_rankings.items.update", async (payload) => {
|
|
59
|
+
* return productService.ensureUniqueRanking(payload);
|
|
60
|
+
* });
|
|
61
|
+
*
|
|
62
|
+
* // Send notification when product is published
|
|
63
|
+
* action("products.items.update", async (meta, context) => {
|
|
64
|
+
* if (context.payload.status === "published") {
|
|
65
|
+
* await notificationService.sendPublishedNotification(meta.key);
|
|
66
|
+
* }
|
|
67
|
+
* });
|
|
68
|
+
*
|
|
69
|
+
* // Return services for debugging/testing
|
|
70
|
+
* return {
|
|
71
|
+
* productService,
|
|
72
|
+
* notificationService
|
|
73
|
+
* };
|
|
74
|
+
* });
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* ### Hook with init and schedule
|
|
78
|
+
*
|
|
79
|
+
* ```ts
|
|
80
|
+
* import {defineHook} from "@tsed/directus-sdk";
|
|
81
|
+
* import {inject} from "@tsed/di";
|
|
82
|
+
*
|
|
83
|
+
* export default defineHook(({init, schedule}) => {
|
|
84
|
+
* const cacheService = inject(CacheService);
|
|
85
|
+
* const syncService = inject(SyncService);
|
|
86
|
+
*
|
|
87
|
+
* // Run once when Directus starts
|
|
88
|
+
* init("app.before", async () => {
|
|
89
|
+
* await cacheService.warmup();
|
|
90
|
+
* });
|
|
91
|
+
*
|
|
92
|
+
* // Run on a schedule (cron syntax)
|
|
93
|
+
* schedule("0 * * * *", async () => {
|
|
94
|
+
* await syncService.hourlySync();
|
|
95
|
+
* });
|
|
96
|
+
* });
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* @param callback - The hook initialization function that receives:
|
|
100
|
+
* - `register` - Functions to register event handlers (filter, action, init, schedule, embed)
|
|
101
|
+
* - `context` - The Directus hook extension context
|
|
102
|
+
*
|
|
103
|
+
* @returns A wrapped hook function ready to be exported from your extension file
|
|
104
|
+
* @see {@link DirectusContextService} to access Directus context within injected services
|
|
105
|
+
* @see {@link https://docs.directus.io/extensions/hooks.html} Directus Hooks Documentation
|
|
106
|
+
*/
|
|
107
|
+
export function defineHook(callback) {
|
|
108
|
+
return async (register, context) => {
|
|
109
|
+
await injector().load();
|
|
110
|
+
const $ctx = new DIContext({
|
|
111
|
+
id: nanoid(),
|
|
112
|
+
platform: "DIRECTUS_HOOK",
|
|
113
|
+
maxStackSize: 0
|
|
114
|
+
});
|
|
115
|
+
return runInContext($ctx, async () => {
|
|
116
|
+
try {
|
|
117
|
+
inject(DirectusContextService).set(context);
|
|
118
|
+
return await callback(register, context);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
$ctx.logger.error({
|
|
122
|
+
error_name: error.name,
|
|
123
|
+
error_message: error.message,
|
|
124
|
+
error_description: error.description,
|
|
125
|
+
error_stack: error.stack
|
|
126
|
+
});
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
$ctx.logger.flush();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { defineOperationApi as oDefineOperationApi } from "@directus/extensions-sdk";
|
|
2
|
+
import { wrapOperation } from "./wrapOperation.js";
|
|
3
|
+
/**
|
|
4
|
+
* Defines a Directus operation with Ts.ED dependency injection support.
|
|
5
|
+
*
|
|
6
|
+
* This function wraps the Directus SDK's `defineOperationApi` to automatically integrate
|
|
7
|
+
* Ts.ED's DI container, allowing you to use `inject()` to access services within your
|
|
8
|
+
* operation handlers. Operations are custom logic that can be used in Directus flows.
|
|
9
|
+
*
|
|
10
|
+
* ### Basic operation with service injection
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* import {defineOperationApi} from "@tsed/directus-sdk";
|
|
14
|
+
* import {inject} from "@tsed/di";
|
|
15
|
+
*
|
|
16
|
+
* type SendEmailOptions = {
|
|
17
|
+
* to: string;
|
|
18
|
+
* subject: string;
|
|
19
|
+
* body: string;
|
|
20
|
+
* };
|
|
21
|
+
*
|
|
22
|
+
* export default defineOperationApi<SendEmailOptions>({
|
|
23
|
+
* id: "send-email",
|
|
24
|
+
* handler: async (options, context) => {
|
|
25
|
+
* const emailService = inject(EmailService);
|
|
26
|
+
*
|
|
27
|
+
* await emailService.send({
|
|
28
|
+
* to: options.to,
|
|
29
|
+
* subject: options.subject,
|
|
30
|
+
* body: options.body
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* return {
|
|
34
|
+
* success: true,
|
|
35
|
+
* sentAt: new Date().toISOString()
|
|
36
|
+
* };
|
|
37
|
+
* }
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* ### Operation with external API call
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* import {defineOperationApi} from "@tsed/directus-sdk";
|
|
45
|
+
* import {inject} from "@tsed/di";
|
|
46
|
+
*
|
|
47
|
+
* type WebhookOptions = {
|
|
48
|
+
* url: string;
|
|
49
|
+
* method: "GET" | "POST";
|
|
50
|
+
* payload?: Record<string, any>;
|
|
51
|
+
* };
|
|
52
|
+
*
|
|
53
|
+
* export default defineOperationApi<WebhookOptions>({
|
|
54
|
+
* id: "call-webhook",
|
|
55
|
+
* handler: async (options, context) => {
|
|
56
|
+
* const httpClient = inject(HttpClient);
|
|
57
|
+
*
|
|
58
|
+
* const response = await httpClient.request({
|
|
59
|
+
* url: options.url,
|
|
60
|
+
* method: options.method,
|
|
61
|
+
* data: options.payload
|
|
62
|
+
* });
|
|
63
|
+
*
|
|
64
|
+
* return {
|
|
65
|
+
* statusCode: response.status,
|
|
66
|
+
* data: response.data
|
|
67
|
+
* };
|
|
68
|
+
* }
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* ### Operation with database access
|
|
73
|
+
*
|
|
74
|
+
* ```ts
|
|
75
|
+
* import {defineOperationApi} from "@tsed/directus-sdk";
|
|
76
|
+
* import {inject} from "@tsed/di";
|
|
77
|
+
*
|
|
78
|
+
* type CalculateStatsOptions = {
|
|
79
|
+
* collection: string;
|
|
80
|
+
* field: string;
|
|
81
|
+
* };
|
|
82
|
+
*
|
|
83
|
+
* export default defineOperationApi<CalculateStatsOptions>({
|
|
84
|
+
* id: "calculate-stats",
|
|
85
|
+
* handler: async (options, context) => {
|
|
86
|
+
* const statsService = inject(StatsService);
|
|
87
|
+
* const directusContext = inject(DirectusContextService);
|
|
88
|
+
*
|
|
89
|
+
* const itemsService = await directusContext.getItemsService(options.collection);
|
|
90
|
+
* const items = await itemsService.readByQuery({ limit: -1 });
|
|
91
|
+
*
|
|
92
|
+
* const stats = statsService.calculate(items, options.field);
|
|
93
|
+
*
|
|
94
|
+
* return {
|
|
95
|
+
* count: items.length,
|
|
96
|
+
* average: stats.avg,
|
|
97
|
+
* min: stats.min,
|
|
98
|
+
* max: stats.max
|
|
99
|
+
* };
|
|
100
|
+
* }
|
|
101
|
+
* });
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* @template Options - Type definition for the operation's configuration options
|
|
105
|
+
*
|
|
106
|
+
* @param config - The operation configuration containing:
|
|
107
|
+
* - `id` - Unique identifier for the operation
|
|
108
|
+
* - `handler` - Function that executes the operation logic
|
|
109
|
+
*
|
|
110
|
+
* @returns The configured operation ready to be exported from your extension file
|
|
111
|
+
* @see {@link wrapOperation} for the underlying wrapper implementation
|
|
112
|
+
* @see {@link DirectusContextService} to access Directus context
|
|
113
|
+
* @see {@link https://docs.directus.io/extensions/operations.html} Directus Operations Documentation
|
|
114
|
+
*/
|
|
115
|
+
export function defineOperationApi(config) {
|
|
116
|
+
return oDefineOperationApi({
|
|
117
|
+
...config,
|
|
118
|
+
handler: wrapOperation(config.handler)
|
|
119
|
+
});
|
|
120
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import "../bootstrap/runtime.js";
|
|
2
|
+
import { attachLogger, DIContext, inject, injector, runInContext } from "@tsed/di";
|
|
3
|
+
import { nanoid } from "nanoid";
|
|
4
|
+
import { DirectusContextService } from "../services/DirectusContextService.js";
|
|
5
|
+
const VERBS = ["get", "put", "post", "delete", "head", "use", "all", "options"];
|
|
6
|
+
/**
|
|
7
|
+
* Wraps an endpoint handler to provide Ts.ED dependency injection and error handling.
|
|
8
|
+
*
|
|
9
|
+
* This function is internally used by `defineEndpoint` to:
|
|
10
|
+
* - Initialize the Ts.ED DI container for each request
|
|
11
|
+
* - Wrap all Express router methods (GET, POST, PUT, DELETE, etc.)
|
|
12
|
+
* - Provide automatic error handling with proper HTTP responses
|
|
13
|
+
* - Attach Directus logger to the DI context
|
|
14
|
+
* - Log errors with structured error information
|
|
15
|
+
*
|
|
16
|
+
* Each route handler executes within its own DI context, allowing proper isolation
|
|
17
|
+
* and service injection via `inject()`.
|
|
18
|
+
*
|
|
19
|
+
* @param callback - The endpoint handler function that receives router and context
|
|
20
|
+
*
|
|
21
|
+
* @returns A wrapped handler that integrates with Directus and Ts.ED
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* This is typically used internally by `defineEndpoint`. You generally don't need to
|
|
25
|
+
* call this directly unless you're building custom endpoint wrappers.
|
|
26
|
+
*
|
|
27
|
+
* Error handling:
|
|
28
|
+
* - Errors are caught and returned as JSON responses
|
|
29
|
+
* - Status code is extracted from `error.status` (defaults to 500)
|
|
30
|
+
* - Error message is extracted from `error.error_message` or `error.message`
|
|
31
|
+
* - Additional errors array is included if present in `error.errors`
|
|
32
|
+
* - All errors are logged with structured information (name, message, description, stack)
|
|
33
|
+
*
|
|
34
|
+
* @see {@link defineEndpoint} for the recommended way to create endpoints
|
|
35
|
+
* @see {@link DirectusContextService} to access Directus context in injected services
|
|
36
|
+
*/
|
|
37
|
+
export function wrapEndpoint(callback) {
|
|
38
|
+
return (router, context) => {
|
|
39
|
+
attachLogger(context.logger);
|
|
40
|
+
VERBS.forEach((verb) => {
|
|
41
|
+
const r = router;
|
|
42
|
+
if (r[verb] && !r["__" + verb]) {
|
|
43
|
+
r["__" + verb] = r[verb];
|
|
44
|
+
r[verb] = (path, handler) => {
|
|
45
|
+
const wrapped = async (req, res) => {
|
|
46
|
+
await injector().load();
|
|
47
|
+
const $ctx = new DIContext({
|
|
48
|
+
id: nanoid(),
|
|
49
|
+
platform: "DIRECTUS_ENDPOINT",
|
|
50
|
+
maxStackSize: 0
|
|
51
|
+
});
|
|
52
|
+
return runInContext($ctx, async () => {
|
|
53
|
+
inject(DirectusContextService).set(context);
|
|
54
|
+
try {
|
|
55
|
+
return await handler(req, res);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
res.status(error.status || 500).json({
|
|
59
|
+
message: error.error_message || error.message || "Internal Server Error",
|
|
60
|
+
errors: error.errors
|
|
61
|
+
});
|
|
62
|
+
$ctx.logger.error({
|
|
63
|
+
error_name: error.name,
|
|
64
|
+
error_message: error.message,
|
|
65
|
+
error_description: error.description,
|
|
66
|
+
error_stack: error.stack
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
$ctx.logger.flush();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
r["__" + verb](path, wrapped);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
callback(router, context);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import "../bootstrap/runtime.js";
|
|
2
|
+
import { DIContext, inject, injector, runInContext } from "@tsed/di";
|
|
3
|
+
import { nanoid } from "nanoid";
|
|
4
|
+
import { DirectusContextService } from "../services/DirectusContextService.js";
|
|
5
|
+
/**
|
|
6
|
+
* Wraps an operation handler to provide Ts.ED dependency injection and error handling.
|
|
7
|
+
*
|
|
8
|
+
* This function is internally used by `defineOperationApi` to:
|
|
9
|
+
* - Initialize the Ts.ED DI container for each operation execution
|
|
10
|
+
* - Enable service injection via `inject()` within the operation handler
|
|
11
|
+
* - Provide automatic error handling and logging
|
|
12
|
+
* - Set up the Directus context in `DirectusContextService`
|
|
13
|
+
* - Create an isolated DI context for each operation run
|
|
14
|
+
*
|
|
15
|
+
* Each operation execution runs within its own DI context with platform set to
|
|
16
|
+
* `DIRECTUS_OPERATION`, ensuring proper isolation between different operation runs.
|
|
17
|
+
*
|
|
18
|
+
* @template Options - Type definition for the operation's configuration options
|
|
19
|
+
*
|
|
20
|
+
* @param callback - The operation handler function that receives options and context
|
|
21
|
+
*
|
|
22
|
+
* @returns A wrapped handler that integrates with Directus flows and Ts.ED
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* This is typically used internally by `defineOperationApi`. You generally don't need
|
|
26
|
+
* to call this directly unless you're building custom operation wrappers.
|
|
27
|
+
*
|
|
28
|
+
* Error handling:
|
|
29
|
+
* - Errors are caught and logged with structured information
|
|
30
|
+
* - Error details include name, message, description, and stack trace
|
|
31
|
+
* - Logger is automatically flushed after error logging
|
|
32
|
+
* - The error is not re-thrown, allowing the flow to continue
|
|
33
|
+
*
|
|
34
|
+
* @see {@link defineOperationApi} for the recommended way to create operations
|
|
35
|
+
* @see {@link DirectusContextService} to access Directus context in injected services
|
|
36
|
+
*/
|
|
37
|
+
export function wrapOperation(callback) {
|
|
38
|
+
return async (options, context) => {
|
|
39
|
+
await injector().load();
|
|
40
|
+
const $ctx = new DIContext({
|
|
41
|
+
id: nanoid(),
|
|
42
|
+
platform: "DIRECTUS_OPERATION",
|
|
43
|
+
maxStackSize: 0
|
|
44
|
+
});
|
|
45
|
+
return runInContext($ctx, async () => {
|
|
46
|
+
try {
|
|
47
|
+
inject(DirectusContextService).set(context);
|
|
48
|
+
return await callback(options, context);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
$ctx.logger.error({
|
|
52
|
+
error_name: error.name,
|
|
53
|
+
error_message: error.message,
|
|
54
|
+
error_description: error.description,
|
|
55
|
+
error_stack: error.stack
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
$ctx.logger.flush();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
}
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Automatically generated by @tsed/barrels.
|
|
3
|
+
*/
|
|
4
|
+
export * from "./bootstrap/attach-logger.js";
|
|
5
|
+
export * from "./bootstrap/runtime.js";
|
|
6
|
+
export * from "./decorators/cache.js";
|
|
7
|
+
export * from "./fn/defineEndpoint.js";
|
|
8
|
+
export * from "./fn/defineHook.js";
|
|
9
|
+
export * from "./fn/defineOperationApi.js";
|
|
10
|
+
export * from "./fn/wrapEndpoint.js";
|
|
11
|
+
export * from "./fn/wrapOperation.js";
|
|
12
|
+
export * from "./services/DirectusCacheInterceptor.js";
|
|
13
|
+
export * from "./services/DirectusContextService.js";
|
|
14
|
+
export * from "./services/DirectusItemsRepository.js";
|