@oxy-hq/sdk 2.13.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.
Files changed (44) hide show
  1. package/README.md +67 -0
  2. package/dist/{function-context-D8eyZuw_.d.cts → function-context-BNpL5bFb.d.cts} +223 -20
  3. package/dist/function-context-BNpL5bFb.d.cts.map +1 -0
  4. package/dist/{function-context-D8eyZuw_.d.mts → function-context-BNpL5bFb.d.mts} +223 -20
  5. package/dist/function-context-BNpL5bFb.d.mts.map +1 -0
  6. package/dist/index.cjs +19 -16
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +3 -3
  9. package/dist/index.d.cts.map +1 -1
  10. package/dist/index.d.mts +3 -3
  11. package/dist/index.d.mts.map +1 -1
  12. package/dist/index.mjs +17 -15
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/ops.d.cts +1 -1
  15. package/dist/ops.d.mts +1 -1
  16. package/dist/{react-D-Sf973d.d.mts → react-CljeXJuw.d.cts} +56 -7
  17. package/dist/react-CljeXJuw.d.cts.map +1 -0
  18. package/dist/{react-D-Sf973d.d.cts → react-CljeXJuw.d.mts} +56 -7
  19. package/dist/react-CljeXJuw.d.mts.map +1 -0
  20. package/dist/{react-CkAQg9wB.cjs → react-Dvkv2deI.cjs} +46 -55
  21. package/dist/react-Dvkv2deI.cjs.map +1 -0
  22. package/dist/{react-Cq3xULOr.mjs → react-OW1t_J0M.mjs} +39 -22
  23. package/dist/react-OW1t_J0M.mjs.map +1 -0
  24. package/dist/rolldown-runtime-KC0qvQup.cjs +34 -0
  25. package/dist/shell.cjs +4 -3
  26. package/dist/shell.cjs.map +1 -1
  27. package/dist/shell.d.cts +2 -2
  28. package/dist/shell.d.mts +2 -2
  29. package/dist/shell.mjs +1 -1
  30. package/dist/testing.cjs +4431 -0
  31. package/dist/testing.cjs.map +1 -0
  32. package/dist/testing.d.cts +656 -0
  33. package/dist/testing.d.cts.map +1 -0
  34. package/dist/testing.d.mts +656 -0
  35. package/dist/testing.d.mts.map +1 -0
  36. package/dist/testing.mjs +4395 -0
  37. package/dist/testing.mjs.map +1 -0
  38. package/package.json +22 -9
  39. package/dist/function-context-D8eyZuw_.d.cts.map +0 -1
  40. package/dist/function-context-D8eyZuw_.d.mts.map +0 -1
  41. package/dist/react-CkAQg9wB.cjs.map +0 -1
  42. package/dist/react-Cq3xULOr.mjs.map +0 -1
  43. package/dist/react-D-Sf973d.d.cts.map +0 -1
  44. package/dist/react-D-Sf973d.d.mts.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 hands the isolate the raw request body as a string (see
7
- * `req_json` in `runtime.rs`); parse it yourself, e.g.
8
- * `JSON.parse(req.body || "{}")`. This is intentionally *not* a full Web
9
- * `Request` — there is no `.json()` / headers object in v1.
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` passes to your callback — a pinned connection with an
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 — and reach for `ctx.oltp` a handful of times per request, not
257
- * in a hot loop.
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
- /** `ctx.secrets` — write app-scoped secrets (gated by the `secrets.write` capability). */
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
- /** Server-side copy within the app's silo (requires `storage.write`). */
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
- /** Read-only SQL (SELECT/WITH only), function-scoped row cap. Resolves to the rows. */
659
- query(sql: string): Promise<OxyFunctionRow[]>;
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 (derived from its slug). The
705
- * write half `ctx.warehouse` cannot give an app on a managed database. Gated
706
- * by the fail-closed `oltp` manifest capability (`{ enabled: true }`). See
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 { StorageDownloadUrl as C, StoragePutResult as D, StoragePutOptions as E, StorageUploadUrl as O, OxyWarehouseApi as S, StorageObject as T, OxyReach as _, OxyEmailApi as a, OxyStorageApi as b, OxyFunctionHandler as c, OxyFunctionUser as d, OxyIdentityKind as f, OxyOrgTeam as g, OxyOrgPlace as h, OxyAirwayApi as i, StorageUploadUrlInput as k, OxyFunctionRequest as l, OxyOrgAssignment as m, EmailSendInput as n, OxyFetchResult as o, OxyOltpApi as p, EmailSendResult as r, OxyFunctionContext as s, EmailAttachment as t, OxyFunctionRow as u, OxySecretsApi as v, StorageListPage as w, OxyTransaction as x, OxySemanticApi as y };
720
- //# sourceMappingURL=function-context-D8eyZuw_.d.cts.map
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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"}