Skip to content

Commit c4791ae

Browse files
committed
feat: add in-function asset uploads
1 parent 5df02ab commit c4791ae

18 files changed

Lines changed: 439 additions & 325 deletions

File tree

AGENTS.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This monorepo ships **five interfaces**:
1313
- **SDK** (`sdk/`) — typed TypeScript client for the run402 API. Used by external integrators, MCP/CLI/OpenClaw, and (eventually) inside deployed functions. Published as `@run402/sdk` on npm. Two entry points: root (isomorphic — works in Node 22, Deno, Bun, V8 isolates) and `/node` (zero-config Node defaults — keystore + allowance + x402).
1414
- **MCP server** (root `src/`) — published as `run402-mcp` on npm. Each tool is a thin shim over an SDK call. Read by Claude Desktop / Cursor / Cline / Claude Code.
1515
- **CLI** (`cli/`) — standalone CLI published as `run402` on npm. Each subcommand is a thin shim over an SDK call; argv parsing and JSON output stay at the CLI edge.
16-
- **Functions library** (`functions/`) — in-function helper imported _inside_ deployed serverless functions. Exposes `db(req)`, `adminDb()`, `getUser()`, `email`, `ai`. Published as `@run402/functions` on npm. Distinct from the SDK: this is the request-scoped, in-function shape; the SDK is the typed external client. The two are complementary, not redundant.
16+
- **Functions library** (`functions/`) — in-function helper imported _inside_ deployed serverless functions. Exposes `db(req)`, `adminDb()`, `getUser()`, `email`, `ai`, and `assets`. Published as `@run402/functions` on npm. Distinct from the SDK: this is the request-scoped, in-function shape; the SDK is the typed external client. The two are complementary, not redundant.
1717
- **OpenClaw skill** (`openclaw/`) — script-based skill for OpenClaw agents, re-exports from CLI modules.
1818

1919
Workspace layout: `package.json` declares `cli`, `sdk`, and `functions` as npm workspaces, and `pnpm-workspace.yaml` mirrors that set for pnpm-based hosts. `core/` is shared internal code, not an npm package.
@@ -80,7 +80,7 @@ When adding a new tool/command, add it to the `SURFACE` array **and** `SDK_BY_CA
8080
core/ ← Node-only primitives (keystore, allowance, SIWE signing, config paths)
8181
Imported by sdk/src/node via ../../../core/dist/; not an npm package.
8282
83-
functions/ ← @run402/functions in-function helper (db, adminDb, getUser, email, ai).
83+
functions/ ← @run402/functions in-function helper (db, adminDb, getUser, email, ai, assets).
8484
Auto-bundled into deployed function zips at deploy time;
8585
also installable for local TypeScript autocomplete.
8686
```
@@ -146,11 +146,12 @@ Core functions return `null` or throw — they never call `process.exit()`. Each
146146

147147
### Functions library (`functions/`)
148148

149-
- **`functions/src/index.ts`** — Public exports: `db`, `adminDb`, `getUser`, `email`, `ai`, `routedHttp`, and routed HTTP envelope types/helpers (`text`, `json`, `bytes`, `isRequest`). Each helper makes raw `fetch()` calls against the project's own gateway endpoints using ambient request context (the function's `RUN402_PROJECT_ID` / `RUN402_SERVICE_KEY` env vars baked at deploy time), except routed HTTP helpers which encode/decode the public browser ingress envelope.
149+
- **`functions/src/index.ts`** — Public exports: `db`, `adminDb`, `getUser`, `email`, `ai`, `assets`, `routedHttp`, and routed HTTP envelope types/helpers (`text`, `json`, `bytes`, `isRequest`). Each helper makes raw `fetch()` calls against the project's own gateway endpoints using ambient request context (the function's `RUN402_PROJECT_ID` / `RUN402_SERVICE_KEY` env vars baked at deploy time), except routed HTTP helpers which encode/decode the public browser ingress envelope.
150150
- **`db(req)`** — caller-context PostgREST client. Forwards the incoming `Authorization` header; RLS evaluates against the caller's role.
151151
- **`adminDb()`** — service-key client. Routes to `/admin/v1/rest/*` (the gateway rejects `role=service_role` on `/rest/v1/*`, so bypass traffic lives on its own surface). Use only when the function acts on behalf of the platform.
152152
- **`adminDb().sql(query, params?)`** — raw parameterized SQL, always BYPASSRLS.
153153
- **`ai.generateImage({ prompt, aspect? })`** — project-billed runtime image generation from deployed functions. Uses the project service key and `/ai/v1/generate-image`, not the wallet/x402 `/generate-image/v1` endpoint. Supported aspects are `square`, `landscape`, and `portrait`; result shape is `{ image, content_type, aspect }`. Gateway rate limits and spend caps apply to the project billing account; routed public functions still own app auth and abuse limits.
154+
- **`assets.put(key, source, opts?)`** — in-function asset upload through the service-key `/apply/v1/service-asset-put` path. Uses the same CAS/activation substrate as deploy-time assets and returns SDK-compatible `AssetRef` snake_case + camelCase fields.
154155
- **`routedHttp`** — non-framework helpers for the `run402.routed_http.v1` same-origin browser ingress contract. Direct `/functions/v1/:name` remains API-key protected; routed function code owns app auth, CSRF, CORS/`OPTIONS`, cookies, redirects, cache headers, and spoofed forwarding-header hygiene.
155156
- This library is auto-bundled into deployed function zips alongside any user-declared `--deps` (npm-installed and esbuild-bundled at deploy time, native binaries rejected). Also installable in your editor for full TypeScript autocomplete.
156157

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ This monorepo ships every surface an agent can pick up:
2222
| [`run402` CLI](./cli/) | Terminal, scripts, CI, agent-controlled shells — JSON in, JSON out, exit code on failure |
2323
| [`run402-mcp`](./src/) | Claude Desktop, Cursor, Cline, Claude Code — core Run402 operations as MCP tools |
2424
| [OpenClaw skill](./openclaw/) | OpenClaw agents (no MCP server required) |
25-
| [`@run402/functions`](./functions/) | Imported _inside_ deployed functions (`db(req)`, `adminDb()`, `getUser()`, `email`, `ai`) and for TypeScript autocomplete in your editor |
25+
| [`@run402/functions`](./functions/) | Imported _inside_ deployed functions (`db(req)`, `adminDb()`, `getUser()`, `email`, `ai`, `assets`) and for TypeScript autocomplete in your editor |
2626

