Sdk

TypeScript SDK

Installation, configuration, and API reference for the Sluice TypeScript SDK.

Sluice SDK -- TypeScript

Installation

pnpm add @ontopix/sluice

The package is published to the Ontopix CodeArtifact registry.

Configuration

Constructor

import { SluiceConfig } from "@ontopix/sluice";

const config = new SluiceConfig({
  tableName: "ontopix-vendor-buckets-prod",   // DynamoDB table
  region: "eu-central-1",                      // AWS region
  endpointUrl: undefined,                      // Custom DynamoDB endpoint (local dev)
  leaseTtl: 60,                                // Lease TTL in seconds
  maxRetries: 3,                               // Retries on contention
  defaultSlotTimeout: 30.0,                    // Default timeout for slot() in seconds
  logLevel: "INFO",                            // Logging level
});

All options are optional and fall back to the defaults shown above.

From environment variables

const config = SluiceConfig.fromEnv();

fromEnv() reads these environment variables, falling back to the same defaults:

VariableTypeDefault
SLUICE_TABLE_NAMEstring"ontopix-vendor-buckets-prod"
SLUICE_AWS_REGIONstring"eu-central-1"
SLUICE_ENDPOINT_URLstringundefined
SLUICE_LEASE_TTLnumber60
SLUICE_MAX_RETRIESnumber3
SLUICE_DEFAULT_SLOT_TIMEOUTnumber30.0
SLUICE_LOG_LEVELstring"INFO"

You can also pass overrides to fromEnv():

const config = SluiceConfig.fromEnv({ maxRetries: 5 });

When no config argument is passed to any function, SluiceConfig.fromEnv() is called automatically.


slot -- handle-based API (preferred)

slot retries acquire internally until a slot is granted or the timeout expires. Returns a SlotHandle that must be released when done.

import { slot } from "@ontopix/sluice";

const handle = await slot("openai#gpt-4o#requests", { timeout: 10 });
try {
  const response = await callOpenAI(prompt);
} finally {
  await handle.release();
}

Using Symbol.asyncDispose

SlotHandle implements Symbol.asyncDispose, so you can use await using (TypeScript 5.2+ with a compatible runtime):

import { slot } from "@ontopix/sluice";

{
  await using handle = await slot("openai#gpt-4o#requests", { timeout: 10 });
  const response = await callOpenAI(prompt);
}
// handle.release() called automatically

Signature

async function slot(
  dimension: string,
  options?: {
    timeout?: number;          // seconds; defaults to config.defaultSlotTimeout
    config?: SluiceConfig;
  },
): Promise<SlotHandle>

SlotHandle

interface SlotHandle {
  result: AcquireResult;
  release: () => Promise<void>;
  [Symbol.asyncDispose]: () => Promise<void>;
}

Multiple dimensions

Use slotMany to acquire slots across multiple dimensions atomically:

import { slotMany } from "@ontopix/sluice";

const handle = await slotMany(
  ["openai#gpt-4o#requests", "openai#gpt-4o#tokens"],
  { timeout: 15 },
);
try {
  const response = await callOpenAI(prompt);
} finally {
  await handle.release();
}

acquire / release -- low-level API

acquire performs a single attempt to obtain a slot. It returns immediately with either Granted or RetryIn. The caller is responsible for retry logic and calling release().

import { acquire, AcquireOutcome } from "@ontopix/sluice";

const result = await acquire("openai#gpt-4o#requests");

if (result.outcome === AcquireOutcome.Granted) {
  try {
    const response = await callOpenAI(prompt);
  } finally {
    await result.release();
  }
} else {
  console.log(`Retry after ${result.waitSeconds.toFixed(1)}s`);
}

Signatures

async function acquire(
  dimension: string,
  config?: SluiceConfig,
): Promise<AcquireResult>

async function acquireMany(
  dimensions: string[],
  config?: SluiceConfig,
): Promise<AcquireResult>

acquireMany is all-or-nothing: if any dimension has insufficient tokens, none are acquired and a single RetryIn result is returned with the maximum wait time.

AcquireResult

interface AcquireResult {
  outcome: AcquireOutcome;
  waitSeconds: number;
  leaseKey?: string;
  dimension?: string;
  release: () => Promise<void>;
}

AcquireOutcome

enum AcquireOutcome {
  Granted = "granted",
  RetryIn = "retry_in",
}

penalize

Reduces the token count for a dimension by a factor. Use this when a vendor returns 429 despite the slot being granted.

Best-effort: not transactional, no version check. Errors are silently swallowed.

import { penalize } from "@ontopix/sluice";

// Reduce tokens to 80% of current value (default factor)
await penalize("openai#gpt-4o#requests");

// Custom factor
await penalize("openai#gpt-4o#requests", 0.5);

Signature

async function penalize(
  dimension: string,
  factor?: number,            // 0.0 to 1.0; defaults to 0.8
  config?: SluiceConfig,
): Promise<void>

Error handling

SlotTimeout

Thrown by slot() and slotMany() when the timeout expires without acquiring a slot.

import { slot, SlotTimeout } from "@ontopix/sluice";

try {
  const handle = await slot("openai#gpt-4o#requests", { timeout: 5 });
  try {
    await callOpenAI(prompt);
  } finally {
    await handle.release();
  }
} catch (err) {
  if (err instanceof SlotTimeout) {
    // Handle timeout
  }
  throw err;
}

Missing dimensions

If a dimension does not exist in the DynamoDB table, acquire throws a standard Error. Ensure dimensions are provisioned before calling the SDK.


SluiceClient -- connection reuse

Module-level functions (slot, acquire, etc.) create a new DynamoDB connection on each call. For multiple calls within the same process, use SluiceClient to hold the connection:

import { SluiceClient } from "@ontopix/sluice";

const client = new SluiceClient();
try {
  const handle = await client.slot("openai#gpt-4o#requests", { timeout: 10 });
  try {
    await callOpenAI(prompt);
  } finally {
    await handle.release();
  }
} finally {
  client.destroy();
}

SluiceClient accepts an optional SluiceConfig:

const client = new SluiceClient(new SluiceConfig({ maxRetries: 5 }));

The client provides the same methods as the module-level functions: acquire, acquireMany, slot, slotMany, penalize. Call destroy() when done to clean up the connection.


Full example

import { slot, penalize, SlotTimeout } from "@ontopix/sluice";

async function callWithRateLimit(prompt: string): Promise<string> {
  const handle = await slot("openai#gpt-4o#requests", { timeout: 10 });
  try {
    return await callOpenAI(prompt);
  } finally {
    await handle.release();
  }
}

async function callWithBackpressure(prompt: string): Promise<string> {
  const handle = await slot("openai#gpt-4o#requests", { timeout: 10 });
  try {
    try {
      return await callOpenAI(prompt);
    } catch (err) {
      if (isRateLimitError(err)) {
        await penalize("openai#gpt-4o#requests", 0.5);
      }
      throw err;
    }
  } finally {
    await handle.release();
  }
}