@veridia/functions-sdk
v1.0.32
Published
TypeScript SDK for developing and testing Veridia integration functions
Downloads
1,365
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
