@oxy-hq/sdk 2.12.0 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -0
- package/dist/{function-context-D8eyZuw_.d.cts → function-context-BNpL5bFb.d.cts} +223 -20
- package/dist/function-context-BNpL5bFb.d.cts.map +1 -0
- package/dist/{function-context-D8eyZuw_.d.mts → function-context-BNpL5bFb.d.mts} +223 -20
- package/dist/function-context-BNpL5bFb.d.mts.map +1 -0
- package/dist/index.cjs +80 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +164 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +164 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +78 -16
- package/dist/index.mjs.map +1 -1
- package/dist/ops.d.cts +1 -1
- package/dist/ops.d.mts +1 -1
- package/dist/{react-DW7Z96sD.d.mts → react-CljeXJuw.d.cts} +142 -8
- package/dist/react-CljeXJuw.d.cts.map +1 -0
- package/dist/{react-DW7Z96sD.d.cts → react-CljeXJuw.d.mts} +142 -8
- package/dist/react-CljeXJuw.d.mts.map +1 -0
- package/dist/{react-DcT-mUPj.cjs → react-Dvkv2deI.cjs} +110 -59
- package/dist/react-Dvkv2deI.cjs.map +1 -0
- package/dist/{react-BXGyzgz0.mjs → react-OW1t_J0M.mjs} +103 -26
- package/dist/react-OW1t_J0M.mjs.map +1 -0
- package/dist/rolldown-runtime-KC0qvQup.cjs +34 -0
- package/dist/shell.cjs +38 -3
- package/dist/shell.cjs.map +1 -1
- package/dist/shell.d.cts +26 -3
- package/dist/shell.d.cts.map +1 -1
- package/dist/shell.d.mts +26 -3
- package/dist/shell.d.mts.map +1 -1
- package/dist/shell.mjs +34 -2
- package/dist/shell.mjs.map +1 -1
- package/dist/testing.cjs +4431 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +656 -0
- package/dist/testing.d.cts.map +1 -0
- package/dist/testing.d.mts +656 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +4395 -0
- package/dist/testing.mjs.map +1 -0
- package/package.json +22 -9
- package/dist/function-context-D8eyZuw_.d.cts.map +0 -1
- package/dist/function-context-D8eyZuw_.d.mts.map +0 -1
- package/dist/react-BXGyzgz0.mjs.map +0 -1
- package/dist/react-DW7Z96sD.d.cts.map +0 -1
- package/dist/react-DW7Z96sD.d.mts.map +0 -1
- package/dist/react-DcT-mUPj.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -282,6 +282,73 @@ await ctx.email.send({ to: ctx.user.email, subject: `Hi ${ctx.user.name}`, html
|
|
|
282
282
|
Use it for an avatar or a greeting. Hiding a tab with it is fine; the endpoint
|
|
283
283
|
behind that tab is what actually has to say no.
|
|
284
284
|
|
|
285
|
+
### Testing a function (`@oxy-hq/sdk/testing`)
|
|
286
|
+
|
|
287
|
+
A typed test context for an Oxy Function's unit tests, so a test meets the
|
|
288
|
+
host's real gates instead of a hand-rolled fake that is more permissive than
|
|
289
|
+
production:
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
import { createTestContext, type HostOp } from "@oxy-hq/sdk/testing";
|
|
293
|
+
import manifest from "../oxy-app.json";
|
|
294
|
+
import handler from "./notify";
|
|
295
|
+
|
|
296
|
+
const t = createTestContext(manifest, {
|
|
297
|
+
function: "notify",
|
|
298
|
+
databases: { warehouse: { dialect: "clickhouse", kind: "customer" } }
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
await t.run(() => handler({ method: "POST", headers: {}, body: "{}" }, t.ctx));
|
|
302
|
+
|
|
303
|
+
t.ops(); // HostOp[] — first-call order, de-duplicated
|
|
304
|
+
t.calls; // every host op, in order: { op, args, outcome, message?, result? }
|
|
305
|
+
t.callsTo("warehouse.insert"); // one op's calls
|
|
306
|
+
t.state.warehouse("warehouse").rows("events");
|
|
307
|
+
t.override("warehouse.upsert", async () => ({})); // replace one host op, typed by name
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
What it enforces, in the host's own words (every refusal is quoted from the
|
|
311
|
+
host's source and held there by a Rust drift test): the manifest's capability
|
|
312
|
+
gates (`secrets.write`, `email.send`, `org.read`, `storage.read` /
|
|
313
|
+
`storage.write`, `oltp`, `airhouse`); the `destinations` allowlist and the
|
|
314
|
+
customer-warehouse rule for `ctx.warehouse` writes and `ctx.tx`; `ctx.tx` on a
|
|
315
|
+
non-Postgres database; `warehouse.upsert` where `ON CONFLICT` does not parse
|
|
316
|
+
(on Airhouse it refuses in its own words, because there the *engine* refuses);
|
|
317
|
+
`ctx.fetch`'s SSRF allowlist and byte cap. A refusal is an `Error` named
|
|
318
|
+
`HostError` whose message is `"<surface>: <host message>"`, as the runtime
|
|
319
|
+
throws it.
|
|
320
|
+
|
|
321
|
+
Rows come from a byte copy of the platform's shape zoo, so a ClickHouse
|
|
322
|
+
`UInt64` above `i64::MAX` arrives as a string in a test as it does in
|
|
323
|
+
production: `t.state.warehouse(db).zoo()` creates the zoo table (column `cNNN`
|
|
324
|
+
is case NNN), `.table(name, { id: "UInt64" }).insert(rows)` renders SQL-side
|
|
325
|
+
values through the case for each type, and `.raw(name, rows)` takes JS values
|
|
326
|
+
as-is — every read of those is marked `source: "author"` on its `HostCall`.
|
|
327
|
+
`ctx.oltp` and `ctx.airhouse` have the same stores; `ctx.fetch` answers from
|
|
328
|
+
`t.state.fetch.on(url, { status, body })`; `ctx.org.*` from
|
|
329
|
+
`t.state.org.people` / `.places` / `.assignments`.
|
|
330
|
+
|
|
331
|
+
`t.run(fn)` evaluates `fn` with the isolate's absent globals removed —
|
|
332
|
+
`Buffer`, `TextEncoder`, `TextDecoder`, `Blob`, `File`, `FormData`, `crypto`,
|
|
333
|
+
`process` — and `btoa` / `atob` replaced by the runtime's own, so a handler
|
|
334
|
+
that reaches for one throws the `ReferenceError` it throws in production. It
|
|
335
|
+
is per call; a vitest environment that removes them for a whole file may
|
|
336
|
+
follow later.
|
|
337
|
+
|
|
338
|
+
What it is not: a server, a real engine, or a replacement for `oxyc checks
|
|
339
|
+
run`. The context's SQL is a small subset (`CREATE TABLE`, `INSERT … VALUES`,
|
|
340
|
+
`SELECT … FROM … WHERE … = …`, `DELETE`, `UPDATE`), and it says so — an
|
|
341
|
+
unrecognised statement throws a `TestContextError` naming `t.override` and
|
|
342
|
+
`t.state.<store>.raw` as the ways out, rather than answering something the host
|
|
343
|
+
would not. `UPDATE … SET` follows the same rule as a projection: an assignment
|
|
344
|
+
the subset cannot read as a value (`SET n = n + 1`, `SET at = now()`) is
|
|
345
|
+
refused rather than stored as its own SQL text. Nothing here reaches the
|
|
346
|
+
network or the filesystem.
|
|
347
|
+
|
|
348
|
+
It also needs Node: `@oxy-hq/sdk/testing` imports `node:crypto` at module load
|
|
349
|
+
(the context's `ctx.crypto` is the real one), so it runs under vitest's `node`
|
|
350
|
+
environment and not under browser mode. The rest of the SDK does not.
|
|
351
|
+
|
|
285
352
|
## Docs
|
|
286
353
|
|
|
287
354
|
- Hands-on dev + deploy guide: `docs/local-development.md` in the
|
|
@@ -3,12 +3,27 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* The request passed as the first argument to a function's default export.
|
|
5
5
|
*
|
|
6
|
-
* The host
|
|
7
|
-
* `
|
|
8
|
-
* `JSON.parse(req.body || "{}")
|
|
9
|
-
*
|
|
6
|
+
* The host builds this as `{ method, headers, body }` (`req_json` in
|
|
7
|
+
* `runtime.rs`). It is intentionally *not* a full Web `Request`: the body
|
|
8
|
+
* arrives as a string, so parse it yourself — `JSON.parse(req.body || "{}")`
|
|
9
|
+
* — and there is no `.json()`, no `url`, and no `Headers` object.
|
|
10
10
|
*/
|
|
11
11
|
interface OxyFunctionRequest {
|
|
12
|
+
/**
|
|
13
|
+
* HTTP method of the triggering request, upper-case (`"POST"`, `"GET"`).
|
|
14
|
+
* A scheduled or Airway run has no real request and reports `"POST"`.
|
|
15
|
+
*/
|
|
16
|
+
method: string;
|
|
17
|
+
/**
|
|
18
|
+
* The headers the function is allowed to see, keyed by **lower-case** name.
|
|
19
|
+
*
|
|
20
|
+
* The host drops everything that is not on its passthrough list and not
|
|
21
|
+
* `x-*`, plus its own `x-oxy-*` prefix and a blocked list; a header sent
|
|
22
|
+
* twice collapses to its **first** value, and one whose bytes are not UTF-8
|
|
23
|
+
* is dropped rather than lossily decoded. A scheduled or Airway run has no
|
|
24
|
+
* headers, so this is `{}` — never absent.
|
|
25
|
+
*/
|
|
26
|
+
headers: Record<string, string>;
|
|
12
27
|
/** Raw request body as received (JSON string for a JSON POST). */
|
|
13
28
|
body: string;
|
|
14
29
|
}
|
|
@@ -191,6 +206,12 @@ type OxyFetchInit = RequestInit & {
|
|
|
191
206
|
* `query` does not, because that allowlist is about modifying a project's
|
|
192
207
|
* warehouse, and a `postgres_managed` database resolves the read-only analyst
|
|
193
208
|
* for every caller regardless.
|
|
209
|
+
*
|
|
210
|
+
* **Shape: the customer's warehouse — read it, don't write it.** Writes to a
|
|
211
|
+
* customer warehouse (anything but `airhouse` / `airhouse_managed`) are refused
|
|
212
|
+
* unless the function names the database in `customerWarehouseWrites` with a
|
|
213
|
+
* reason. Facts your app records go to `ctx.airhouse`, records it edits to
|
|
214
|
+
* `ctx.oltp`, files to `ctx.storage`.
|
|
194
215
|
*/
|
|
195
216
|
interface OxyWarehouseApi {
|
|
196
217
|
/**
|
|
@@ -206,11 +227,21 @@ interface OxyWarehouseApi {
|
|
|
206
227
|
}>;
|
|
207
228
|
insert(database: string, table: string, rows: OxyFunctionRow[]): Promise<unknown>;
|
|
208
229
|
exec(database: string, sql: string): Promise<unknown>;
|
|
230
|
+
/**
|
|
231
|
+
* Insert rows, updating any whose `conflictColumns` already exist.
|
|
232
|
+
*
|
|
233
|
+
* Compiles to `INSERT … ON CONFLICT … DO UPDATE`, which only Postgres and
|
|
234
|
+
* DuckDB parse, and which needs a primary key or unique constraint on
|
|
235
|
+
* `conflictColumns`. On any other warehouse — ClickHouse, Snowflake, BigQuery,
|
|
236
|
+
* MySQL — the call is refused by name before anything is sent; use `exec` with
|
|
237
|
+
* that warehouse's own upsert statement instead. Airhouse (DuckLake) tables
|
|
238
|
+
* carry no such constraint, so there the engine refuses it.
|
|
239
|
+
*/
|
|
209
240
|
upsert(database: string, table: string, rows: OxyFunctionRow[], conflictColumns: string[]): Promise<unknown>;
|
|
210
241
|
}
|
|
211
242
|
/**
|
|
212
|
-
* The handle `ctx.tx`
|
|
213
|
-
* open transaction.
|
|
243
|
+
* The handle `ctx.tx` and `ctx.oltp.tx` pass to your callback — a pinned
|
|
244
|
+
* connection with an open transaction.
|
|
214
245
|
*
|
|
215
246
|
* Both methods take **bound parameters** (`$1`, `$2`, …). Never build SQL by
|
|
216
247
|
* concatenating request data: `ctx.warehouse.exec` takes a bare string, but a
|
|
@@ -227,6 +258,11 @@ interface OxyTransaction {
|
|
|
227
258
|
exec(sql: string, params?: unknown[]): Promise<number>;
|
|
228
259
|
}
|
|
229
260
|
/**
|
|
261
|
+
* **Shape: records your app edits** — current state that needs constraints or
|
|
262
|
+
* transactions: a booking, a shift assignment, a template, a count. What
|
|
263
|
+
* happened (history that will not change) goes to `ctx.airhouse`; bytes go to
|
|
264
|
+
* `ctx.storage`.
|
|
265
|
+
*
|
|
230
266
|
* `ctx.oltp` — read and WRITE the app's OWN per-org OLTP schema (`app_<writer>`)
|
|
231
267
|
* on the managed Postgres tenant, and nothing else.
|
|
232
268
|
*
|
|
@@ -253,8 +289,9 @@ interface OxyTransaction {
|
|
|
253
289
|
* handshake, and a wake-up if the compute was idle) and its own transaction, so
|
|
254
290
|
* a per-row loop pays that per row. Prefer one statement over many — a
|
|
255
291
|
* multi-row `INSERT`, an `INSERT … SELECT`, or `INSERT … RETURNING` to avoid a
|
|
256
|
-
* follow-up read —
|
|
257
|
-
* in a hot
|
|
292
|
+
* follow-up read — or run several inside one `ctx.oltp.tx`, which holds a single
|
|
293
|
+
* connection. Reach for `ctx.oltp` a handful of times per request, not in a hot
|
|
294
|
+
* loop.
|
|
258
295
|
*
|
|
259
296
|
* ```ts
|
|
260
297
|
* const [row] = await ctx.oltp.query(
|
|
@@ -268,11 +305,147 @@ interface OxyOltpApi {
|
|
|
268
305
|
query(sql: string, params?: unknown[]): Promise<OxyFunctionRow[]>;
|
|
269
306
|
/** Run a statement for its effect; resolves to the number of rows affected. */
|
|
270
307
|
exec(sql: string, params?: unknown[]): Promise<number>;
|
|
308
|
+
/**
|
|
309
|
+
* Run several statements as one transaction on one connection: commits when
|
|
310
|
+
* `fn` resolves, rolls back when it throws, and rethrows your error. The same
|
|
311
|
+
* handle and rules as `ctx.tx` — let a failed statement's error propagate —
|
|
312
|
+
* without naming a database: the app's own store is implicit.
|
|
313
|
+
*
|
|
314
|
+
* ```ts
|
|
315
|
+
* await ctx.oltp.tx(async (tx) => {
|
|
316
|
+
* await tx.exec("UPDATE shifts SET status = 'closed' WHERE id = $1", [shiftId]);
|
|
317
|
+
* await tx.exec("INSERT INTO shift_notes (shift_id, body) VALUES ($1, $2)", [shiftId, note]);
|
|
318
|
+
* });
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
tx<T>(fn: (tx: OxyTransaction) => Promise<T> | T): Promise<T>;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* **Shape: facts — what happened, which will not change**: an order, a completed
|
|
325
|
+
* checklist, a delivery, a reading. `ctx.airhouse` appends them to your app's own
|
|
326
|
+
* schema in the workspace's Airhouse, where the analytics agent, semantic views
|
|
327
|
+
* and other apps read them as history. Records your app edits in place belong in
|
|
328
|
+
* `ctx.oltp`; files in `ctx.storage`.
|
|
329
|
+
*
|
|
330
|
+
* Gated by the fail-closed `airhouse` manifest capability (`"airhouse": {
|
|
331
|
+
* "enabled": true }`), a pure gate: the schema is `app_<writer>`, derived from
|
|
332
|
+
* the app's slug (`store-ops` → `app_store_ops`) and exposed as `schema`. Writes
|
|
333
|
+
* run **as the app**, whoever invoked the function — a schedule, a webhook and a
|
|
334
|
+
* click write the same way.
|
|
335
|
+
*
|
|
336
|
+
* Every statement is checked before it is sent. Reads may name any schema;
|
|
337
|
+
* writes must target `<schema>.<table>`; `exec` runs no DDL — declare tables in
|
|
338
|
+
* `airhouseMigrations` files, which run once at publish. One statement per call.
|
|
339
|
+
*
|
|
340
|
+
* Airhouse is DuckLake: **no primary keys, UNIQUE, indexes or foreign keys**, and
|
|
341
|
+
* no bound parameters. Give every fact the id its source assigned and a
|
|
342
|
+
* `recorded_at`, append a correction as a new fact instead of updating, and keep
|
|
343
|
+
* one row per id when reading — a retried function can append twice:
|
|
344
|
+
*
|
|
345
|
+
* ```ts
|
|
346
|
+
* await ctx.airhouse.append("checklist_completions", [
|
|
347
|
+
* { completion_id: id, store_guid: store, recorded_at: new Date().toISOString() },
|
|
348
|
+
* ]);
|
|
349
|
+
*
|
|
350
|
+
* const { rows } = await ctx.airhouse.query(`
|
|
351
|
+
* SELECT * FROM ${ctx.airhouse.schema}.checklist_completions
|
|
352
|
+
* QUALIFY row_number() OVER (PARTITION BY completion_id ORDER BY recorded_at DESC) = 1
|
|
353
|
+
* `);
|
|
354
|
+
* ```
|
|
355
|
+
*/
|
|
356
|
+
interface OxyAirhouseApi {
|
|
357
|
+
/** `app_<writer>` when the function declares the capability, else `null`. */
|
|
358
|
+
readonly schema: string | null;
|
|
359
|
+
/** Read, from any schema. Capped like `ctx.warehouse.query`; `truncated` says the cap cut rows. */
|
|
360
|
+
query(sql: string): Promise<{
|
|
361
|
+
rows: OxyFunctionRow[];
|
|
362
|
+
truncated: boolean;
|
|
363
|
+
}>;
|
|
364
|
+
/**
|
|
365
|
+
* Append rows to `<schema>.<table>`. Pass the bare table name; every row must
|
|
366
|
+
* carry the first row's columns. Values are sent as literals, safely quoted.
|
|
367
|
+
* Resolves to the number of rows sent.
|
|
368
|
+
*/
|
|
369
|
+
append(table: string, rows: OxyFunctionRow[]): Promise<number>;
|
|
370
|
+
/**
|
|
371
|
+
* One write statement against your own schema: a `DELETE` for retention or
|
|
372
|
+
* erasure, an `INSERT … SELECT` deriving facts from other facts. Nothing is
|
|
373
|
+
* bound, so never interpolate request data here — that is what `append` is for.
|
|
374
|
+
*/
|
|
375
|
+
exec(sql: string): Promise<void>;
|
|
271
376
|
}
|
|
272
|
-
/**
|
|
377
|
+
/**
|
|
378
|
+
* `ctx.secrets` — write app-scoped secrets (gated by the `secrets.write` capability).
|
|
379
|
+
*
|
|
380
|
+
* **Credentials only — not state.** A secret is for a value you authenticate
|
|
381
|
+
* with, like a rotated token. A cursor, a counter or a JSON blob that changes
|
|
382
|
+
* between runs is a record: keep it in `ctx.oltp`.
|
|
383
|
+
*/
|
|
273
384
|
interface OxySecretsApi {
|
|
274
385
|
set(key: string, value: string): Promise<void>;
|
|
275
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* `ctx.crypto` — HMAC signing and verification, and a constant-time compare.
|
|
389
|
+
* **Synchronous**: pure CPU inside the isolate, so these skip the host-call
|
|
390
|
+
* channel and there is nothing to `await`. Mirrors `crypto` in `__buildCtx`
|
|
391
|
+
* (`runtime.rs`); the three members here are the three the host binds.
|
|
392
|
+
*
|
|
393
|
+
* `key` and `data` are read as UTF-8 — every webhook scheme in the wild signs a
|
|
394
|
+
* UTF-8 base string with a UTF-8 secret (GitHub the body, Slack `v0:ts:body`,
|
|
395
|
+
* Stripe `ts.body`) — so there is deliberately no per-argument encoding knob.
|
|
396
|
+
*
|
|
397
|
+
* Who controls an input decides what its absence does. An unset or empty `key`,
|
|
398
|
+
* or an unknown `algorithm` / `encoding`, is the author's mistake and **throws**.
|
|
399
|
+
* A `signature` that is absent or will not decode is the caller's, and returns
|
|
400
|
+
* **`false`** — throwing would turn a forged request into a 500. Both sides of
|
|
401
|
+
* `timingSafeEqual` are symmetric, so an absent or empty side is `false` and
|
|
402
|
+
* never a throw: with the secret unset, every request is rejected.
|
|
403
|
+
*/
|
|
404
|
+
interface OxyCryptoApi {
|
|
405
|
+
/**
|
|
406
|
+
* The digest of `data` under `key`, as a string in `encoding`. For signing an
|
|
407
|
+
* **outbound** request; `verifyHmac` is the inverse direction.
|
|
408
|
+
*/
|
|
409
|
+
hmac(input: OxyHmacInput): string;
|
|
410
|
+
/**
|
|
411
|
+
* Whether `signature` is the digest of `data` under `key`, compared in
|
|
412
|
+
* constant time. Strip the provider's prefix first (`sha256=`, `v0=`) and
|
|
413
|
+
* pass the bare digest — prefix formats are per-provider. A header the caller
|
|
414
|
+
* omitted can be passed as-is: absent is `false`, not a throw.
|
|
415
|
+
*/
|
|
416
|
+
verifyHmac(input: OxyVerifyHmacInput): boolean;
|
|
417
|
+
/**
|
|
418
|
+
* Constant-time equality for a plain shared secret where there is no HMAC.
|
|
419
|
+
* Use it, not `===`, for any secret comparison: `===` short-circuits at the
|
|
420
|
+
* first differing byte and leaks the secret one byte at a time to anyone who
|
|
421
|
+
* can time the endpoint. `false` when either side is absent or empty.
|
|
422
|
+
*/
|
|
423
|
+
timingSafeEqual(a: string | null | undefined, b: string | null | undefined): boolean;
|
|
424
|
+
}
|
|
425
|
+
/** The inputs `ctx.crypto.hmac` and `ctx.crypto.verifyHmac` share. */
|
|
426
|
+
interface OxyHmacInput {
|
|
427
|
+
/** `"sha256"` (default) or `"sha512"`. Anything else throws. */
|
|
428
|
+
algorithm?: "sha256" | "sha512";
|
|
429
|
+
/**
|
|
430
|
+
* The secret, from configuration (`ctx.env.…`), never from the request.
|
|
431
|
+
* Required and non-empty: an absent one throws rather than signing with `""`
|
|
432
|
+
* or the literal `"undefined"` — keys an attacker guesses as easily as you do.
|
|
433
|
+
*/
|
|
434
|
+
key: string;
|
|
435
|
+
/** The base string to sign — for a webhook, the body or `v0:{ts}:{body}`. */
|
|
436
|
+
data: string;
|
|
437
|
+
/** How the digest is written (`hmac`) or read (`verifyHmac`): `"hex"` (default) or `"base64"`. */
|
|
438
|
+
encoding?: "hex" | "base64";
|
|
439
|
+
}
|
|
440
|
+
/** `ctx.crypto.verifyHmac`'s input: {@link OxyHmacInput} plus the signature to check. */
|
|
441
|
+
interface OxyVerifyHmacInput extends OxyHmacInput {
|
|
442
|
+
/**
|
|
443
|
+
* The bare digest the caller sent, provider prefix (`sha256=`, `v0=`) already
|
|
444
|
+
* stripped. Attacker-controlled, so a header the caller omitted may be passed
|
|
445
|
+
* as-is: absent, or not decodable in `encoding`, is `false`, never a throw.
|
|
446
|
+
*/
|
|
447
|
+
signature: string | null | undefined;
|
|
448
|
+
}
|
|
276
449
|
/** `ctx.semantic` — airlayer-compiled semantic queries (inherits the pre-agg fast path). */
|
|
277
450
|
interface OxySemanticApi {
|
|
278
451
|
/**
|
|
@@ -544,7 +717,10 @@ interface OxyStorageApi {
|
|
|
544
717
|
delete(keyOrKeys: string | string[]): Promise<{
|
|
545
718
|
deleted: number;
|
|
546
719
|
}>;
|
|
547
|
-
/**
|
|
720
|
+
/**
|
|
721
|
+
* Server-side copy within the app's silo (requires `storage.read` **and**
|
|
722
|
+
* `storage.write`: it reads the source and writes the destination).
|
|
723
|
+
*/
|
|
548
724
|
copy(fromKey: string, toPathname: string, opts?: {
|
|
549
725
|
allowOverwrite?: boolean;
|
|
550
726
|
}): Promise<StoragePutResult>;
|
|
@@ -589,7 +765,8 @@ interface OxyOrgAssignment {
|
|
|
589
765
|
/**
|
|
590
766
|
* The data-plane context passed as the second argument to a function's default
|
|
591
767
|
* export. Mirrors the host-assembled `ctx` (`__buildCtx` in `runtime.rs`);
|
|
592
|
-
* every member is a host-provided async function bridged to a Rust backend
|
|
768
|
+
* every member is a host-provided async function bridged to a Rust backend,
|
|
769
|
+
* except `crypto`, which is synchronous (pure CPU inside the isolate).
|
|
593
770
|
*/
|
|
594
771
|
interface OxyFunctionContext {
|
|
595
772
|
/** Invoking user (route) or system identity (schedule/airway). */
|
|
@@ -655,8 +832,22 @@ interface OxyFunctionContext {
|
|
|
655
832
|
env: Record<string, string>;
|
|
656
833
|
/** Structured per-invocation logging (captured + surfaced with the response). */
|
|
657
834
|
log(...args: unknown[]): void;
|
|
658
|
-
/**
|
|
659
|
-
|
|
835
|
+
/**
|
|
836
|
+
* HMAC sign / verify and a constant-time compare. Synchronous — no `await`.
|
|
837
|
+
* See {@link OxyCryptoApi}.
|
|
838
|
+
*/
|
|
839
|
+
crypto: OxyCryptoApi;
|
|
840
|
+
/**
|
|
841
|
+
* Read-only SQL (`SELECT` / `WITH` only) against the app's default database,
|
|
842
|
+
* capped at the function row limit. Resolves to `{ rows, truncated }` — the
|
|
843
|
+
* shape the host sends (`host.rs` `query`), the same as `ctx.warehouse.query`;
|
|
844
|
+
* `truncated` says the cap cut rows. Destructure it:
|
|
845
|
+
* `const { rows } = await ctx.query(sql)`.
|
|
846
|
+
*/
|
|
847
|
+
query(sql: string): Promise<{
|
|
848
|
+
rows: OxyFunctionRow[];
|
|
849
|
+
truncated: boolean;
|
|
850
|
+
}>;
|
|
660
851
|
/** Read-only SQL with a higher row cap, yielded to the caller in batches. */
|
|
661
852
|
queryStream(sql: string, opts?: {
|
|
662
853
|
batchSize?: number;
|
|
@@ -675,7 +866,11 @@ interface OxyFunctionContext {
|
|
|
675
866
|
*
|
|
676
867
|
* `database` must be in this function's manifest `destinations` — a
|
|
677
868
|
* transaction is a write, and the same fail-closed allowlist applies. Postgres
|
|
678
|
-
* only; other backends reject `ctx.tx` rather than faking it.
|
|
869
|
+
* only; other backends reject `ctx.tx` rather than faking it. A customer
|
|
870
|
+
* warehouse is refused unless named in `customerWarehouseWrites`; for the app's
|
|
871
|
+
* own OLTP store use `ctx.oltp.tx`, which needs no destination. A transaction
|
|
872
|
+
* counts as a write even when your callback only reads — `begin` cannot know —
|
|
873
|
+
* so for reads alone use `ctx.warehouse.query`.
|
|
679
874
|
*
|
|
680
875
|
* **Do not catch a failed statement and return normally.** A statement the
|
|
681
876
|
* server rejects aborts the whole transaction, and `COMMIT` on an aborted
|
|
@@ -701,20 +896,28 @@ interface OxyFunctionContext {
|
|
|
701
896
|
*/
|
|
702
897
|
tx<T>(database: string, fn: (tx: OxyTransaction) => Promise<T> | T): Promise<T>;
|
|
703
898
|
/**
|
|
704
|
-
* Read/write the app's OWN per-org OLTP schema
|
|
705
|
-
*
|
|
706
|
-
* by the fail-closed `oltp` manifest capability (`{ enabled: true }`).
|
|
707
|
-
* {@link OxyOltpApi}.
|
|
899
|
+
* **Records your app edits.** Read/write the app's OWN per-org OLTP schema
|
|
900
|
+
* (derived from its slug), with `ctx.oltp.tx` for several statements at once.
|
|
901
|
+
* Gated by the fail-closed `oltp` manifest capability (`{ enabled: true }`).
|
|
902
|
+
* See {@link OxyOltpApi}.
|
|
708
903
|
*/
|
|
709
904
|
oltp: OxyOltpApi;
|
|
905
|
+
/**
|
|
906
|
+
* **Facts: what happened.** Append-only history in the app's own Airhouse
|
|
907
|
+
* schema, written as the app. Gated by the fail-closed `airhouse` manifest
|
|
908
|
+
* capability (`{ enabled: true }`). See {@link OxyAirhouseApi}.
|
|
909
|
+
*/
|
|
910
|
+
airhouse: OxyAirhouseApi;
|
|
911
|
+
/** Credentials the app rotates — not a state store. See {@link OxySecretsApi}. */
|
|
710
912
|
secrets: OxySecretsApi;
|
|
711
913
|
semantic: OxySemanticApi;
|
|
712
914
|
airway: OxyAirwayApi;
|
|
713
915
|
email: OxyEmailApi;
|
|
916
|
+
/** **Files: bytes.** The app's private storage silo. See {@link OxyStorageApi}. */
|
|
714
917
|
storage: OxyStorageApi;
|
|
715
918
|
}
|
|
716
919
|
/** Signature of a function's default export: `export default async (req, ctx) => Response`. */
|
|
717
920
|
type OxyFunctionHandler = (req: OxyFunctionRequest, ctx: OxyFunctionContext) => Promise<Response> | Response;
|
|
718
921
|
//#endregion
|
|
719
|
-
export {
|
|
720
|
-
//# sourceMappingURL=function-context-
|
|
922
|
+
export { StoragePutOptions as A, OxyStorageApi as C, StorageDownloadUrl as D, OxyWarehouseApi as E, StorageUploadUrl as M, StorageUploadUrlInput as N, StorageListPage as O, OxySemanticApi as S, OxyVerifyHmacInput as T, OxyOrgAssignment as _, OxyAirwayApi as a, OxyReach as b, OxyFetchResult as c, OxyFunctionRequest as d, OxyFunctionRow as f, OxyOltpApi as g, OxyIdentityKind as h, OxyAirhouseApi as i, StoragePutResult as j, StorageObject as k, OxyFunctionContext as l, OxyHmacInput as m, EmailSendInput as n, OxyCryptoApi as o, OxyFunctionUser as p, EmailSendResult as r, OxyEmailApi as s, EmailAttachment as t, OxyFunctionHandler as u, OxyOrgPlace as v, OxyTransaction as w, OxySecretsApi as x, OxyOrgTeam as y };
|
|
923
|
+
//# sourceMappingURL=function-context-BNpL5bFb.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"function-context-BNpL5bFb.d.cts","names":[],"sources":["../src/custom-app/function-context.ts"],"mappings":"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"}
|