Skip to main content

The runtime adapter

When you call db.query(sql) inside a handler, the call has to land somewhere — pg against RDS on AWS, pg against CloudNativePG on the runtime. The runtime adapter is what makes that work transparently.

The contract

interface RuntimeAdapter {
getSecret(urn): Promise<string>;
queryDatabase(urn, sql, params?): Promise<unknown[]>;
putToBucket(urn, key, body): Promise<void>;
getFromBucket(urn, key): Promise<Buffer | null>;
publishToQueue(urn, message): Promise<void>;
}

Each primitive instance (db, stripeKey, receipts, ordersQueue) holds its URN. When you call db.query(sql), the SDK dispatches to getRuntime().queryDatabase(this.urn, sql).

Adapter packages

Two ship today, one per target:

PackageFor targetWhat it does
@sprintsail/runtime-awsawsUses @aws-sdk/client-s3, pg (against RDS), @aws-sdk/client-secrets-manager, @aws-sdk/client-sqs.
@sprintsail/runtime-sprintsail-runtimesprintsail-runtimeUses @aws-sdk/client-s3 against MinIO, pg against CNPG, projected secret files, amqplib against RabbitMQ.

You never import an adapter yourself. The container bundle the SDK builds for your handler imports the right one as a side effect:

// In the generated wrapper.mjs (you don't write this):
import '@sprintsail/runtime-aws'; // or runtime-sprintsail-runtime
import userHandler from './your-handler.ts';

On import, the adapter reads the SAIL_BINDINGS env var (a JSON map of urn → binding info injected by the provider) and calls installRuntime(adapter) on the SDK.

How the runtime locates bindings

SAIL_BINDINGS looks like this (pretty-printed):

{
"orders.database.orders": {
"id": "arn:aws:rds:us-east-1:…:db:orders-orders",
"type": "database",
"attributes": {
"endpoint": "orders-orders.xxx.rds.amazonaws.com",
"port": "5432",
"secretName": "orders/orders-credentials"
},
"bindingName": "db"
}
}

When you call db.query(sql), the adapter:

  1. Looks up the URN → finds the entry above.
  2. Reads credentials (getSecretValue on AWS, or /var/run/sprintsail/db-creds/db/username|password on the runtime).
  3. Constructs a pg connection, executes the SQL, returns rows.

Connections are cached per URN for the lifetime of the process.

Worker triggers

A Worker's triggers (queues that invoke the worker) are exposed via a different env var, SAIL_TRIGGERS, also JSON. The runtime adapter on the Sprintsail Runtime target exports a runWorkerLoop(handler) that reads SAIL_TRIGGERS, connects to each queue via amqplib, and invokes the handler in an SQS-shaped event ({ Records: [{ messageId, body }] }) — same shape as AWS Lambda's SQS event source mapping. Same handler code on both targets.

Mocking the adapter for local dev

For unit tests you can install a mock adapter before calling your handler:

import { installRuntime } from '@sprintsail/sdk';
import handler from '../src/handlers/process-order.js';

installRuntime({
getSecret: async () => 'sk_test_fake',
queryDatabase: async (_urn, sql) => [{ id: 1 }],
putToBucket: async () => {},
getFromBucket: async () => null,
publishToQueue: async () => {},
});

await handler({ orderId: 'X', amount: 100 });

sail dev will eventually do this automatically with local emulators (MinIO, Postgres, RabbitMQ in Docker). Planned for v1.1.