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:
| Package | For target | What it does |
|---|---|---|
@sprintsail/runtime-aws | aws | Uses @aws-sdk/client-s3, pg (against RDS), @aws-sdk/client-secrets-manager, @aws-sdk/client-sqs. |
@sprintsail/runtime-sprintsail-runtime | sprintsail-runtime | Uses @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:
- Looks up the URN → finds the entry above.
- Reads credentials (
getSecretValueon AWS, or/var/run/sprintsail/db-creds/db/username|passwordon the runtime). - Constructs a
pgconnection, 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.