@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/readme.md ADDED
@@ -0,0 +1,493 @@
1
+ <p style="text-align: center" align="center">
2
+ <a href="https://tsed.dev" target="_blank"><img src="https://tsed.dev/tsed-og.png" width="200" alt="Ts.ED logo"/></a>
3
+ </p>
4
+
5
+ <div align="center">
6
+ <h1>Ts.ED Directus SDK</h1>
7
+
8
+ [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
9
+ [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
10
+ [![github](https://img.shields.io/static/v1?label=Github%20sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/romakita)
11
+ [![opencollective](https://img.shields.io/static/v1?label=OpenCollective%20sponsor&message=%E2%9D%A4&logo=OpenCollective&color=%23fe8e86)](https://opencollective.com/tsed)
12
+
13
+ </div>
14
+
15
+ <div align="center">
16
+ <a href="https://tsed.dev/">Website</a>
17
+ <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
18
+ <a href="https://tsed.dev/getting-started/">Getting started</a>
19
+ <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
20
+ <a href="https://slack.tsed.dev">Slack</a>
21
+ <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
22
+ <a href="https://twitter.com/TsED_io">Twitter</a>
23
+ </div>
24
+
25
+ <hr />
26
+
27
+ A package that integrates [Ts.ED](https://tsed.dev/) dependency injection with [Directus](https://directus.io/)
28
+ extensions (endpoints, hooks, and operations).
29
+
30
+ ---
31
+
32
+ ## ✨ Features
33
+
34
+ - 🎯 **Dependency Injection** - Use Ts.ED's DI container in Directus extensions
35
+ - 🪝 **Hooks Support** - Create hooks with service injection and error handling
36
+ - 🌐 **Endpoints** - Build API endpoints with automatic DI context management
37
+ - ⚙️ **Operations** - Create flow operations with full DI support
38
+ - 💾 **Cache Decorator** - Built-in caching using Directus cache infrastructure
39
+ - 📦 **Repository Pattern** - Type-safe repository base class for collections
40
+ - 🔌 **Context Service** - Access Directus context from anywhere in your code
41
+
42
+ ---
43
+
44
+ ## 📦 Installation
45
+
46
+ Install with your favorite package manager:
47
+
48
+ ```sh npm
49
+ npm install @tsed/directus-sdk
50
+ ```
51
+
52
+ ```sh yarn
53
+ yarn add @tsed/directus-sdk
54
+ ```
55
+
56
+ ```sh pnpm
57
+ pnpm add @tsed/directus-sdk
58
+ ```
59
+
60
+ ```sh bun
61
+ bun add @tsed/directus-sdk
62
+ ```
63
+
64
+ ---
65
+
66
+ ## 🚀 Quick Start
67
+
68
+ ### Creating an Endpoint
69
+
70
+ ```ts
71
+ import {defineEndpoint} from "@tsed/directus-sdk";
72
+ import {inject} from "@tsed/di";
73
+
74
+ export default defineEndpoint({
75
+ id: "my-api",
76
+ handler: (router) => {
77
+ router.get("/hello", async (req, res) => {
78
+ const myService = inject(MyService);
79
+
80
+ const result = await myService.doSomething();
81
+ return res.json(result);
82
+ });
83
+ }
84
+ });
85
+ ```
86
+
87
+ ### Creating a Hook
88
+
89
+ ```ts
90
+ import {defineHook} from "@tsed/directus-sdk";
91
+ import {inject} from "@tsed/di";
92
+
93
+ export default defineHook(({filter, action}) => {
94
+ const validationService = inject(ValidationService);
95
+
96
+ filter("items.create", async (payload, meta) => {
97
+ await validationService.validate(payload);
98
+ return payload;
99
+ });
100
+
101
+ action("items.create", async (meta) => {
102
+ console.log("Item created:", meta.key);
103
+ });
104
+ });
105
+ ```
106
+
107
+ ### Creating an Operation
108
+
109
+ ```ts
110
+ import {defineOperationApi} from "@tsed/directus-sdk";
111
+ import {inject} from "@tsed/di";
112
+
113
+ type SendEmailOptions = {
114
+ to: string;
115
+ subject: string;
116
+ body: string;
117
+ };
118
+
119
+ export default defineOperationApi<SendEmailOptions>({
120
+ id: "send-email",
121
+ handler: async (options, context) => {
122
+ const emailService = inject(EmailService);
123
+
124
+ await emailService.send({
125
+ to: options.to,
126
+ subject: options.subject,
127
+ body: options.body
128
+ });
129
+
130
+ return {
131
+ success: true,
132
+ sentAt: new Date().toISOString()
133
+ };
134
+ }
135
+ });
136
+ ```
137
+
138
+ ---
139
+
140
+ ## 📚 Core Features
141
+
142
+ ### Dependency Injection
143
+
144
+ Use Ts.ED's dependency injection in all your Directus extensions:
145
+
146
+ ```ts
147
+ import {injectable} from "@tsed/di";
148
+
149
+ export class MyService {
150
+ async doSomething() {
151
+ return "Hello from MyService";
152
+ }
153
+ }
154
+
155
+ injectable(MyService);
156
+ ```
157
+
158
+ Then inject it using `inject()`:
159
+
160
+ ```ts
161
+ import {inject} from "@tsed/di";
162
+
163
+ const myService = inject(MyService);
164
+ ```
165
+
166
+ ### Cache Decorator
167
+
168
+ Cache method results using Directus cache infrastructure:
169
+
170
+ ```ts
171
+ import {Injectable} from "@tsed/di";
172
+ import {Cache} from "@tsed/directus-sdk";
173
+
174
+ @Injectable()
175
+ export class ApiClient {
176
+ @Cache({ttl: 900000})
177
+ async search(query: string) {
178
+ // Expensive operation
179
+ return this.performSearch(query);
180
+ }
181
+
182
+ @Cache({
183
+ ttl: 60000,
184
+ keyGenerator: (userId: string) => `user:${userId}`
185
+ })
186
+ async getUserById(userId: string) {
187
+ return this.fetchUser(userId);
188
+ }
189
+ }
190
+ ```
191
+
192
+ **Options:**
193
+
194
+ - `ttl` - Time to live in milliseconds (default: 900000ms = 15 minutes)
195
+ - `keyGenerator` - Custom function to generate cache keys
196
+ - `namespace` - Custom namespace for cache keys
197
+ - `useSystemCache` - Use system cache (true) or regular cache (false). Default: true
198
+
199
+ > Important: to use decorator you have to configure your tsconfig with the appropriate options (
200
+ > `"experimentalDecorators": true`, `"emitDecoratorMetadata": true`)
201
+
202
+ ### Programmatic Cache Binding
203
+
204
+ Apply cache programmatically using `useDirectusCache`:
205
+
206
+ ```ts
207
+ import {injectable} from "@tsed/di";
208
+ import {useDirectusCache} from "@tsed/directus-sdk";
209
+
210
+ export class JiraIssueClient {
211
+ search(query: string) {
212
+ return this.performSearch(query);
213
+ }
214
+ }
215
+
216
+ injectable(JiraIssueClient);
217
+ useDirectusCache(JiraIssueClient, "search", {ttl: 900000});
218
+ ```
219
+
220
+ ### Repository Pattern
221
+
222
+ Create type-safe repositories for your collections:
223
+
224
+ ```ts
225
+ import {injectable} from "@tsed/di";
226
+ import {DirectusItemsRepository} from "@tsed/directus-sdk";
227
+ import type {Item} from "@directus/types";
228
+
229
+ type Release = Item & {
230
+ id: string;
231
+ title: string;
232
+ status: "draft" | "published";
233
+ publishedAt?: string;
234
+ };
235
+
236
+ export class ReleasesRepository extends DirectusItemsRepository<Release, "releases"> {
237
+ protected collection = "releases" as const;
238
+
239
+ async getPublishedReleases() {
240
+ const collection = await this.getCollection();
241
+
242
+ return collection.readByQuery({
243
+ filter: {
244
+ status: {
245
+ _eq: "published"
246
+ }
247
+ },
248
+ sort: ["-publishedAt"]
249
+ });
250
+ }
251
+
252
+ async publishRelease(id: string) {
253
+ return this.update({
254
+ id,
255
+ status: "published",
256
+ publishedAt: new Date().toISOString()
257
+ });
258
+ }
259
+ }
260
+
261
+ injectable(ReleasesRepository);
262
+ ```
263
+
264
+ **Built-in Methods:**
265
+
266
+ - `getCollection()` - Get the Directus ItemsService for the collection
267
+ - `create(data, opts?)` - Create a new item
268
+ - `update(data, opts?)` - Update an existing item
269
+ - `listAll()` - Get all items without pagination
270
+
271
+ Use the repository in your endpoints:
272
+
273
+ ```ts
274
+ import {defineEndpoint} from "@tsed/directus-sdk";
275
+ import {inject} from "@tsed/di";
276
+
277
+ export default defineEndpoint({
278
+ id: "releases-api",
279
+ handler: (router) => {
280
+ const releasesRepo = inject(ReleasesRepository);
281
+
282
+ router.get("/releases", async (req, res) => {
283
+ const releases = await releasesRepo.getPublishedReleases();
284
+ res.json(releases);
285
+ });
286
+
287
+ router.post("/releases/:id/publish", async (req, res) => {
288
+ const release = await releasesRepo.publishRelease(req.params.id);
289
+ res.json(release);
290
+ });
291
+ }
292
+ });
293
+ ```
294
+
295
+ ### Directus Context Service
296
+
297
+ Access Directus context from anywhere in your code:
298
+
299
+ ```ts
300
+ import {injectable} from "@tsed/di";
301
+ import {DirectusContextService} from "@tsed/directus-sdk";
302
+
303
+ export class MyService {
304
+ directusContext = inject(DirectusContextService);
305
+
306
+ async doSomething() {
307
+ const context = this.directusContext.get();
308
+ const schema = await context.getSchema();
309
+ // Use schema...
310
+ }
311
+
312
+ async getUsersService() {
313
+ // Convenience method to get ItemsService
314
+ return this.directusContext.getItemsService("users");
315
+ }
316
+ }
317
+
318
+ injectable(MyService);
319
+ ```
320
+
321
+ **Methods:**
322
+
323
+ - `set(context)` - Set the Directus context (internal use)
324
+ - `get()` - Get the current Directus context
325
+ - `getItemsService(collection, options?)` - Create an ItemsService for a collection
326
+
327
+ ---
328
+
329
+ ## 🔧 Advanced Usage
330
+
331
+ ### Custom Endpoints with Multiple Routes
332
+
333
+ ```ts
334
+ import {defineEndpoint} from "@tsed/directus-sdk";
335
+ import {inject} from "@tsed/di";
336
+
337
+ export default defineEndpoint({
338
+ id: "api-v1",
339
+ handler: (router) => {
340
+ router.get("/users", async (req, res) => {
341
+ const userService = inject(UserService);
342
+ const users = await userService.findAll();
343
+ res.json(users);
344
+ });
345
+
346
+ router.post("/auth/login", async (req, res) => {
347
+ const authService = inject(AuthService);
348
+ const token = await authService.authenticate(req.body);
349
+ res.json({token});
350
+ });
351
+ }
352
+ });
353
+ ```
354
+
355
+ ### Hooks with Service Injection
356
+
357
+ ```ts
358
+ import {defineHook} from "@tsed/directus-sdk";
359
+ import {inject} from "@tsed/di";
360
+ import {logger} from "@tsed/logger";
361
+
362
+ export default defineHook(({filter, action}) => {
363
+ const productService = inject(ProductService);
364
+ const notificationService = inject(NotificationService);
365
+
366
+ logger().info("Product hooks loaded");
367
+
368
+ filter("product_rankings.items.create", async (payload) => {
369
+ return productService.ensureUniqueRanking(payload);
370
+ });
371
+
372
+ filter("product_rankings.items.update", async (payload) => {
373
+ return productService.ensureUniqueRanking(payload);
374
+ });
375
+
376
+ action("products.items.update", async (meta, context) => {
377
+ if (context.payload.status === "published") {
378
+ await notificationService.sendPublishedNotification(meta.key);
379
+ }
380
+ });
381
+ });
382
+ ```
383
+
384
+ ### Operations with Database Access
385
+
386
+ ```ts
387
+ import {defineOperationApi} from "@tsed/directus-sdk";
388
+ import {inject} from "@tsed/di";
389
+
390
+ type CalculateStatsOptions = {
391
+ collection: string;
392
+ field: string;
393
+ };
394
+
395
+ export default defineOperationApi<CalculateStatsOptions>({
396
+ id: "calculate-stats",
397
+ handler: async (options, context) => {
398
+ const statsService = inject(StatsService);
399
+ const directusContext = inject(DirectusContextService);
400
+
401
+ const itemsService = await directusContext.getItemsService(options.collection);
402
+ const items = await itemsService.readByQuery({limit: -1});
403
+
404
+ const stats = statsService.calculate(items, options.field);
405
+
406
+ return {
407
+ count: items.length,
408
+ average: stats.avg,
409
+ min: stats.min,
410
+ max: stats.max
411
+ };
412
+ }
413
+ });
414
+ ```
415
+
416
+ ---
417
+
418
+ ## 🎯 API Reference
419
+
420
+ ### Functions
421
+
422
+ - **`defineEndpoint(config)`** - Define a Directus endpoint with DI support
423
+ - **`defineHook(callback)`** - Define a Directus hook with DI support
424
+ - **`defineOperationApi(config)`** - Define a Directus operation with DI support
425
+ - **`wrapEndpoint(callback)`** - Internal wrapper for endpoints
426
+ - **`wrapOperation(callback)`** - Internal wrapper for operations
427
+ - **`useDirectusCache(token, propertyKey, opts)`** - Apply cache programmatically
428
+
429
+ ### Decorators
430
+
431
+ - **`@Cache(options)`** - Cache method results using Directus cache
432
+
433
+ ### Classes
434
+
435
+ - **`DirectusContextService`** - Service to access Directus context
436
+ - **`DirectusItemsRepository<T, Collection>`** - Base class for collection repositories
437
+ - **`DirectusCacheInterceptor`** - Interceptor for caching functionality
438
+
439
+ ### Types
440
+
441
+ - **`DirectusCacheOptions`** - Cache configuration options
442
+ - **`DirectusContext`** - Union of Directus extension contexts
443
+
444
+ ---
445
+
446
+ ## 📖 Documentation
447
+
448
+ For more information about Directus extensions, see:
449
+
450
+ - [Directus Endpoints Documentation](https://docs.directus.io/extensions/endpoints.html)
451
+ - [Directus Hooks Documentation](https://docs.directus.io/extensions/hooks.html)
452
+ - [Directus Operations Documentation](https://docs.directus.io/extensions/operations.html)
453
+
454
+ For more information about Ts.ED, see:
455
+
456
+ - [Ts.ED Documentation](https://tsed.dev/)
457
+ - [Ts.ED Dependency Injection](https://tsed.dev/docs/injection.html)
458
+
459
+ ---
460
+
461
+ ## Contributors
462
+
463
+ <a href="https://github.com/tsedio/tsed/graphs/contributors"><img src="https://opencollective.com/tsed/contributors.svg?width=890" /></a>
464
+
465
+ ## Backers
466
+
467
+ Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/tsed#backer)]
468
+
469
+ <a href="https://opencollective.com/tsed#backers" target="_blank"><img src="https://opencollective.com/tsed/tiers/backer.svg?width=890"></a>
470
+
471
+ ## Sponsors
472
+
473
+ Support this project by becoming a sponsor. Your logo will show up here with a link to your
474
+ website. [[Become a sponsor](https://opencollective.com/tsed#sponsor)]
475
+
476
+ ## License
477
+
478
+ The MIT License (MIT)
479
+
480
+ Copyright (c) 2016 - 2025 Romain Lenzotti
481
+
482
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
483
+ documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
484
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
485
+ persons to whom the Software is furnished to do so, subject to the following conditions:
486
+
487
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
488
+ Software.
489
+
490
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
491
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
492
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
493
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.