@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
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { getCache, getCacheValue, setCacheValue } from "@directus/api/cache";
|
|
2
|
+
import { classOf } from "@tsed/core/utils/classOf.js";
|
|
3
|
+
import { nameOf } from "@tsed/core/utils/nameOf.js";
|
|
4
|
+
import { bindIntercept, injectable } from "@tsed/di";
|
|
5
|
+
import { snakeCase } from "lodash";
|
|
6
|
+
/**
|
|
7
|
+
* Default cache key generator that serializes method arguments into a cache key.
|
|
8
|
+
*
|
|
9
|
+
* Creates a JSON string of the arguments, or an empty string if no arguments.
|
|
10
|
+
* This ensures unique cache keys for different argument combinations.
|
|
11
|
+
*
|
|
12
|
+
* @param args - The method arguments to serialize
|
|
13
|
+
* @returns A string representation of the arguments, prefixed with ':'
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* defaultKeyGenerator("user", 123)
|
|
18
|
+
* // Returns: ':["user",123]'
|
|
19
|
+
*
|
|
20
|
+
* defaultKeyGenerator()
|
|
21
|
+
* // Returns: ':'
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
function defaultKeyGenerator(...args) {
|
|
25
|
+
return args.length > 0 ? JSON.stringify(args) : "";
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Interceptor that provides caching functionality using Directus cache infrastructure.
|
|
29
|
+
*
|
|
30
|
+
* This interceptor wraps method calls to cache their results based on method arguments.
|
|
31
|
+
* It integrates with Directus's built-in caching system and supports both system cache
|
|
32
|
+
* and regular cache.
|
|
33
|
+
*
|
|
34
|
+
* Features:
|
|
35
|
+
* - Automatic cache key generation based on class name, method name, and arguments
|
|
36
|
+
* - Configurable TTL (time to live) for cached values
|
|
37
|
+
* - Custom key generators for complex caching strategies
|
|
38
|
+
* - Support for both system cache and regular cache
|
|
39
|
+
* - Proper handling of falsy values (null, false, 0, empty string)
|
|
40
|
+
*
|
|
41
|
+
* ### Direct usage with bindIntercept
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* import {DirectusCacheInterceptor, useDirectusCache} from "@tsed/directus-sdk";
|
|
45
|
+
* import {injectable} from "@tsed/di";
|
|
46
|
+
*
|
|
47
|
+
* export class MyService {
|
|
48
|
+
* search(query: string) {
|
|
49
|
+
* // Expensive operation
|
|
50
|
+
* return this.performSearch(query);
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* // Apply cache programmatically
|
|
55
|
+
* injectable(MyService);
|
|
56
|
+
* useDirectusCache(MyService, "search", { ttl: 60000 });
|
|
57
|
+
* ```
|
|
58
|
+
* @remarks
|
|
59
|
+
* This interceptor is typically used via the `@Cache` decorator rather than directly.
|
|
60
|
+
* The cache key format is: `{namespace}:{keyGenerator(...args)}`
|
|
61
|
+
* where namespace defaults to `snake_case_class_name:methodName`
|
|
62
|
+
*
|
|
63
|
+
* @see {@link Cache} decorator for the recommended usage
|
|
64
|
+
* @see {@link useDirectusCache} for programmatic cache binding
|
|
65
|
+
*/
|
|
66
|
+
export class DirectusCacheInterceptor {
|
|
67
|
+
/**
|
|
68
|
+
* Intercepts a method call to provide caching functionality.
|
|
69
|
+
*
|
|
70
|
+
* The interception flow:
|
|
71
|
+
* 1. Extract cache options from the context
|
|
72
|
+
* 2. Generate a cache key based on class name, method name, and arguments
|
|
73
|
+
* 3. Check if a cached value exists
|
|
74
|
+
* 4. If cached, return the cached value (skipping method execution)
|
|
75
|
+
* 5. If not cached, execute the method
|
|
76
|
+
* 6. Store the result in cache with the specified TTL
|
|
77
|
+
* 7. Return the result
|
|
78
|
+
*
|
|
79
|
+
* ### Cache key generation
|
|
80
|
+
*
|
|
81
|
+
* ```ts
|
|
82
|
+
* // For class MyService with method search("query")
|
|
83
|
+
* // Default namespace: "my_service:search"
|
|
84
|
+
* // Full cache key: "my_service:search:["query"]"
|
|
85
|
+
* ```
|
|
86
|
+
* @param context - The interceptor context containing method information and options
|
|
87
|
+
*
|
|
88
|
+
* @returns The cached or newly computed result of the method call
|
|
89
|
+
*
|
|
90
|
+
* @remarks
|
|
91
|
+
* - The cache check uses strict inequality (`!== undefined`) to allow caching falsy values
|
|
92
|
+
* - Cache keys are automatically namespaced to avoid collisions between different methods
|
|
93
|
+
* - The system cache is used by default, which persists across requests
|
|
94
|
+
*/
|
|
95
|
+
async intercept(context) {
|
|
96
|
+
const { ttl = 900000, // 15 minutes default
|
|
97
|
+
keyGenerator = defaultKeyGenerator, namespace = snakeCase(nameOf(context.target)) + ":" + String(context.propertyKey), useSystemCache = true } = context.options || {};
|
|
98
|
+
// Get appropriate cache instance
|
|
99
|
+
const cache = getCache();
|
|
100
|
+
const cacheInstance = useSystemCache ? cache.systemCache : cache.cache;
|
|
101
|
+
// Generate cache key
|
|
102
|
+
const cacheKey = `${namespace}:${keyGenerator(...context.args)}`;
|
|
103
|
+
// Try to get from cache
|
|
104
|
+
const cachedValue = await getCacheValue(cacheInstance, cacheKey);
|
|
105
|
+
if (cachedValue !== undefined) {
|
|
106
|
+
return cachedValue;
|
|
107
|
+
}
|
|
108
|
+
// Execute function and cache result
|
|
109
|
+
const result = await context.next();
|
|
110
|
+
await setCacheValue(cacheInstance, cacheKey, result, ttl);
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
injectable(DirectusCacheInterceptor);
|
|
115
|
+
/**
|
|
116
|
+
* Programmatically applies cache interception to a method using Directus cache infrastructure.
|
|
117
|
+
*
|
|
118
|
+
* This function allows you to add caching to a method after the class is defined,
|
|
119
|
+
* which is useful when you can't use decorators or need dynamic cache configuration.
|
|
120
|
+
*
|
|
121
|
+
* @example Basic usage
|
|
122
|
+
*
|
|
123
|
+
* ```ts
|
|
124
|
+
* import {injectable} from "@tsed/di";
|
|
125
|
+
* import {useDirectusCache} from "@tsed/directus-sdk";
|
|
126
|
+
*
|
|
127
|
+
* export class JiraIssueClient {
|
|
128
|
+
* search(query: string) {
|
|
129
|
+
* // Expensive search operation
|
|
130
|
+
* return this.performSearch(query);
|
|
131
|
+
* }
|
|
132
|
+
* }
|
|
133
|
+
*
|
|
134
|
+
* injectable(JiraIssueClient)
|
|
135
|
+
*
|
|
136
|
+
* // Apply cache after class definition
|
|
137
|
+
* useDirectusCache(JiraIssueClient, "search", { ttl: 900000 });
|
|
138
|
+
* ```
|
|
139
|
+
*
|
|
140
|
+
* ### With custom options
|
|
141
|
+
* ```ts
|
|
142
|
+
*
|
|
143
|
+
* useDirectusCache(MyService, "fetchData", {
|
|
144
|
+
* ttl: 60000,
|
|
145
|
+
* keyGenerator: (id: string) => `data:${id}`,
|
|
146
|
+
* namespace: "my_service:data",
|
|
147
|
+
* useSystemCache: false
|
|
148
|
+
* });
|
|
149
|
+
* ```
|
|
150
|
+
*
|
|
151
|
+
* ### Applying cache to multiple methods
|
|
152
|
+
*
|
|
153
|
+
* ```ts
|
|
154
|
+
* import {injectable} from "@tsed/di";
|
|
155
|
+
* import {useDirectusCache} from "@tsed/directus-sdk";
|
|
156
|
+
*
|
|
157
|
+
* export class ApiClient {
|
|
158
|
+
* getUser(id: string) { }
|
|
159
|
+
* getPosts() { }
|
|
160
|
+
* }
|
|
161
|
+
* injectable(ApiClient);
|
|
162
|
+
*
|
|
163
|
+
* useDirectusCache(ApiClient, "getUser", { ttl: 300000 });
|
|
164
|
+
* useDirectusCache(ApiClient, "getPosts", { ttl: 60000 });
|
|
165
|
+
* ```
|
|
166
|
+
*
|
|
167
|
+
* @template K - The class type
|
|
168
|
+
* @template Props - The property key type (method name)
|
|
169
|
+
*
|
|
170
|
+
* @param token - The class constructor to apply caching to
|
|
171
|
+
* @param propertyKey - The name of the method to cache
|
|
172
|
+
* @param opts - Cache configuration options
|
|
173
|
+
*
|
|
174
|
+
* @remarks
|
|
175
|
+
* - Must be called after the class is marked as `@Injectable()` or `injectable()`
|
|
176
|
+
* - The class must be registered in the DI container
|
|
177
|
+
* - Prefer using the `@Cache` decorator when possible for better readability
|
|
178
|
+
*
|
|
179
|
+
* @see {@link Cache} decorator for the declarative approach
|
|
180
|
+
* @see {@link DirectusCacheInterceptor} for implementation details
|
|
181
|
+
*/
|
|
182
|
+
export function useDirectusCache(token, propertyKey, opts) {
|
|
183
|
+
bindIntercept(classOf(token), propertyKey, DirectusCacheInterceptor, opts);
|
|
184
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { context, injectable } from "@tsed/di";
|
|
2
|
+
/**
|
|
3
|
+
* Service to manage and access Directus context within Ts.ED dependency injection container.
|
|
4
|
+
*
|
|
5
|
+
* This service provides a bridge between Directus extensions and Ts.ED's DI system,
|
|
6
|
+
* allowing you to access the Directus context and its services from anywhere in your code.
|
|
7
|
+
*
|
|
8
|
+
* The context is automatically set by `wrapEndpoint`, `wrapOperation`, and `defineHook`
|
|
9
|
+
* wrappers, making it available through the DI container.
|
|
10
|
+
*
|
|
11
|
+
* ### Accessing context in a custom service
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* import {Injectable} from "@tsed/di";
|
|
15
|
+
* import {DirectusContextService} from "@tsed/directus-sdk";
|
|
16
|
+
*
|
|
17
|
+
* @Injectable()
|
|
18
|
+
* export class MyService {
|
|
19
|
+
* directusContext = inject(DirectusContextService);
|
|
20
|
+
*
|
|
21
|
+
* async doSomething() {
|
|
22
|
+
* const context = this.directusContext.get();
|
|
23
|
+
* const schema = await context.getSchema();
|
|
24
|
+
* // Use schema...
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* ### Using inject() to get the service
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* import {inject} from "@tsed/di";
|
|
33
|
+
* import {DirectusContextService} from "@tsed/directus-sdk";
|
|
34
|
+
*
|
|
35
|
+
* const contextService = inject(DirectusContextService);
|
|
36
|
+
* const context = contextService.get();
|
|
37
|
+
*```
|
|
38
|
+
*
|
|
39
|
+
* @remarks
|
|
40
|
+
* This service is automatically registered as injectable and can be accessed via `inject()`.
|
|
41
|
+
*/
|
|
42
|
+
export class DirectusContextService {
|
|
43
|
+
/**
|
|
44
|
+
* Sets the Directus context in the current DI context.
|
|
45
|
+
*
|
|
46
|
+
* This method is typically called internally by the wrapper functions
|
|
47
|
+
* (`wrapEndpoint`, `wrapOperation`, `defineHook`) and should not be
|
|
48
|
+
* called manually in most cases.
|
|
49
|
+
*
|
|
50
|
+
* @param ctx - The Directus extension context (endpoint, operation, or hook)
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* const contextService = inject(DirectusContextService);
|
|
55
|
+
* contextService.set(directusContext);
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
set(ctx) {
|
|
59
|
+
context().set("DIRECTUS_CONTEXT", ctx);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Retrieves the Directus context from the current DI context.
|
|
63
|
+
*
|
|
64
|
+
* Returns the context that was previously set by the wrapper functions.
|
|
65
|
+
* This allows you to access Directus services, schema, logger, and other
|
|
66
|
+
* context-specific functionality.
|
|
67
|
+
*
|
|
68
|
+
* ### Accessing context properties
|
|
69
|
+
*
|
|
70
|
+
* ```ts
|
|
71
|
+
* const contextService = inject(DirectusContextService);
|
|
72
|
+
* const context = contextService.get();
|
|
73
|
+
*
|
|
74
|
+
* // Access logger
|
|
75
|
+
* context.logger.info("Message");
|
|
76
|
+
*
|
|
77
|
+
* // Access schema
|
|
78
|
+
* const schema = await context.getSchema();
|
|
79
|
+
*
|
|
80
|
+
* // Access services
|
|
81
|
+
* const itemsService = new context.services.ItemsService("users");
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @returns The Directus extension context, or undefined if not set
|
|
85
|
+
*
|
|
86
|
+
*/
|
|
87
|
+
get() {
|
|
88
|
+
return context().get("DIRECTUS_CONTEXT");
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Creates and returns an ItemsService instance for the specified collection.
|
|
92
|
+
*
|
|
93
|
+
* This is a convenience method that:
|
|
94
|
+
*
|
|
95
|
+
* 1. Retrieves the Directus context
|
|
96
|
+
* 2. Fetches the database schema
|
|
97
|
+
* 3. Creates an ItemsService with the schema and provided options
|
|
98
|
+
*
|
|
99
|
+
* The ItemsService allows you to perform CRUD operations on Directus collections.
|
|
100
|
+
*
|
|
101
|
+
* ### Basic usage
|
|
102
|
+
*
|
|
103
|
+
* ```ts
|
|
104
|
+
* const contextService = inject(DirectusContextService);
|
|
105
|
+
* const usersService = await contextService.getItemsService("users");
|
|
106
|
+
*
|
|
107
|
+
* // Read all users
|
|
108
|
+
* const users = await usersService.readByQuery({ limit: -1 });
|
|
109
|
+
*
|
|
110
|
+
* // Create a user
|
|
111
|
+
* const userId = await usersService.createOne({ name: "John", email: "john@example.com" });
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* ### With typed collection
|
|
115
|
+
*
|
|
116
|
+
* ```ts
|
|
117
|
+
* type User = Item & {
|
|
118
|
+
* id: string;
|
|
119
|
+
* name: string;
|
|
120
|
+
* email: string;
|
|
121
|
+
* };
|
|
122
|
+
*
|
|
123
|
+
* const contextService = inject(DirectusContextService);
|
|
124
|
+
* const usersService = await contextService.getItemsService<User, "users">("users");
|
|
125
|
+
*
|
|
126
|
+
* // Now fully typed
|
|
127
|
+
* const users: User[] = await usersService.readByQuery({ limit: -1 });
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* ### With accountability options
|
|
131
|
+
*
|
|
132
|
+
* ```ts
|
|
133
|
+
* const contextService = inject(DirectusContextService);
|
|
134
|
+
* const itemsService = await contextService.getItemsService("articles", {
|
|
135
|
+
* accountability: {
|
|
136
|
+
* user: "user-id",
|
|
137
|
+
* role: "admin"
|
|
138
|
+
* }
|
|
139
|
+
* });
|
|
140
|
+
* ```
|
|
141
|
+
*
|
|
142
|
+
* @template T - The item type extending Directus Item
|
|
143
|
+
* @template Collection - The collection name as a string literal type
|
|
144
|
+
* @param collection - The name of the collection to access
|
|
145
|
+
* @param options - Additional options for the ItemsService (e.g., accountability, knex instance)
|
|
146
|
+
* @returns A promise resolving to an ItemsService instance for the collection
|
|
147
|
+
* @throws {Error} If no Directus context is available (called outside extension context)
|
|
148
|
+
* @see {@link DirectusItemsRepository} for a more structured way to work with collections
|
|
149
|
+
*/
|
|
150
|
+
async getItemsService(collection, options = {}) {
|
|
151
|
+
const context = this.get();
|
|
152
|
+
if (context) {
|
|
153
|
+
const schema = await context.getSchema();
|
|
154
|
+
return new context.services.ItemsService(collection, {
|
|
155
|
+
...options,
|
|
156
|
+
schema
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
throw new Error("No directus context available");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
injectable(DirectusContextService);
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { inject } from "@tsed/di";
|
|
2
|
+
import { DirectusContextService } from "./DirectusContextService.js";
|
|
3
|
+
/**
|
|
4
|
+
* Abstract base class for creating typed repository patterns with Directus collections.
|
|
5
|
+
*
|
|
6
|
+
* This class provides a structured way to interact with Directus collections by:
|
|
7
|
+
* - Automatically retrieving the Directus context via dependency injection
|
|
8
|
+
* - Providing common CRUD operations (create, update, listAll)
|
|
9
|
+
* - Supporting custom methods through inheritance
|
|
10
|
+
* - Ensuring type safety with TypeScript generics
|
|
11
|
+
*
|
|
12
|
+
* Extend this class to create collection-specific repositories with custom business logic.
|
|
13
|
+
*
|
|
14
|
+
* ### Basic repository with custom methods
|
|
15
|
+
* ```ts
|
|
16
|
+
* import {injectable} from "@tsed/di";
|
|
17
|
+
* import {DirectusItemsRepository} from "@tsed/directus-sdk";
|
|
18
|
+
* import type {Item} from "@directus/types";
|
|
19
|
+
*
|
|
20
|
+
* type Release = Item & {
|
|
21
|
+
* id: string;
|
|
22
|
+
* title: string;
|
|
23
|
+
* status: "draft" | "published";
|
|
24
|
+
* publishedAt?: string;
|
|
25
|
+
* };
|
|
26
|
+
*
|
|
27
|
+
* export class ReleasesRepository extends DirectusItemsRepository<Release, "releases"> {
|
|
28
|
+
* protected collection = "releases" as const;
|
|
29
|
+
*
|
|
30
|
+
* async getPublishedReleases() {
|
|
31
|
+
* const collection = await this.getCollection();
|
|
32
|
+
*
|
|
33
|
+
* return collection.readByQuery({
|
|
34
|
+
* filter: {
|
|
35
|
+
* status: {
|
|
36
|
+
* _eq: "published"
|
|
37
|
+
* }
|
|
38
|
+
* },
|
|
39
|
+
* sort: ["-publishedAt"]
|
|
40
|
+
* });
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* async publishRelease(id: string) {
|
|
44
|
+
* return this.update({
|
|
45
|
+
* id,
|
|
46
|
+
* status: "published",
|
|
47
|
+
* publishedAt: new Date().toISOString()
|
|
48
|
+
* });
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
* injectable(ReleasesRepository)
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* ### Repository with complex queries
|
|
55
|
+
*
|
|
56
|
+
* ```ts
|
|
57
|
+
* import {injectable} from "@tsed/di";
|
|
58
|
+
* import {DirectusItemsRepository} from "@tsed/directus-sdk";
|
|
59
|
+
*
|
|
60
|
+
* export class ArticlesRepository extends DirectusItemsRepository<Article, "articles"> {
|
|
61
|
+
* protected collection = "articles" as const;
|
|
62
|
+
*
|
|
63
|
+
* async findByAuthor(authorId: string) {
|
|
64
|
+
* const collection = await this.getCollection();
|
|
65
|
+
*
|
|
66
|
+
* return collection.readByQuery({
|
|
67
|
+
* filter: {
|
|
68
|
+
* author: {
|
|
69
|
+
* _eq: authorId
|
|
70
|
+
* }
|
|
71
|
+
* }
|
|
72
|
+
* });
|
|
73
|
+
* }
|
|
74
|
+
*
|
|
75
|
+
* async searchByTitle(query: string) {
|
|
76
|
+
* const collection = await this.getCollection();
|
|
77
|
+
*
|
|
78
|
+
* return collection.readByQuery({
|
|
79
|
+
* filter: {
|
|
80
|
+
* title: {
|
|
81
|
+
* _contains: query
|
|
82
|
+
* }
|
|
83
|
+
* }
|
|
84
|
+
* });
|
|
85
|
+
* }
|
|
86
|
+
* }
|
|
87
|
+
* injectable(ArticlesRepository);
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* ### Using repository in an endpoint
|
|
91
|
+
*
|
|
92
|
+
* ```ts
|
|
93
|
+
* import {defineEndpoint} from "@tsed/directus-sdk";
|
|
94
|
+
* import {inject} from "@tsed/di";
|
|
95
|
+
*
|
|
96
|
+
* export default defineEndpoint({
|
|
97
|
+
* id: "releases-api",
|
|
98
|
+
* handler: (router) => {
|
|
99
|
+
* const releasesRepo = inject(ReleasesRepository);
|
|
100
|
+
*
|
|
101
|
+
* router.get("/releases", async (req, res) => {
|
|
102
|
+
* const releases = await releasesRepo.getPublishedReleases();
|
|
103
|
+
* res.json(releases);
|
|
104
|
+
* });
|
|
105
|
+
*
|
|
106
|
+
* router.post("/releases/:id/publish", async (req, res) => {
|
|
107
|
+
* const release = await releasesRepo.publishRelease(req.params.id);
|
|
108
|
+
* res.json(release);
|
|
109
|
+
* });
|
|
110
|
+
* }
|
|
111
|
+
* });
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* @template T - The item type extending Directus Item interface
|
|
115
|
+
* @template Collection - The collection name as a string literal type
|
|
116
|
+
*
|
|
117
|
+
* @remarks
|
|
118
|
+
* - The extending class must define the `collection` property
|
|
119
|
+
* - Don't forget to mark the extending class with `@Injectable()` or `injectable()`
|
|
120
|
+
* - Access to Directus context is automatically handled through `DirectusContextService`
|
|
121
|
+
*
|
|
122
|
+
* @see {@link DirectusContextService} for lower-level access to Directus context
|
|
123
|
+
*/
|
|
124
|
+
export class DirectusItemsRepository {
|
|
125
|
+
constructor() {
|
|
126
|
+
this.contextService = inject(DirectusContextService);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Retrieves the Directus ItemsService for the configured collection.
|
|
130
|
+
*
|
|
131
|
+
* This method provides access to the underlying Directus ItemsService,
|
|
132
|
+
* which offers full CRUD capabilities and advanced query features.
|
|
133
|
+
*
|
|
134
|
+
* ```ts
|
|
135
|
+
* const collection = await this.getCollection();
|
|
136
|
+
* const items = await collection.readByQuery({
|
|
137
|
+
* filter: { status: { _eq: "active" } },
|
|
138
|
+
* limit: 10,
|
|
139
|
+
* offset: 0
|
|
140
|
+
* });
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* @returns A promise resolving to the ItemsService for the collection
|
|
144
|
+
*
|
|
145
|
+
*/
|
|
146
|
+
getCollection() {
|
|
147
|
+
return this.contextService.getItemsService(this.collection);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Creates a new item in the collection and returns the created item.
|
|
151
|
+
*
|
|
152
|
+
* ```ts
|
|
153
|
+
* const newRelease = await releasesRepo.create({
|
|
154
|
+
* title: "Version 1.0",
|
|
155
|
+
* status: "draft"
|
|
156
|
+
* });
|
|
157
|
+
*
|
|
158
|
+
* console.log(newRelease.id); // Auto-generated ID
|
|
159
|
+
* ```
|
|
160
|
+
*
|
|
161
|
+
* ### With mutation options
|
|
162
|
+
*
|
|
163
|
+
* ```ts
|
|
164
|
+
* const item = await repo.create(data, {
|
|
165
|
+
* emitEvents: false,
|
|
166
|
+
* bypassLimits: true
|
|
167
|
+
* });
|
|
168
|
+
* ```
|
|
169
|
+
*
|
|
170
|
+
* @param data - Partial item data to create
|
|
171
|
+
* @param opts - Optional mutation options (e.g., emitEvents, bypassLimits)
|
|
172
|
+
*
|
|
173
|
+
* @returns A promise resolving to the created item with all fields
|
|
174
|
+
*/
|
|
175
|
+
async create(data, opts) {
|
|
176
|
+
const collection = await this.getCollection();
|
|
177
|
+
const key = await collection.createOne(data, opts);
|
|
178
|
+
return collection.readOne(key);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Updates an existing item in the collection and returns the updated item.
|
|
182
|
+
*
|
|
183
|
+
* ```ts
|
|
184
|
+
* const updated = await releasesRepo.update({
|
|
185
|
+
* id: "release-123",
|
|
186
|
+
* status: "published"
|
|
187
|
+
* });
|
|
188
|
+
* ```
|
|
189
|
+
*
|
|
190
|
+
* ### With mutation options
|
|
191
|
+
*
|
|
192
|
+
* ```ts
|
|
193
|
+
* const updated = await repo.update(
|
|
194
|
+
* { id: "123", title: "New Title" },
|
|
195
|
+
* { emitEvents: true }
|
|
196
|
+
* );
|
|
197
|
+
* ```
|
|
198
|
+
*
|
|
199
|
+
* @param data - Partial item data including the required `id` field
|
|
200
|
+
* @param opts - Optional mutation options (e.g., emitEvents, bypassLimits)
|
|
201
|
+
*
|
|
202
|
+
* @returns A promise resolving to the updated item
|
|
203
|
+
*/
|
|
204
|
+
async update(data, opts) {
|
|
205
|
+
const collection = await this.getCollection();
|
|
206
|
+
const key = await collection.updateOne(data.id, data, opts);
|
|
207
|
+
return collection.readOne(key);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Retrieves all items from the collection without pagination.
|
|
211
|
+
*
|
|
212
|
+
* ```ts
|
|
213
|
+
* const allReleases = await releasesRepo.listAll();
|
|
214
|
+
* console.log(`Total releases: ${allReleases.length}`);
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
* ### For large collections, consider custom pagination
|
|
218
|
+
*
|
|
219
|
+
* ```ts
|
|
220
|
+
* async getPaginatedItems(page: number, pageSize: number) {
|
|
221
|
+
* const collection = await this.getCollection();
|
|
222
|
+
* return collection.readByQuery({
|
|
223
|
+
* limit: pageSize,
|
|
224
|
+
* offset: (page - 1) * pageSize
|
|
225
|
+
* });
|
|
226
|
+
* }
|
|
227
|
+
* ```
|
|
228
|
+
*
|
|
229
|
+
* @returns A promise resolving to an array of all items in the collection
|
|
230
|
+
*
|
|
231
|
+
* @remarks
|
|
232
|
+
* This method uses `limit: -1` to retrieve all items. Use with caution on
|
|
233
|
+
* large collections as it may impact performance.
|
|
234
|
+
*
|
|
235
|
+
*/
|
|
236
|
+
async listAll() {
|
|
237
|
+
const service = await this.getCollection();
|
|
238
|
+
return (await service.readByQuery({ limit: -1 }));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "@tsed/logger-connect";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "./attach-logger.js";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type DirectusCacheOptions } from "../services/DirectusCacheInterceptor.js";
|
|
2
|
+
/**
|
|
3
|
+
* Decorator to cache method results using Directus cache system.
|
|
4
|
+
*
|
|
5
|
+
* This decorator uses the Directus cache infrastructure (system cache by default) to store
|
|
6
|
+
* method results. Cached values are retrieved on subsequent calls with the same arguments,
|
|
7
|
+
* avoiding redundant computations or API calls.
|
|
8
|
+
*
|
|
9
|
+
* ### Basic usage
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import {Injectable} from "@tsed/di";
|
|
13
|
+
* import {Cache} from "@tsed/directus-sdk";
|
|
14
|
+
*
|
|
15
|
+
* @Injectable()
|
|
16
|
+
* export class JiraIssueClient {
|
|
17
|
+
* // Cache search results for 15 minutes (default TTL)
|
|
18
|
+
* @Cache({ ttl: 900000 })
|
|
19
|
+
* search(query: string) {
|
|
20
|
+
* // Expensive API call
|
|
21
|
+
* return this.fetchFromJira(query);
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* // Custom cache key generator
|
|
25
|
+
* @Cache({
|
|
26
|
+
* ttl: 60000,
|
|
27
|
+
* keyGenerator: (userId: string) => `user:${userId}`
|
|
28
|
+
* })
|
|
29
|
+
* getUserById(userId: string) {
|
|
30
|
+
* return this.fetchUser(userId);
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* // Use regular cache instead of system cache
|
|
34
|
+
* @Cache({
|
|
35
|
+
* ttl: 300000,
|
|
36
|
+
* useSystemCache: false,
|
|
37
|
+
* namespace: "custom:namespace"
|
|
38
|
+
* })
|
|
39
|
+
* getCustomData() {
|
|
40
|
+
* return this.fetchCustomData();
|
|
41
|
+
* }
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @param options - Cache configuration options
|
|
46
|
+
* @param options.ttl - Time to live in milliseconds (default: 900000ms = 15 minutes)
|
|
47
|
+
* @param options.keyGenerator - Custom function to generate cache keys from method arguments
|
|
48
|
+
* @param options.namespace - Custom namespace for cache keys (default: className:methodName)
|
|
49
|
+
* @param options.useSystemCache - Whether to use system cache (true) or regular cache (false). Default: true
|
|
50
|
+
*
|
|
51
|
+
* @returns A method decorator that intercepts method calls and caches results
|
|
52
|
+
*
|
|
53
|
+
* @decorator
|
|
54
|
+
* @see {@link DirectusCacheInterceptor} for implementation details
|
|
55
|
+
* @see {@link DirectusCacheOptions} for available options
|
|
56
|
+
*/
|
|
57
|
+
export declare function Cache(options: DirectusCacheOptions): any;
|