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.yamlYAML
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

How resolution works

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

  1. Computes a SHA-256 hash of your pubspec.yaml content
  2. If a cached resolution exists for that hash, reuses it instantly (cache hit)
  3. 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)
  4. Stores pubspec.yaml, pubspec.lock, and the resolution hash 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.yamlYAML
name: hash_function
environment:
  sdk: '>=3.0.0 <4.0.0'
dependencies:
  crypto: ^3.0.0
index.dartDart
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.yamlYAML
name: encrypt_function
environment:
  sdk: '>=3.0.0 <4.0.0'
dependencies:
  cryptography: ^2.7.0
index.dartDart
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.yamlYAML
name: webhook_handler
environment:
  sdk: '>=3.0.0 <4.0.0'
dependencies:
  crypto: ^3.0.0
index.dartDart
import 'dart:convert';
import 'package:crypto/crypto.dart';

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

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.

terminalbash
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.