Dart Function Dependencies

Declare pub.dev packages your Dart Function needs via pubspec.yaml. Koolbase resolves dependencies at deploy time, caches the result, and runs your Function against the resolved package environment.

Why this exists

Production Dart Functions need access to real cryptography, HTTP clients, JWT libraries, payment SDKs, webhook signature verifiers, and other ecosystem packages. Koolbase supports per-function dependency declaration so you can use any pub.dev package — not just dart:* core libraries.

Each function version locks in its resolved dependency set. Redeploying with a different pubspec.yaml creates a new function version with its own dependency context. Existing versions continue running against the dependencies they were deployed with.

Declaring dependencies

Send a pubspec field alongside your function code at deploy time. The minimum shape requires name and environment.sdk:

pubspec.yaml
YAML
name: my_function
environment:
  sdk: '>=3.0.0 <4.0.0'
dependencies:
  crypto: ^3.0.0
  cryptography: ^2.7.0
  http: ^1.0.0

Required fields

  • name — any valid pub.dev package name; doesn't need to match the function name
  • environment.sdk — Dart SDK constraint; '>=3.0.0 <4.0.0' is a good default

Constraints

  • Maximum pubspec.yaml size: 64KB
  • Only the Dart runtime supports pubspec.yaml — sending it with a Deno function returns 400
  • Functions with no pubspec.yaml still work but can only use dart:* core libraries

The standard profile

Most Functions reach for the same handful of packages. Rather than declare them every time, deploy with the dart-standard runtime and Koolbase merges a curated set into your pubspec:

crypto: 3.0.5         # hashing / HMAC
cryptography: 2.7.0   # encryption / signing / key derivation
http: 1.2.2           # outbound APIs / webhooks
uuid: 4.5.1           # identifiers / idempotency keys
collection: 1.19.0    # general utilities

You can still declare your own packages — they merge alongside the profile's. A Function that needs nothing else can omit pubspec.yaml entirely:

koolbase deploy my-fn --file ./my_fn.dart --runtime dart-standard

The profile owns its packages

Redeclaring one of the five above fails the deploy rather than silently overriding it. The profile is a contract: every Function on a given version resolves to the same pinned set. To control those versions yourself, use runtime: dart and manage the full pubspec.

Profile versions

Profiles are versioned and immutable. Deploying with dart-standard resolves to the current version and stores that concrete version on your Function — you will see dart-standard-1 in the response and the dashboard, not the bare alias.

That pin is permanent. When a future profile ships with newer packages, existing Functions keep resolving against the version they were built with. Moving to a new profile is an explicit redeploy, never something that happens underneath you.

How resolution works

On deploy, Koolbase runs dart pub get against your pubspec.yaml:

  1. Resolves the effective pubspec — for dart that is exactly what you submitted; for dart-standard it is your pubspec merged with the profile's packages
  2. Computes a SHA-256 hash of the effective pubspec
  3. If a cached resolution exists for that hash, reuses it instantly (cache hit)
  4. Otherwise, runs dart pub get, persists the resolved package set, and generates pubspec.lock (cache miss — first deploy with this pubspec takes 30–90 seconds)
  5. Stores the effective pubspec, pubspec.lock, the resolution hash, and the pubspec you originally submitted on the function version

Subsequent deploys with the same dependency set are near-instant. The locked pubspec.lock guarantees the same dependency tree every time your function runs.

Failure modes

Dependency errors block deployment. Nothing partial gets persisted — if resolution fails, the function is not created and no disk state is written:

pubspec invalid YAML

Malformed YAML syntax

pubspec missing required field: name

No `name` field declared

pubspec missing required field: environment.sdk

No SDK constraint declared

pubspec resolution failed: ...

A package version conflict, unavailable package, or SDK incompatibility. The exact `dart pub get` error appears in the response body.

pubspec resolution timed out (3 min)

Dependency resolution took longer than 3 minutes — usually means deep version conflicts or registry slowness

pubspec exceeds 64KB limit

Your pubspec.yaml is too large

Example: SHA-256 hashing

Use package:crypto for SHA-256 digests:

pubspec.yaml
YAML
name: hash_function
environment:
  sdk: '>=3.0.0 <4.0.0'
dependencies:
  crypto: ^3.0.0
index.dart
Dart
import 'dart:convert';
import 'package:crypto/crypto.dart';

Future<Map<String, dynamic>> handler(Map<String, dynamic> ctx) async {
  final body = ctx['request']['body'] as Map<String, dynamic>;
  final input = body['data'] as String;

  final digest = sha256.convert(utf8.encode(input));

  return {
    'algorithm': 'SHA-256',
    'hash': digest.toString(),
  };
}

