npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tsed/directus-sdk

v8.25.1

Published

A library to integrate Directus SDK with Ts.ED, facilitating the creation of extensions (endpoints, hooks, operations) and the use of Directus services.

Readme

semantic-release code style: prettier github opencollective

A package that integrates Ts.ED dependency injection with Directus extensions (endpoints, hooks, and operations).


✨ Features

  • 🎯 Dependency Injection - Use Ts.ED's DI container in Directus extensions
  • 🪝 Hooks Support - Create hooks with service injection and error handling
  • 🌐 Endpoints - Build API endpoints with automatic DI context management
  • ⚙️ Operations - Create flow operations with full DI support
  • 💾 Cache Decorator - Built-in caching using Directus cache infrastructure
  • 📦 Repository Pattern - Type-safe repository base class for collections
  • 🔌 Context Service - Access Directus context from anywhere in your code

📦 Installation

Install with your favorite package manager:

npm install @tsed/directus-sdk
yarn add @tsed/directus-sdk
pnpm add @tsed/directus-sdk
bun add @tsed/directus-sdk

🚀 Quick Start

Creating an Endpoint

import {defineEndpoint} from "@tsed/directus-sdk";
import {inject} from "@tsed/di";

export default defineEndpoint({
  id: "my-api",
  handler: (router) => {
    router.get("/hello", async (req, res) => {
      const myService = inject(MyService);

      const result = await myService.doSomething();
      return res.json(result);
    });
  }
});

Creating a Hook

import {defineHook} from "@tsed/directus-sdk";
import {inject} from "@tsed/di";

export default defineHook(({filter, action}) => {
  const validationService = inject(ValidationService);

  filter("items.create", async (payload, meta) => {
    await validationService.validate(payload);
    return payload;
  });

  action("items.create", async (meta) => {
    console.log("Item created:", meta.key);
  });
});

Creating an Operation

import {defineOperationApi} from "@tsed/directus-sdk";
import {inject} from "@tsed/di";

type SendEmailOptions = {
  to: string;
  subject: string;
  body: string;
};

export default defineOperationApi<SendEmailOptions>({
  id: "send-email",
  handler: async (options, context) => {
    const emailService = inject(EmailService);

    await emailService.send({
      to: options.to,
      subject: options.subject,
      body: options.body
    });

    return {
      success: true,
      sentAt: new Date().toISOString()
    };
  }
});

📚 Core Features

Dependency Injection

Use Ts.ED's dependency injection in all your Directus extensions:

import {injectable} from "@tsed/di";

export class MyService {
  async doSomething() {
    return "Hello from MyService";
  }
}

injectable(MyService);

Then inject it using inject():

import {inject} from "@tsed/di";

const myService = inject(MyService);

Directus Context Service

Access Directus context from anywhere in your code:

import {injectable} from "@tsed/di";
import {DirectusContextService} from "@tsed/directus-sdk";

export class MyService {
  directusContext = inject(DirectusContextService);

  async doSomething() {
    const context = this.directusContext.get();
    const schema = await context.getSchema();
    // Use schema...
  }

  async getUsersService() {
    // Convenience method to get ItemsService
    return this.directusContext.getItemsService("users");
  }
}

injectable(MyService);

Methods:

  • set(context) - Set the Directus context (internal use)
  • get() - Get the current Directus context
  • getItemsService(collection, options?) - Create an ItemsService for a collection

Repository Pattern

Create type-safe repositories for your collections:

import {injectable} from "@tsed/di";
import {DirectusItemsRepository} from "@tsed/directus-sdk";
import type {Item} from "@directus/types";

type Release = Item & {
  id: string;
  title: string;
  status: "draft" | "published";
  publishedAt?: string;
};

export class ReleasesRepository extends DirectusItemsRepository<Release, "releases"> {
  protected collection = "releases" as const;

  async getPublishedReleases() {
    const collection = await this.getCollection();

    return collection.readByQuery({
      filter: {
        status: {
          _eq: "published"
        }
      },
      sort: ["-publishedAt"]
    });
  }

  async publishRelease(id: string) {
    return this.update({
      id,
      status: "published",
      publishedAt: new Date().toISOString()
    });
  }
}

injectable(ReleasesRepository);

Built-in Methods:

  • getCollection() - Get the Directus ItemsService for the collection
  • create(data, opts?) - Create a new item
  • update(data, opts?) - Update an existing item
  • listAll() - Get all items without pagination

Use the repository in your endpoints:

import {defineEndpoint} from "@tsed/directus-sdk";
import {inject} from "@tsed/di";

export default defineEndpoint({
  id: "releases-api",
  handler: (router) => {
    const releasesRepo = inject(ReleasesRepository);

    router.get("/releases", async (req, res) => {
      const releases = await releasesRepo.getPublishedReleases();
      res.json(releases);
    });

    router.post("/releases/:id/publish", async (req, res) => {
      const release = await releasesRepo.publishRelease(req.params.id);
      res.json(release);
    });
  }
});

