Edge runtimes
The Node SDK's native client and app kit have a node-free /edge entry designed for Cloudflare Workers, Vercel Edge, Bun, and Deno. They import nothing from node:* and are built on web globals (fetch, ReadableStream, TextEncoder, btoa, crypto.subtle).
Static import-graph and Node-hosted WebCrypto tests cover that portable boundary. A committed smoke also runs under Bun and Deno, exercising WebCrypto plus native token mint and request handling. The Hono example also runs under Wrangler's local workerd engine and Vercel's Edge Runtime VM. B-009 remains open only for hosted deployment provenance and a protected live flow in authorized provider projects.
The one exception is the S3 Client, whose SigV4 signer uses node:crypto. It is Node-only by design and is not exported from the edge entry. On the edge you use the native data plane instead, which is the recommended surface anyway.
Use the /edge entry
Import from the dedicated edge entry, @kelphect/sdk/edge:
import {
LockwellKit,
NativeClient,
AdminClient,
verifyWebhook,
WEBHOOK_SIGNATURE_HEADER_NAME,
sha256ChecksumBase64,
} from "@kelphect/sdk/edge";The /edge subpath re-exports only the node:*-free modules (native.js, admin.js, kit.js, and helpers).
Import from /edge, not the default barrel. The default barrel (@kelphect/sdk) also re-exports the Node-only S3 Client, so a bundler that follows the barrel drags node:crypto into the edge bundle and forces a nodejs_compat flag or polyfill. Importing from /edge bundles to zero node-only code with no compatibility flag.
What is enforced The SDK's test/edge.test.js statically walks the /edge import graph and fails the build
if any node: specifier becomes reachable. It also runs a request with node:crypto poisoned at the module loader. This enforces the node-free import boundary. Bun, Deno, Wrangler/workerd, and Vercel Edge Runtime add real execution; hosted provider deployments remain a separate evidence gate. :::
A Cloudflare Workers / Hono example
This is the intended shape of the examples/hono-edge app. It has not yet been validated as a deployed Cloudflare Worker. Secrets should live only in edge env bindings (c.env) and never reach the browser; the browser should receive only a short-lived signed URL.
import { Hono } from "hono";
import { cors } from "hono/cors";
// Edge-safe imports only. The S3 Client is not even reachable through /edge.
import { LockwellKit, verifyWebhook, WEBHOOK_SIGNATURE_HEADER_NAME } from "@kelphect/sdk/edge";
const app = new Hono();
app.use("/api/*", cors());
// env bindings are per-request on Workers, so build the kit from c.env per call.
function kitFor(env) {
return new LockwellKit({
admin: { endpoint: env.LOCKWELL_ADMIN_ENDPOINT, token: env.LOCKWELL_ADMIN_TOKEN },
native: { endpoint: env.LOCKWELL_PUBLIC_ENDPOINT },
});
}
// Mint a browser-direct signed upload URL (no credential leaves the server).
app.post("/api/signed-upload", async (c) => {
const { tenantId, creds } = resolveTenant(c); // server-side; never from a forgeable path
const { key, contentType } = await c.req.json();
const kit = kitFor(c.env);
const native = kit.clientForTenant(tenantId, creds);
const up = await kit.signedUploadUrl(native, creds.bucket, key, {
ttlSeconds: 300,
contentType,
});
// Return ONLY the URL + how to use it.
return c.json({ url: up.url, method: up.method, headers: up.headers });
});
// Stream an upload straight through the Worker to the native PUT (no buffering).
app.put("/api/objects/:key{.+}", async (c) => {
const { tenantId, creds } = resolveTenant(c);
const native = kitFor(c.env).clientForTenant(tenantId, creds);
const res = await native.putObject(creds.bucket, c.req.param("key"), c.req.raw.body, {
contentType: c.req.header("content-type") || "application/octet-stream",
});
return c.json({ etag: res.etag, versionId: res.versionId });
});
// Receive + verify a webhook with the constant-time WebCrypto HMAC.
app.post("/api/webhooks/lockwell", async (c) => {
const secret = c.env.WEBHOOK_SHARED_SECRET;
if (!secret) return c.json({ error: "no verification secret configured" }, 501);
const sig = c.req.header(WEBHOOK_SIGNATURE_HEADER_NAME) || "";
const raw = new Uint8Array(await c.req.raw.clone().arrayBuffer()); // exact bytes
if (!(await verifyWebhook(raw, sig, secret))) return c.json({ error: "invalid signature" }, 401);
const event = await c.req.json();
return c.json({ verified: true, recordCount: (event.Records || []).length });
});
export default app; // Hono's `app` IS a { fetch(request, env, ctx) } Workers handlerc.req.raw.body is a web ReadableStream. The native client streams it straight to Lockwell with no whole-object buffering. The native bearer token is minted and refreshed by the SDK and never leaves the server.
Per-runtime integration sketches
The Cloudflare and Vercel deployment sections are configuration sketches, not records of hosted deployments. Their official local runtime engines pass alongside Bun and Deno; B-009 must clear before hosted deployments are claimed.
Cloudflare Workers. export default app. Set the two endpoint URLs as wrangler.toml [vars] and the admin token and shared secrets via wrangler secret put. The node-free entry is intended to avoid nodejs_compat; validate that in the target Worker project before relying on it.
Vercel Edge. Re-export app.fetch from an Edge Function or route handler with export const runtime = 'edge', passing process.env as the env arg: app.fetch(request, process.env).
Bun. Bun.serve({ port, fetch: (req) => app.fetch(req, process.env) }).
Deno. Deno.serve({ port }, (req) => app.fetch(req, Deno.env.toObject())). Import-map the @kelphect/sdk and hono specifiers in deno.json.
Edge-safe checksums
A native idempotent PUT needs a body-integrity checksum (see idempotency keys). On the edge, sha256ChecksumBase64 computes the SHA-256 with crypto.subtle, no node:crypto:
import { sha256ChecksumBase64 } from "@kelphect/sdk/edge";
const body = "invoice-payload";
await native.putObject("billing", "invoices/2026-001.json", body, {
idempotencyKey: "invoice-2026-001",
checksums: { sha256: await sha256ChecksumBase64(body) },
});CRC32C and CRC64NVME checksums can be precomputed and passed directly.
What runs where
| Surface | Edge-safe? | Notes |
|---|---|---|
NativeClient (data plane) | Node-free entry | Bun/Deno/workerd/Vercel-VM smokes pass; hosted deploys pending. |
LockwellKit (app kit) | Node-free entry | clientForTenant, signed URLs, verifyWebhook are node-free. |
AdminClient (control plane) | Node-free entry | Uses global fetch; typically a server-side/admin context. |
verifyWebhook | Node-free entry | WebCrypto HMAC + constant-time compare; hosted deploys pending. |
S3 Client (SigV4) | No | Node-only. Its signer uses node:crypto. Not exported from /edge. |
Next steps
- Data operations. The native API the edge client exposes.
- Signed URLs. The browser-direct upload pattern shown above.
- Webhooks. Receiving and verifying deliveries.