A TypeScript SDK for building Crossplane Composition Functions. This SDK provides type-safe interfaces and utilities for creating functions that generate and manage Crossplane resources.
This SDK can be used as the base of a Crossplane Composition function.
For complete usage instructions on importing this SDK into your projects, see USAGE.md.
npm install @crossplane-org/function-sdk-typescriptImplement the FunctionHandler interface:
import type {
FunctionHandler,
RunFunctionRequest,
RunFunctionResponse,
Logger
} from "@crossplane-org/function-sdk-typescript";
import {
to,
normal,
fatal,
setDesiredComposedResources,
getDesiredComposedResources,
getObservedCompositeResource,
Resource
} from "@crossplane-org/function-sdk-typescript";
export class MyFunction implements FunctionHandler {
async RunFunction(req: RunFunctionRequest, logger?: Logger): Promise<RunFunctionResponse> {
let rsp = to(req);
try {
// Get observed composite resource and desired composed resources
const oxr = getObservedCompositeResource(req);
let dcds = getDesiredComposedResources(req);
logger?.info("Processing function request");
// Your function logic here - create a ConfigMap
dcds["my-resource"] = Resource.fromJSON({
resource: {
apiVersion: "v1",
kind: "ConfigMap",
metadata: { name: "my-config" },
data: { key: "value" }
}
});
rsp = setDesiredComposedResources(rsp, dcds);
normal(rsp, "Function completed successfully");
return rsp;
} catch (error) {
logger?.error({ error }, "Function failed");
fatal(rsp, error instanceof Error ? error.message : String(error));
return rsp;
}
}
}See USAGE.md for complete examples and API documentation.
- Node.js 18+
- npm or yarn
- Docker (for building container images)
- protoc (Protocol Buffer compiler) -
brew install protobuf
npm installThe SDK uses Vitest for testing. Tests are located alongside source files with the .test.ts extension.
# Run tests once
npm test
# Run tests in watch mode (for development)
npm run test:watch
# Run tests with coverage report
npm run test:coverageTest files are automatically excluded from the build output.
Start the function server in development mode:
npm run build
npm run local-runThe function will listen on 0.0.0.0:9443 by default.
After running npm run local-run in another terminal, use the Crossplane CLI to
call your local function using crossplane render:
cd example
./render.shThe main function logic goes in src/function/function.ts:
import { Resource, RunFunctionRequest, RunFunctionResponse } from "../proto/run_function.js";
import { to, setDesiredComposedResources, normal } from "../response/response.js";
import { getDesiredComposedResources } from "../request/request.js";
export class FunctionRunner {
async RunFunction(
req: RunFunctionRequest,
logger?: Logger,
): Promise<RunFunctionResponse> {
// Initialize response from request
let rsp = to(req);
// Get desired composed resources from request
let dcds = getDesiredComposedResources(req);
// Create a new resource using plain JSON
dcds["my-deployment"] = Resource.fromJSON({
resource: {
apiVersion: "apps/v1",
kind: "Deployment",
metadata: {
name: "my-deployment",
namespace: "default",
},
spec: {
replicas: 3,
// ... deployment spec
},
},
});
// Set desired resources in response
rsp = setDesiredComposedResources(rsp, dcds);
// Add a result message
normal(rsp, "Resources created successfully");
return rsp;
}
}You can use type-safe Kubernetes models from the kubernetes-models package:
import { Deployment } from "kubernetes-models/apps/v1";
import { Pod } from "kubernetes-models/v1";
// Create a type-safe Pod
const pod = new Pod({
metadata: {
name: "my-pod",
namespace: "default",
},
spec: {
containers: [{
name: "app",
image: "nginx:latest",
}],
},
});
// Validate the pod
pod.validate();
// Convert to Resource
dcds["my-pod"] = Resource.fromJSON({
resource: pod.toJSON()
});The SDK provides comprehensive request helpers to extract data from RunFunctionRequest:
import {
getObservedCompositeResource,
getDesiredCompositeResource,
getDesiredComposedResources,
getObservedComposedResources,
getInput,
getContextKey,
getRequiredResources,
getCredentials,
} from "@crossplane-org/function-sdk-typescript";
// Get the observed composite resource (XR)
const oxr = getObservedCompositeResource(req);
// Get the desired composite resource
const dxr = getDesiredCompositeResource(req);
// Get desired composed resources
const dcds = getDesiredComposedResources(req);
// Get observed composed resources
const ocds = getObservedComposedResources(req);
// Get function input configuration
const input = getInput(req);
// Get context value from previous function
const [value, exists] = getContextKey(req, "my-key");
// Get required resources
const required = getRequiredResources(req);
// Get credentials
const creds = getCredentials(req, "aws-creds");The SDK provides response helpers to build and manipulate RunFunctionResponse:
import {
to,
setDesiredComposedResources,
setDesiredCompositeResource,
setDesiredCompositeStatus,
setContextKey,
setOutput,
normal,
fatal,
warning,
update,
DEFAULT_TTL,
} from "@crossplane-org/function-sdk-typescript";
// Initialize response from request (with optional TTL)
let rsp = to(req, DEFAULT_TTL);
// Set desired composed resources (merges with existing)
rsp = setDesiredComposedResources(rsp, dcds);
// Set desired composite resource
rsp = setDesiredCompositeResource(rsp, dxr);
// Update composite resource status
rsp = setDesiredCompositeStatus({ rsp, status: { ready: true } });
// Set context for next function
rsp = setContextKey(rsp, "my-key", "my-value");
// Set output (returned to user)
rsp = setOutput(rsp, { result: "success" });
// Add result messages
normal(rsp, "Success message");
warning(rsp, "Warning message");
fatal(rsp, "Fatal error message");
// Update a resource by merging
const updated = update(sourceResource, targetResource);The SDK provides utilities for working with Kubernetes resources:
import {
Resource,
asObject,
asStruct,
fromObject,
toObject,
newDesiredComposed,
} from "@crossplane-org/function-sdk-typescript";
// Create a Resource from a plain object
const resource = fromObject({
apiVersion: "v1",
kind: "ConfigMap",
metadata: { name: "my-config" }
});
// Extract plain object from Resource
const obj = toObject(resource);
// Convert between struct and object formats
const struct = asStruct(obj);
const plainObj = asObject(struct);
// Create a new empty DesiredComposed resource
const desired = newDesiredComposed();try {
// Your function logic
} catch (error) {
fatal(rsp, error instanceof Error ? error.message : String(error));
return rsp;
}This section is for SDK maintainers who want to publish updates to npm.
-
Update version in package.json
-
Build the TypeScript code:
npm run build
-
Verify the build output in
dist/:ls -la dist/
Before publishing, test the package locally:
# Create a tarball
npm pack
# This creates function-sdk-typescript-<version>.tgz
# Install it in another project to test
npm install /path/to/function-sdk-typescript-<version>.tgz# Dry run to see what will be published
npm publish --dry-run
# Publish to npm (requires authentication)
npm publishThe package.json files field ensures only necessary files are included:
dist/- Compiled JavaScript and type definitionsREADME.md- Documentation
If you're developing a function based on this SDK and need to containerize it:
- Create a
Dockerfilefor your function - Build the image with your function code
- Package as a Crossplane function package
See the Crossplane documentation for details on building and packaging functions.
This repo uses the Protobuf definitions from Crossplane. If the upstream definition is updated, copy it into this repo and regenerate the TypeScript code.
You need the Protocol Buffer compiler (protoc) installed:
# macOS
brew install protobuf
# Verify installation
protoc --version # Should be 3.x or higherTo regenerate TypeScript code from the Protobuf definitions:
./scripts/protoc-gen.shThis script uses ts-proto to generate:
- Type definitions from protobuf messages
- gRPC service stubs for the FunctionRunner service
- Conversion utilities for Protocol Buffer types
After regenerating, rebuild the project:
npm run buildFor SDK contributors making changes to the SDK itself:
- Make changes to source files in
src/ - Build the SDK:
npm run build - Test locally by creating a tarball:
npm pack - Install the tarball in a test project to verify changes
For testing with an example function:
- Implement an example function using the SDK
- Build:
npm run build - Run the function server locally (if you have a main entry point)
- Test with
crossplane beta renderusing example compositions
Create a Function resource in your cluster:
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
name: your-registry-function-typescript
spec:
package: your-registry/function-typescript:v0.1.0Reference it in your Composition:
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: my-composition
spec:
mode: Pipeline
pipeline:
- step: run-typescript-function
functionRef:
name: function-typescriptResources can be created from Typescript Objects using Resource.fromJSON():
// Plain JSON object automatically converted to Struct
Resource.fromJSON({
resource: {
apiVersion: "v1",
kind: "ConfigMap",
// ... any valid Kubernetes resource
}
});The SDK exports all types and interfaces from a single entry point:
import {
// Core interfaces
FunctionHandler,
FunctionRunner,
Logger,
// Request/Response types
RunFunctionRequest,
RunFunctionResponse,
Resource,
// Resource types
Composite,
ObservedComposed,
DesiredComposed,
ConnectionDetails,
// Protocol buffer types
Severity,
Result,
State,
Ready,
Target,
Status,
Condition,
Resources,
Credentials,
CredentialData,
// Runtime types
ServerOptions,
} from "@crossplane-org/function-sdk-typescript";to(req, ttl?)- Initialize a response from a requestnormal(rsp, message)- Add a normal (info) resultwarning(rsp, message)- Add a warning resultfatal(rsp, message)- Add a fatal error resultgetObservedCompositeResource(req)- Get the observed XRgetDesiredCompositeResource(req)- Get the desired XRgetDesiredComposedResources(req)- Get desired composed resourcesgetObservedComposedResources(req)- Get observed composed resourcessetDesiredComposedResources(rsp, resources)- Set desired composed resourcessetDesiredCompositeStatus({rsp, status})- Update XR statussetContextKey(rsp, key, value)- Set context for next functiongetContextKey(req, key)- Get context from previous functiongetInput(req)- Get function input configurationgetRequiredResources(req)- Get required resourcesgetCredentials(req, name)- Get credentials by name (throws error if not found)
See USAGE.md for detailed API documentation and examples.
@grpc/grpc-js- gRPC implementation for Node.js@grpc/proto-loader- Protocol buffer loadergoogle-protobuf- Google Protocol Buffers runtimets-proto- TypeScript protobuf code generatorts-deepmerge- Deep merging utility for resourcespino- Fast, structured JSON loggerkubernetes-models- Type-safe Kubernetes resource models (optional)
typescript- TypeScript compiler (v5.7+)@types/node- Node.js type definitions@types/google-protobuf- Google Protobuf type definitionsts-node- TypeScript execution enginevitest- Fast unit test framework@vitest/coverage-v8- Code coverage reporting- Protocol Buffer compiler (
protoc) - Required for regenerating protobuf code
Problem: Package not found or installation fails
# Verify npm registry access
npm ping
# Try installing with verbose output
npm install @crossplane-org/function-sdk-typescript --verboseProblem: TypeScript can't find types from the SDK
Solution: Ensure your tsconfig.json includes:
{
"compilerOptions": {
"moduleResolution": "node",
"esModuleInterop": true
}
}Problem: Cannot find module '@crossplane-org/function-sdk-typescript'
Solution: Verify the package is installed:
npm list @crossplane-org/function-sdk-typescriptIf missing, reinstall:
npm install @crossplane-org/function-sdk-typescriptProblem: EADDRINUSE error when starting function server
Solution: Kill the process using the port:
# Find and kill process on port 9443
lsof -ti:9443 | xargs kill -9Problem: Errors when running ./scripts/protoc-gen.sh
Solution: Ensure Protocol Buffer compiler is installed:
# Check version
protoc --version # Should be 3.x or higher
# macOS installation
brew install protobuf
# After installing, regenerate
./scripts/protoc-gen.sh
npm run buildContributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable (use
npm testto run tests) - Ensure all tests pass and the build succeeds (
npm run build) - Submit a pull request
Apache 2.0
- Crossplane Documentation
- Composition Functions
- Write a Composition Function
- Composition Function API
- USAGE.md - Complete usage guide for this SDK
- ts-proto - TypeScript protobuf code generator
- kubernetes-models - Type-safe Kubernetes resource models for TypeScript
- Pino - Fast JSON logger
- gRPC-js - Pure JavaScript gRPC implementation