Example: AES-GCM encryption

Use package:cryptography for authenticated encryption:

pubspec.yaml
YAML
name: encrypt_function
environment:
  sdk: '>=3.0.0 <4.0.0'
dependencies:
  cryptography: ^2.7.0
index.dart
Dart
import 'dart:convert';
import 'package:cryptography/cryptography.dart';

Future<Map<String, dynamic>> handler(Map<String, dynamic> ctx) async {
  final body = ctx['request']['body'] as Map<String, dynamic>;
  final plaintext = body['plaintext'] as String;

  final algo = AesGcm.with256bits();
  final secretKey = await algo.newSecretKey();
  final keyBytes = await secretKey.extractBytes();

  final encrypted = await algo.encrypt(
    utf8.encode(plaintext),
    secretKey: secretKey,
  );

  return {
    'algorithm': 'AES-256-GCM',
    'key': base64Encode(keyBytes),
    'nonce': base64Encode(encrypted.nonce),
    'ciphertext': base64Encode(encrypted.cipherText),
    'mac': base64Encode(encrypted.mac.bytes),
  };
}

Production note

Don't generate a fresh encryption key per request. Store keys securely (e.g. as function secrets) and reuse them across invocations. The example above generates a key inline only for demonstration.

Example: Webhook signature verification

A common pattern: verifying incoming webhooks (Stripe, GitHub, etc.) using HMAC-SHA256:

pubspec.yaml
YAML
name: webhook_handler
environment:
  sdk: '>=3.0.0 <4.0.0'
dependencies:
  crypto: ^3.0.0
index.dart
Dart
import 'dart:convert';
import 'package:crypto/crypto.dart';

export const metadata = {
  title: 'Dart Dependencies — Functions',
  description: 'Declare pub.dev packages your Dart Function needs via pubspec.yaml. Koolbase resolves dependencies at deploy time, caches the result, and runs your Function ...',
}

Future<Map<String, dynamic>> handler(Map<String, dynamic> ctx) async {
  final request = ctx['request'] as Map<String, dynamic>;
  final headers = request['headers'] as Map<String, dynamic>;
  final body = request['body'] as Map<String, dynamic>;

  // Webhook signing secret stored as a function secret
  final env = ctx['env'] as Map<String, dynamic>;
  final secret = env['WEBHOOK_SIGNING_SECRET'] as String;

  // The incoming signature from the provider
  final receivedSig = headers['x-webhook-signature'] as String?;
  if (receivedSig == null) {
    return {'verified': false, 'reason': 'missing signature'};
  }

  // Compute HMAC of the raw body
  final hmac = Hmac(sha256, utf8.encode(secret));
  final payload = jsonEncode(body);
  final expectedSig = hmac.convert(utf8.encode(payload)).toString();

  if (receivedSig != expectedSig) {
    return {'verified': false, 'reason': 'signature mismatch'};
  }

  // ... process webhook event
  return {'verified': true, 'event': body};
}

Function versions and dependencies

Each function deploy produces a new version with its own locked dependency context:

  • Function version 1 with crypto: ^3.0.0 always runs against the crypto resolved at version 1 deploy time
  • Deploying version 2 with crypto: ^3.1.0 creates a new resolution; version 1 continues running against the old resolution
  • Triggered functions, cron functions, and retries all use the dependency context of the function version they were dispatched against

For dart-standard Functions the runtime string itself carries the pin: a Function stored as dart-standard-1 resolves against that profile's packages for its entire life.

A redeploy that breaks a dependency doesn't affect functions already in flight. Existing retries finish against the version they originally fired with.

Deploying via API

The SDK deploy endpoint requires a secret key (sk_*). Publishable keys (pk_*) are restricted to client-side surfaces and cannot deploy code.

terminal
bash
curl -X POST https://api.koolbase.com/v1/sdk/functions/deploy \
  -H "Content-Type: application/json" \
  -H "x-api-key: sk_test_..." \
  -d '{
    "runtime": "dart",
    "name": "my_function",
    "code": "Future<Map<String, dynamic>> handler(...) async { ... }",
    "pubspec": "name: my_function\nenvironment:\n  sdk: \">=3.0.0 <4.0.0\"\ndependencies:\n  crypto: ^3.0.0",
    "timeout_ms": 10000
  }'

Where to find your secret key

Each environment has both a publishable key and a secret key. In the Koolbase dashboard, open your project → environments → reveal the secret key. Treat secret keys like passwords — never embed them in client apps or commit them to source control.