Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/rivenmedia/riven-ts/llms.txt

Use this file to discover all available pages before exploring further.

The @repo/util-plugin-sdk package bundles a set of focused utilities that solve common plugin authoring problems: timing async calls, managing type-safe dates, parsing environment-variable settings, mapping media items to Stremio-compatible API parameters, and validating data structures with Zod codecs. Each utility is designed to be imported individually so plugins only pull in what they need.

Helpers (lib/helpers/)

benchmark

Wraps any async function and measures its wall-clock execution time using the Web Performance API (performance.now()). Returns an object containing the original result and the elapsed time in milliseconds. Particularly useful for logging the latency of DataSource HTTP calls.
import { benchmark } from "@repo/util-plugin-sdk/helpers/benchmark";

const { result, timeTaken } = await benchmark(() =>
  this.get("/api/resource"),
);

this.logger.info(`Fetched resource in ${timeTaken.toFixed(2)}ms`);
fn
() => Promisable<unknown>
required
The async (or sync) function to execute and time. Called with no arguments. Must return a value — benchmark preserves the full return type via Awaited<ReturnType<T>>.
Return value:
result
Awaited<ReturnType<T>>
The resolved return value of fn, typed correctly.
timeTaken
number
Elapsed time in milliseconds with sub-millisecond precision, computed as performance.now() end − start.

dates

A re-export of the entire Luxon library with Settings.throwOnInvalid = true pre-configured. Importing from this module guarantees that any invalid DateTime operation throws a TypeError at the point of failure rather than silently producing an Invalid DateTime sentinel — eliminating an entire class of silent data corruption bugs.
// ✅ Use this instead of importing directly from 'luxon'
import { DateTime } from "@repo/util-plugin-sdk/helpers/dates";

const dt = DateTime.fromISO("not-a-date");
// Throws: Invalid DateTime — instead of returning an Invalid DateTime object
Never import { DateTime } from "luxon" directly inside a plugin. Always use the SDK re-export so that throwOnInvalid is guaranteed to be set, regardless of import order.

getStremioScrapeConfig

Maps any MediaItem entity to the three-field config object expected by Stremio-compatible scraper APIs. Handles all four media item types (Movie, Show, Season, Episode) and derives the correct identifier string (:season:episode) for series content.
import { getStremioScrapeConfig } from "@repo/util-plugin-sdk/helpers/get-stremio-scrape-config";

const config = await getStremioScrapeConfig(item);
// e.g. { identifier: ":2:3", scrapeType: "series", imdbId: "tt0903747" }

const url = `https://stremio-addon.example.com/stream/${config.scrapeType}/${config.imdbId}${config.identifier ?? ""}.json`;
item
MediaItem
required
Any MediaItem subclass instance (Movie, Show, Season, or Episode). Must have a non-null imdbId — throws an Error if imdbId is missing. Throws if an unsupported media item type is provided.
Return value:
identifier
`:\${string}:\${string}` | null
The :season:episode suffix for series content. null for movies.
  • Movienull
  • Show":1:1" (first episode)
  • Season":<seasonNumber>:1"
  • Episode":<seasonNumber>:<episodeNumber>"
scrapeType
"series" | "movie"
"movie" for Movie, "series" for all show-like types.
imdbId
string
The IMDB identifier of the media item. Taken directly from item.imdbId.

Utilities (lib/utilities/)

DataSourceMap

A typed extension of the built-in Map<DataSourceConstructor, BaseDataSource> that overrides .get() to throw when a constructor is not registered, rather than returning undefined. This makes misconfigured plugins fail fast during startup rather than producing subtle null-dereference bugs at runtime.
import { DataSourceMap } from "@repo/util-plugin-sdk";

// Inside context factory or validator:
const client = dataSources.get(MyApiDataSource);
// ✅ Returns InstanceType<typeof MyApiDataSource>

// If MyApiDataSource is not in plugin.dataSources:
// ❌ Throws: DataSource for MyApiDataSource not found in DataSourceMap
Always list every DataSource you intend to call in the dataSources array of your RivenPlugin definition. The DataSourceMap is populated from that array, so any constructor you omit will cause a throw on .get().

PluginSettings

Manages typed, prefix-namespaced plugin settings sourced from environment variables. Riven instantiates a single PluginSettings during startup, calls .set() once per plugin to register its schema, then calls .lock() to freeze all settings before any resolver runs. Environment variable format:
RIVEN_PLUGIN_SETTING__<PREFIX>__<KEY>=value
# e.g.
RIVEN_PLUGIN_SETTING__MY_PLUGIN__API_KEY=secret
RIVEN_PLUGIN_SETTING__MY_PLUGIN__BASE_URL=https://api.example.com

