@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.
@@ -0,0 +1,5 @@
1
+ {
2
+ "directory": ["./src"],
3
+ "exclude": ["**/bin", "**/__mock__", "**/__mocks__", "**/*.spec.ts"],
4
+ "delete": true
5
+ }
@@ -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
+ }
@@ -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";