Cache

Because @directus/api/cache isn't declared as external dependency to build the extension you have to configure the rollup bundler to exclude it from the build.

So for each extension you have to add the following to the extension.config.js, if your extension build fail:

function externals() {
  return {
    name: "external-plus", // this name will show up in logs and errors
    version: "1.0.0",
    options(rawOptions) {
      if (rawOptions.external?.includes("directus:api")) {
        rawOptions.external.push("@directus/api/cache");
      }

      return rawOptions;
    }
  };
}

export default {
  plugins: [externals()]
};

Cache Decorator

Cache method results using Directus cache infrastructure:

import {Injectable} from "@tsed/di";
import {Cache} from "@tsed/directus-sdk/cache";

@Injectable()
export class ApiClient {
  @Cache({ttl: 900000})
  async search(query: string) {
    // Expensive operation
    return this.performSearch(query);
  }

  @Cache({
    ttl: 60000,
    keyGenerator: (userId: string) => `user:${userId}`
  })
  async getUserById(userId: string) {
    return this.fetchUser(userId);
  }
}

Options:

  • ttl - Time to live in milliseconds (default: 900000ms = 15 minutes)
  • keyGenerator - Custom function to generate cache keys
  • namespace - Custom namespace for cache keys
  • useSystemCache - Use system cache (true) or regular cache (false). Default: true

Important: to use decorator you have to configure your tsconfig with the appropriate options ( "experimentalDecorators": true, "emitDecoratorMetadata": true)

Programmatic Cache Binding

Apply cache programmatically using useDirectusCache:

import {injectable} from "@tsed/di";
import {useDirectusCache} from "@tsed/directus-sdk/cache";

export class JiraIssueClient {
  search(query: string) {
    return this.performSearch(query);
  }
}

injectable(JiraIssueClient).directusCache("search", {ttl: 900000});

🔧 Advanced Usage

Custom Endpoints with Multiple Routes

import {defineEndpoint} from "@tsed/directus-sdk";
import {inject} from "@tsed/di";

export default defineEndpoint({
  id: "api-v1",
  handler: (router) => {
    router.get("/users", async (req, res) => {
      const userService = inject(UserService);
      const users = await userService.findAll();
      res.json(users);
    });

    router.post("/auth/login", async (req, res) => {
      const authService = inject(AuthService);
      const token = await authService.authenticate(req.body);
      res.json({token});
    });
  }
});

Hooks with Service Injection

import {defineHook} from "@tsed/directus-sdk";
import {inject} from "@tsed/di";
import {logger} from "@tsed/logger";

export default defineHook(({filter, action}) => {
  const productService = inject(ProductService);
  const notificationService = inject(NotificationService);

  logger().info("Product hooks loaded");

  filter("product_rankings.items.create", async (payload) => {
    return productService.ensureUniqueRanking(payload);
  });

  filter("product_rankings.items.update", async (payload) => {
    return productService.ensureUniqueRanking(payload);
  });

  action("products.items.update", async (meta, context) => {
    if (context.payload.status === "published") {
      await notificationService.sendPublishedNotification(meta.key);
    }
  });
});

Operations with Database Access

import {defineOperationApi} from "@tsed/directus-sdk";
import {inject} from "@tsed/di";

type CalculateStatsOptions = {
  collection: string;
  field: string;
};

export default defineOperationApi<CalculateStatsOptions>({
  id: "calculate-stats",
  handler: async (options, context) => {
    const statsService = inject(StatsService);
    const directusContext = inject(DirectusContextService);

    const itemsService = await directusContext.getItemsService(options.collection);
    const items = await itemsService.readByQuery({limit: -1});

    const stats = statsService.calculate(items, options.field);

    return {
      count: items.length,
      average: stats.avg,
      min: stats.min,
      max: stats.max
    };
  }
});

🎯 API Reference

Functions

  • defineEndpoint(config) - Define a Directus endpoint with DI support
  • defineHook(callback) - Define a Directus hook with DI support
  • defineOperationApi(config) - Define a Directus operation with DI support
  • wrapEndpoint(callback) - Internal wrapper for endpoints
  • wrapOperation(callback) - Internal wrapper for operations
  • useDirectusCache(token, propertyKey, opts) - Apply cache programmatically

Decorators

  • @Cache(options) - Cache method results using Directus cache

Classes

  • DirectusContextService - Service to access Directus context
  • DirectusItemsRepository<T, Collection> - Base class for collection repositories
  • DirectusCacheInterceptor - Interceptor for caching functionality

Types

  • DirectusCacheOptions - Cache configuration options
  • DirectusContext - Union of Directus extension contexts

📖 Documentation

For more information about Directus extensions, see:

For more information about Ts.ED, see:


Contributors

Backers

Thank you to all our backers! 🙏 [Become a backer]

Sponsors

Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor]

License

The MIT License (MIT)

Copyright (c) 2016 - 2025 Romain Lenzotti

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.