2727
All five interfaces release in lockstep at the same version and share a single typed kernel where appropriate: `@run402/sdk`. MCP tools, CLI subcommands, and OpenClaw scripts are thin shims over SDK calls; `@run402/functions` is the in-function helper that runs inside deployed code. Pick whichever interface fits your runtime.
2828

@@ -265,6 +265,8 @@ export default async (req: Request) => {
265265

266266
`ai.generateImage({ prompt, aspect? })` is available inside deployed functions for live app flows such as generated avatars or OG images. It calls the project runtime image endpoint with `RUN402_SERVICE_KEY`, so deployed functions do not need allowance wallets or x402 signing code. Aspects are `square`, `landscape`, and `portrait`; the result is `{ image, content_type, aspect }` with base64 image bytes. Runtime image generation is billed, rate-limited, and spend-capped against the project billing account; public routed functions should authenticate/rate-limit their users before calling it.
267267

268+
`assets.put(key, source, opts?)` uploads bytes from inside a deployed function through the same CAS-backed apply substrate as deploy-time assets. It uses `RUN402_SERVICE_KEY`, accepts a string, `Uint8Array`, or `{ content | bytes }`, and returns an SDK-compatible `AssetRef` with mutable and immutable URLs.
269+
268270
**Calling from outside a function entirely** (raw `curl`/`fetch` from CI scripts, bash bootstrappers, non-TS runtimes) — service-key writes go to `/admin/v1/rest/<table>`, not `/rest/v1/*`. The gateway 403s service-role tokens on `/rest/v1/*` so a leaked key can't silently bypass RLS, which means `curl ... > /dev/null` against the wrong path looks like success but writes nothing. SQL-shaped admin work uses `POST /projects/v1/admin/:id/sql` (or `run402 projects sql`).
269271

270272
```bash

SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ Known route warning recovery: `PUBLIC_ROUTED_FUNCTION` means review app auth, CS
235235
Inside a deployed function, import from `@run402/functions`. Two distinct DB clients keep RLS clean:
236236

237237
```ts
238-
import { db, adminDb, getUser, email, ai } from "@run402/functions";
238+
import { db, adminDb, getUser, email, ai, assets } from "@run402/functions";
239239

240240
export default async (req: Request) => {
241241
const user = await getUser(req);
@@ -259,6 +259,7 @@ export default async (req: Request) => {
259259
- **`adminDb()`** — bypasses RLS. Use only for audit logs, cron cleanup, webhook handlers, platform-authored writes.
260260
- **`adminDb().sql(query, params?)`** — raw parameterized SQL, always bypasses RLS.
261261
- **`ai.generateImage({ prompt, aspect? })`** — live image generation from deployed functions, billed/rate-limited against the project billing account through `RUN402_SERVICE_KEY`. Aspects: `square`, `landscape`, `portrait`; result: `{ image, content_type, aspect }`. For public routed functions, authenticate/rate-limit app users before calling it.
262+
- **`assets.put(key, source, opts?)`** — upload runtime bytes through the same CAS-backed apply substrate as deploy-time assets. `source` is a string, `Uint8Array`, or `{ content | bytes }`; returns an SDK-compatible `AssetRef`.
262263

263264
Fluent surface on both `db(req).from(t)` and `adminDb().from(t)`:
264265
- Reads: `.select()`, `.eq()`, `.neq()`, `.gt()`, `.lt()`, `.gte()`, `.lte()`, `.like()`, `.ilike()`, `.in()`, `.order()`, `.limit()`, `.offset()`

0 commit comments

Comments
 (0)