Methods

.set(configPrefix, schema)
method
Registers and parses the settings block for a given prefix. Must be called before .lock(). Throws if settings are already locked or if no environment variables are found for the given prefix.
.get(schema)
method
Retrieves the previously parsed settings object for a schema. Throws if the schema was never registered via .set().Returns: z.infer<T> — the fully parsed and validated settings object.
.lock()
method
Freezes all registered settings using a deep-freeze. Called by Riven after all plugins are registered. Any subsequent .set() call throws. Logs a warning for any RIVEN_PLUGIN_SETTING__* variables that were not consumed by any schema.
// Inside your plugin's validator or context factory:
import z from "zod";

const mySchema = z.object({
  apiKey: z.string().min(1),
  baseUrl: z.url().default("https://api.example.com"),
});

// settings is provided by Riven — do not construct it yourself
const { apiKey, baseUrl } = settings.get(mySchema);

StatusCodes

A direct re-export of the StatusCodes enum from the http-status-codes package. Use it in event handler responses wherever a numeric HTTP status code is required.
import { StatusCodes } from "@repo/util-plugin-sdk/utilities/status-codes";

hooks: {
  "riven.media-item.stream-link.requested": async ({ event }) => {
    const link = await myApi.getStreamLink(event.item.providerDownloadId);

    if (!link) {
      return { success: false, statusCode: StatusCodes.NOT_FOUND };
    }

    return { success: true, data: { link, isPermalink: true } };
  },
}

Validation Codecs (lib/validation/)

These Zod utilities are exported from @repo/util-plugin-sdk/validation alongside a re-export of z from Zod itself.

json(schema)

A Zod codec that bidirectionally transforms between a raw string and a parsed JSON value. The decoded value is validated against the provided schema.
schema
z.core.$ZodType
required
Any Zod schema. The decoded JSON value is piped through this schema before being returned.
import { json } from "@repo/util-plugin-sdk/validation";
import z from "zod";

const metadataCodec = json(z.object({ title: z.string(), year: z.number() }));

// Decode (string → object)
const parsed = metadataCodec.parse('{"title":"Inception","year":2010}');
// => { title: "Inception", year: 2010 }

// Encode (object → string)
const encoded = metadataCodec.encode({ title: "Inception", year: 2010 });
// => '{"title":"Inception","year":2010}'

urlSearchParamsCodec

A Zod codec that bidirectionally transforms between a query string and a URLSearchParams instance.
import { urlSearchParamsCodec } from "@repo/util-plugin-sdk/validation";

// Decode
const params = urlSearchParamsCodec.parse("category=movies&page=2");
// => URLSearchParams { 'category' => 'movies', 'page' => '2' }

// Encode
const str = urlSearchParamsCodec.encode(new URLSearchParams({ q: "batman" }));
// => "q=batman"

atLeastOnePropertyRequired(obj, fields?)

A plain function (not a Zod refinement directly) that returns true if at least one of the specified fields in obj is non-null, non-empty-string, non-zero, and non-empty-array. Pass it to z.refine() or z.superRefine().
obj
Record<string, unknown>
required
The object to inspect.
fields
(keyof T)[]
If provided, only the listed keys are checked. If omitted, all keys are checked.
import { atLeastOnePropertyRequired } from "@repo/util-plugin-sdk/validation";
import z from "zod";

const ExternalIds = z
  .object({
    imdbId: z.string().nullable().optional(),
    tmdbId: z.string().nullable().optional(),
    tvdbId: z.string().nullable().optional(),
  })
  .refine(
    (val) => atLeastOnePropertyRequired(val, ["imdbId", "tmdbId", "tvdbId"]),
    "At least one external ID is required",
  );

recordIsNotEmpty

A plain function that returns true if an object has at least one own key. Use it with z.refine() to ensure a record-type field is not an empty object {}.
import { recordIsNotEmpty } from "@repo/util-plugin-sdk/validation";
import z from "zod";

const NonEmptyResults = z
  .record(z.string(), z.string())
  .refine(recordIsNotEmpty, "Results must contain at least one entry");
Both atLeastOnePropertyRequired and recordIsNotEmpty are plain boolean-returning functions, not Zod .superRefine() callbacks. Pass them directly to .refine() as the predicate argument.

Build docs developers (and LLMs) love