@veridia/functions-sdk
v1.0.29
Published
TypeScript SDK for developing and testing Veridia integration functions
Readme
@veridia/functions-sdk
TypeScript types and ambient declarations for writing handlers that run inside the Veridia functions runtime.
The SDK ships:
- Handler types (
ConnectionHandler,ActionHandler,DestinationHandler, plus pre-typed Kafka/HTTP aliases). - Event/response shapes (
KafkaEvent,HttpEvent,HttpResponse,FunctionContext,FunctionResponse). - Error classes (
InvalidEventPayload,ValidationError,EventNotSupported,RetryError,FinalError). - An ambient
.d.ts(@veridia/functions-sdk/globals) that types the values the runtime injects into every handler module (veridiaClient,veridia, the five error classes).
Your handler runs in a sandboxed Deno isolate. The SDK is types-only — no runtime code from this package executes inside your function.
Requirements
Deno 2.x. The runtime is Deno-based; you write Deno modules, not Node ones.
Installation
Add the SDK to your deno.json import map:
{
"imports": {
"@veridia/functions-sdk": "npm:@veridia/functions-sdk@^1"
}
}Or use deno add:
deno add npm:@veridia/functions-sdkTo get types for the runtime-injected globals (veridiaClient, veridia, error classes), add this once at the top of your handler module — anywhere TypeScript sees it is enough:
import type {} from "@veridia/functions-sdk/globals";Quick start
import type { ActionHandler } from "@veridia/functions-sdk";
import type {} from "@veridia/functions-sdk/globals";
type Secrets = { SLACK_WEBHOOK_URL: string };
type Parameters = { userId: string; message: string };
export const handler: ActionHandler<Parameters, Secrets> = async (
parameters,
secrets,
context,
) => {
console.log("sending slack message", { userId: parameters.userId });
await fetch(secrets.SLACK_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: parameters.message }),
});
veridiaClient.track(
"userId",
parameters.userId,
"Slack Notification Sent",
crypto.randomUUID(),
new Date().toISOString(),
{ message: parameters.message },
);
};A handler module is just an ES module with a named handler export. The runtime invokes it once per inbound event.
Handler types
Every handler is (event | parameters, secrets, context) => Promise<TResponse>. secrets is passed explicitly — type it as a literal record to get autocomplete on keys. context is FunctionContext (tenant id, function id, idempotency key, etc.).
Connection — Kafka batch
import type { KafkaBatchConnectionHandler } from "@veridia/functions-sdk";
type Secrets = { API_KEY: string };
type OrderPayload = {
orderId: string;
userId: string;
total: number;
currency: string;
placedAt: string;
};
export const handler: KafkaBatchConnectionHandler<Secrets> = async (
batch,
secrets,
context,
) => {
for (const event of batch) {
const order = event.value as OrderPayload;
veridiaClient.identify("userId", order.userId, {
lastOrderId: order.orderId,
lastOrderTotal: order.total,
});
veridiaClient.track(
"userId",
order.userId,
"Order Placed",
order.orderId,
order.placedAt,
{ total: order.total, currency: order.currency },
);
}
};KafkaEvent:
interface KafkaEvent {
topic: string;
partition: number;
offset: string;
key: string;
value: unknown; // cast to your payload type
headers: Record<string, string>;
timestamp: string;
}Connection — HTTP
import type { HttpConnectionHandler } from "@veridia/functions-sdk";
export const handler: HttpConnectionHandler = async (event, _secrets, _context) => {
const body = JSON.parse(event.body ?? "{}") as { userId: string; email: string };
veridiaClient.identify("userId", body.userId, { email: body.email });
return { status: 204 };
};HttpEvent / HttpResponse:
interface HttpEvent {
method: string;
path: string;
headers: Record<string, string>;
query: Record<string, string>;
body?: string;
}
interface HttpResponse {
status: number;
statusText?: string;
headers?: Record<string, string>;
body?: string;
}Action
Runs side effects from explicit parameters. Same shape as Connection but the input is a parameters object rather than an event:
import type { ActionHandler } from "@veridia/functions-sdk";
export const handler: ActionHandler<{ to: string; subject: string }, { API_KEY: string }> = async (
parameters,
secrets,
) => {
await fetch("https://api.example.com/email", {
method: "POST",
headers: { Authorization: `Bearer ${secrets.API_KEY}` },
body: JSON.stringify(parameters),
});
};Destination
Forwards an event to an external system:
import type { DestinationHandler } from "@veridia/functions-sdk";
type Event = { identifierId: string; eventType: string; properties: Record<string, unknown> };
type Secrets = { API_KEY: string; BASE_URL: string };
export const handler: DestinationHandler<Event, Secrets> = async (event, secrets) => {
await fetch(`${secrets.BASE_URL}/events`, {
method: "POST",
headers: {
Authorization: `Bearer ${secrets.API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(event),
});
};Error handling
Throw one of the SDK's error classes to signal a structured failure. The runtime classifies the response based on which class you threw. Anything else becomes "UnknownError".
| Class | When to use |
|---|---|
| InvalidEventPayload | The incoming event is malformed or missing required fields |
| ValidationError | The data fails business-level validation |
| EventNotSupported | The event type is not handled by this function |
| RetryError | A transient upstream failure occurred; the invocation should be retried |
| FinalError | A terminal failure; the invocation should not be retried |
The classes are available as runtime globals inside your handler (no import needed), and as named imports from @veridia/functions-sdk for tooling/tests:
import type { DestinationHandler } from "@veridia/functions-sdk";
import type {} from "@veridia/functions-sdk/globals";
export const handler: DestinationHandler<MyEvent, Secrets> = async (event, secrets) => {
if (!event.userId) throw new InvalidEventPayload("userId is required");
try {
await fetch(secrets.ENDPOINT, { /* … */ });
} catch (err) {
throw new RetryError(`upstream failed: ${err}`);
}
};Runtime-injected globals
The runtime makes a small set of values available to every handler module without an import. Pull in the ambient declarations once with import type {} from "@veridia/functions-sdk/globals"; and you'll get types for:
| Global | Type | Purpose |
|---|---|---|
| veridiaClient | VeridiaClient | identify(...) profiles and track(...) events |
| veridia | VeridiaServices | Channel + profile services (render templates, send in-app, resolve canonical ids, etc.) |
| console | log collector | log / info / warn / error / debug; captured into FunctionResponse.logs |
| Buffer | node:buffer.Buffer | Pre-bound for compatibility with npm packages that expect it |
| InvalidEventPayload, ValidationError, EventNotSupported, RetryError, FinalError | error classes | See Error handling |
veridiaClient.identify(identifierType, identifierId, attributes) and veridiaClient.track(identifierType, identifierId, eventType, eventId, eventTime, properties) are queued; the runtime flushes them as part of the function response.
Importing dependencies
You can import any npm, JSR, or Deno-registered package directly in your handler module. The runtime resolves bare specifiers as npm packages automatically, so all four forms are equivalent and supported:
import _ from "lodash"; // resolved as npm:lodash
import _ from "npm:lodash"; // npm explicit
import { decode } from "jsr:@std/encoding/hex"; // jsr explicit
import { SMTPClient } from "https://deno.land/x/[email protected]/mod.ts"; // deno.land/x
import crypto from "node:crypto"; // node built-ins via Deno's node compatAllowed prefixes:
npm:— any package on the npm registry (default for bare specifiers).jsr:— any package on JSR.node:— Node built-ins via Deno's node compatibility layer (node:crypto,node:buffer,node:zlib,node:timers,node:net,node:tls, …).https://deno.land/...andhttps://jsr.io/...— direct URL imports from the two allowlisted hosts.
Other HTTPS hosts (esm.sh, unpkg, raw GitHub, etc.) are blocked by the runtime. Relative imports (./helpers.ts) don't work — each function is a single module file.
Persistent state across invocations
Top-level state in your handler module is preserved for the lifetime of a warm worker. Plain Maps, Sets, or module-level let bindings all work — there's no SDK-provided cache abstraction, because you don't need one:
import type { ActionHandler } from "@veridia/functions-sdk";
type Token = { value: string; expiresAt: number };
const tokens = new Map<string, Token>();
export const handler: ActionHandler<Parameters, Secrets> = async (parameters, secrets) => {
let token = tokens.get(secrets.CLIENT_ID);
if (!token || token.expiresAt < Date.now()) {
const res = await fetch("https://auth.example.com/token", {
method: "POST",
body: JSON.stringify({
client_id: secrets.CLIENT_ID,
client_secret: secrets.CLIENT_SECRET,
}),
});
const { access_token } = await res.json();
token = { value: access_token, expiresAt: Date.now() + 50 * 60 * 1000 };
tokens.set(secrets.CLIENT_ID, token);
}
await fetch("https://api.example.com/do-thing", {
headers: { Authorization: `Bearer ${token.value}` },
method: "POST",
body: JSON.stringify(parameters),
});
};The state is per-worker and in-memory — it disappears on cold start and is not shared between workers or regions.
Exports
| Import path | Contents |
|---|---|
| @veridia/functions-sdk | Handler / event / response types, error classes, VeridiaClient / VeridiaServices types |
| @veridia/functions-sdk/globals | Ambient .d.ts declarations for veridiaClient, veridia, and the error classes |
License
MIT
