@oxy-hq/sdk 2.11.0 → 2.12.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 (40) hide show
  1. package/README.md +44 -4
  2. package/dist/function-context-D8eyZuw_.d.cts +720 -0
  3. package/dist/function-context-D8eyZuw_.d.cts.map +1 -0
  4. package/dist/function-context-D8eyZuw_.d.mts +720 -0
  5. package/dist/function-context-D8eyZuw_.d.mts.map +1 -0
  6. package/dist/index.cjs +156 -11
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +338 -638
  9. package/dist/index.d.cts.map +1 -1
  10. package/dist/index.d.mts +338 -638
  11. package/dist/index.d.mts.map +1 -1
  12. package/dist/index.mjs +155 -12
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/ops.cjs +85 -0
  15. package/dist/ops.cjs.map +1 -0
  16. package/dist/ops.d.cts +61 -0
  17. package/dist/ops.d.cts.map +1 -0
  18. package/dist/ops.d.mts +61 -0
  19. package/dist/ops.d.mts.map +1 -0
  20. package/dist/ops.mjs +79 -0
  21. package/dist/ops.mjs.map +1 -0
  22. package/dist/{react-DqnINwTi.mjs → react-BXGyzgz0.mjs} +8 -3
  23. package/dist/react-BXGyzgz0.mjs.map +1 -0
  24. package/dist/{react-CLONxcnA.d.cts → react-DW7Z96sD.d.cts} +8 -1
  25. package/dist/{react-CLONxcnA.d.cts.map → react-DW7Z96sD.d.cts.map} +1 -1
  26. package/dist/{react-CLONxcnA.d.mts → react-DW7Z96sD.d.mts} +8 -1
  27. package/dist/{react-CLONxcnA.d.mts.map → react-DW7Z96sD.d.mts.map} +1 -1
  28. package/dist/{react-riTxd9ce.cjs → react-DcT-mUPj.cjs} +8 -3
  29. package/dist/react-DcT-mUPj.cjs.map +1 -0
  30. package/dist/shell.cjs +34 -4
  31. package/dist/shell.cjs.map +1 -1
  32. package/dist/shell.d.cts +39 -7
  33. package/dist/shell.d.cts.map +1 -1
  34. package/dist/shell.d.mts +39 -7
  35. package/dist/shell.d.mts.map +1 -1
  36. package/dist/shell.mjs +34 -5
  37. package/dist/shell.mjs.map +1 -1
  38. package/package.json +12 -1
  39. package/dist/react-DqnINwTi.mjs.map +0 -1
  40. package/dist/react-riTxd9ce.cjs.map +0 -1
@@ -0,0 +1,720 @@
1
+
2
+ //#region src/custom-app/function-context.d.ts
3
+ /**
4
+ * The request passed as the first argument to a function's default export.
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.
10
+ */
11
+ interface OxyFunctionRequest {
12
+ /** Raw request body as received (JSON string for a JSON POST). */
13
+ body: string;
14
+ }
15
+ /** A single row from a `ctx.query` / `ctx.queryStream` result. */
16
+ type OxyFunctionRow = Record<string, unknown>;
17
+ /** One org team the caller belongs to, as reported by {@link OxyFunctionUser.teams}. */
18
+ interface OxyOrgTeam {
19
+ id: string;
20
+ name: string;
21
+ }
22
+ /**
23
+ * Who — or what — invoked this function.
24
+ *
25
+ * `"system"` means **no caller to attribute this to** — not necessarily "no
26
+ * human caused it". A schedule tick, an Airway transform step, and an operator's
27
+ * manual *Run now* all take this path: they run under the org owner's `id` (the
28
+ * invocation record needs a real user FK) with every caller field absent. So on
29
+ * a manual run a person really did click, and there is still no way to reach
30
+ * them; the platform does not carry the triggering operator through the job
31
+ * queue.
32
+ *
33
+ * Any branch that emails "the person who clicked" or renders a personal view
34
+ * must check this rather than sniff the synthetic `email` — and must have a
35
+ * sensible answer for the case where there is nobody to send to.
36
+ */
37
+ type OxyIdentityKind = "user" | "system";
38
+ /**
39
+ * Identity of the invoking user (route) or the system identity (schedule,
40
+ * Airway step, or a manual job run).
41
+ *
42
+ * Assembled server-side on every invocation from the authenticated session —
43
+ * **nothing on it is client-supplied**, which is the entire reason to read
44
+ * identity here instead of from the request body. See
45
+ * `internal-docs/custom-apps-user-identity.md` for the full contract, including
46
+ * what the client-side `useShellContext()` can and cannot be trusted for.
47
+ */
48
+ interface OxyFunctionUser {
49
+ /**
50
+ * `users.id`. On a `"system"` invocation this is the org owner's id and not a
51
+ * caller — check {@link kind} before attributing anything to it.
52
+ */
53
+ id: string;
54
+ /**
55
+ * Their email; `schedule+<fn>@system.oxy` when {@link kind} is `"system"`; and
56
+ * **`null` for a frontline worker** — a crew member enrolled by PIN on a shared
57
+ * device has no mailbox, and the platform stores none rather than inventing
58
+ * one. That null is the one field that tells the crew from the office inside
59
+ * a function, because a worker can never hold org membership (see
60
+ * `orgRole`, which is absent for them too). Treat it as `string | null` in
61
+ * app logic; it was typed `string` before the crew existed.
62
+ */
63
+ email: string | null;
64
+ /**
65
+ * The org that owns this app — the tenant boundary for anything the function
66
+ * reads or writes.
67
+ *
68
+ * Servers before 2026-08-21 mistakenly sent this as `org_id`, so `orgId` read
69
+ * `undefined` there; both keys are populated now. If your function filters SQL
70
+ * on it, that is exactly the bug to re-check.
71
+ */
72
+ orgId: string;
73
+ /** Display name. Absent on a `"system"` invocation. User-controlled free text —
74
+ * fine for a greeting or an audit row, never a key, and escape it before it
75
+ * reaches HTML or SQL. */
76
+ name?: string;
77
+ /** Avatar URL. Absent when unset or on a `"system"` invocation. */
78
+ picture?: string;
79
+ /**
80
+ * The caller's role **within this app**, derived server-side from app
81
+ * membership (with org-owner / Oxy-staff break-glass). Absent when they hold
82
+ * no membership.
83
+ *
84
+ * This is the value to gate a privileged surface on — it cannot be forged by
85
+ * the client, unlike a query param or a client-side flag:
86
+ *
87
+ * ```ts
88
+ * if (ctx.user.appRole !== "admin") {
89
+ * return Response.json({ error: "forbidden" }, { status: 403 });
90
+ * }
91
+ * ```
92
+ *
93
+ * Note it is deliberately NOT the org role: an app admin administers one app
94
+ * without holding org-Admin (which also carries billing and member management).
95
+ *
96
+ * A `"system"` invocation runs under the org owner, so this reads `"admin"`
97
+ * there — a schedule carries owner authority by construction. Add a
98
+ * {@link kind} check when a surface must be human-only.
99
+ */
100
+ appRole?: "admin" | "member";
101
+ /**
102
+ * The caller's role in the owning **org**. Absent when they reach the app
103
+ * without an org membership (Oxy staff on break-glass) or on a `"system"`
104
+ * invocation.
105
+ *
106
+ * Informational, not a gate — org standing and app standing are separate
107
+ * rings. Use it to explain ("ask your org admin to connect a warehouse"), to
108
+ * label, or to route; gate on {@link appRole}.
109
+ */
110
+ orgRole?: "owner" | "admin" | "member";
111
+ /**
112
+ * The org teams the caller belongs to, name-sorted, and scoped to this app's
113
+ * org — teams they hold in other orgs are never reported. Empty when they
114
+ * belong to none.
115
+ *
116
+ * Optional because a server older than 2026-08-21 does not send it: use
117
+ * `ctx.user.teams?.some(...)`, never `ctx.user.teams.some(...)`, or the
118
+ * function throws on that server rather than degrading.
119
+ *
120
+ * Useful for *shaping* a view (default the Finance team to the finance tab).
121
+ * Not a permission: a team only grants anything on an app through an app team
122
+ * grant, which is already folded into {@link appRole}. Gating on a team name
123
+ * invents a permission the platform cannot revoke.
124
+ */
125
+ teams?: OxyOrgTeam[];
126
+ /**
127
+ * Whether there is a caller to attribute this invocation to.
128
+ *
129
+ * On a current server this is exact — `ctx.user.kind === "system"` is the
130
+ * check.
131
+ *
132
+ * Optional for the same reason as {@link teams}: a server older than
133
+ * 2026-08-21 does not send it. Note there is no safe *inference* to fall back
134
+ * on, in either direction — `=== "system"` reads `false` for a cron tick, and
135
+ * `!== "user"` reads `true` for a real person. An older server genuinely
136
+ * cannot tell you.
137
+ *
138
+ * So if you must support one, don't infer: a schedule invokes the function
139
+ * with the `input` you configured on it, which is yours to mark.
140
+ *
141
+ * ```ts
142
+ * const body = JSON.parse(req.body || "{}");
143
+ * const isSystem = ctx.user.kind ? ctx.user.kind === "system" : body._trigger === "schedule";
144
+ * ```
145
+ */
146
+ kind?: OxyIdentityKind;
147
+ /**
148
+ * Where the caller may act, derived by the platform from their assignments
149
+ * (`internal-docs/operating-graph.md` §3.3): a system invocation or an app
150
+ * admin everywhere; a holder of an org-wide position everywhere; an
151
+ * assigned person exactly their places; an unassigned org member
152
+ * everywhere and an unassigned frontline worker nowhere. Apply it with
153
+ * `@oxy-hq/sdk/ops` (`requireReach`, `predicate`); tighten it if the app
154
+ * needs to, never widen it. A lookup failure lands on nowhere.
155
+ */
156
+ reach: OxyReach;
157
+ }
158
+ /** `ctx.user.reach` — see {@link OxyFunctionUser.reach}. */
159
+ interface OxyReach {
160
+ everywhere: boolean;
161
+ /** Why everywhere, when it is; `null` when scoped or nowhere. */
162
+ via: "system" | "app-admin" | "org-wide-position" | "org-member" | null;
163
+ /** The caller's assigned location ids, in id order; empty when none. */
164
+ locations: string[];
165
+ }
166
+ /** Result of a `ctx.fetch` call. */
167
+ interface OxyFetchResult {
168
+ status: number;
169
+ /** Response body, decoded per the requested {@link OxyFetchInit.encoding}. */
170
+ body: string;
171
+ /** Echoes how `body` was encoded (`"utf8"` unless base64 was requested). */
172
+ encoding?: "utf8" | "base64";
173
+ }
174
+ /**
175
+ * `init` for `ctx.fetch` — the standard `RequestInit` fields the host honours
176
+ * (`method`, `headers`, `body`) plus how to decode the response.
177
+ */
178
+ type OxyFetchInit = RequestInit & {
179
+ /**
180
+ * How to decode the response body. `"utf8"` (default) is **lossy for
181
+ * binary** — every non-UTF-8 byte becomes U+FFFD, so a fetched PDF/PNG comes
182
+ * back corrupt. Pass `"base64"` for any binary response, e.g. to hand it
183
+ * straight to an email attachment.
184
+ */
185
+ encoding?: "utf8" | "base64";
186
+ };
187
+ /**
188
+ * `ctx.warehouse.*` — one of the app's configured databases by name.
189
+ *
190
+ * The writes require the database in the function's `destinations` allowlist;
191
+ * `query` does not, because that allowlist is about modifying a project's
192
+ * warehouse, and a `postgres_managed` database resolves the read-only analyst
193
+ * for every caller regardless.
194
+ */
195
+ interface OxyWarehouseApi {
196
+ /**
197
+ * Read from a named database.
198
+ *
199
+ * `ctx.query` only ever reaches the project's DEFAULT database, so this is
200
+ * how an app reads its own per-org OLTP store, which sits beside whatever
201
+ * warehouse the project analyses.
202
+ */
203
+ query(database: string, sql: string): Promise<{
204
+ rows: OxyFunctionRow[];
205
+ truncated: boolean;
206
+ }>;
207
+ insert(database: string, table: string, rows: OxyFunctionRow[]): Promise<unknown>;
208
+ exec(database: string, sql: string): Promise<unknown>;
209
+ upsert(database: string, table: string, rows: OxyFunctionRow[], conflictColumns: string[]): Promise<unknown>;
210
+ }
211
+ /**
212
+ * The handle `ctx.tx` passes to your callback — a pinned connection with an
213
+ * open transaction.
214
+ *
215
+ * Both methods take **bound parameters** (`$1`, `$2`, …). Never build SQL by
216
+ * concatenating request data: `ctx.warehouse.exec` takes a bare string, but a
217
+ * transaction exists for surfaces that accept end-user input, and placeholders
218
+ * are the only thing that makes that safe.
219
+ *
220
+ * The handle is live only for the duration of the callback. Using it after the
221
+ * callback returns throws — it is not a connection you can stash.
222
+ */
223
+ interface OxyTransaction {
224
+ /** Run a row-returning statement (including `INSERT … RETURNING`). */
225
+ query(sql: string, params?: unknown[]): Promise<OxyFunctionRow[]>;
226
+ /** Run a statement for its effect; resolves to the number of rows affected. */
227
+ exec(sql: string, params?: unknown[]): Promise<number>;
228
+ }
229
+ /**
230
+ * `ctx.oltp` — read and WRITE the app's OWN per-org OLTP schema (`app_<writer>`)
231
+ * on the managed Postgres tenant, and nothing else.
232
+ *
233
+ * This is the write half `ctx.warehouse` cannot give an app: for a
234
+ * `postgres_managed` database `ctx.warehouse` resolves the read-only analyst
235
+ * (org-wide read, the org's `raw_*` extracts included), so a write authenticates
236
+ * and then fails `permission denied`. `ctx.oltp` resolves the app's **writer**
237
+ * role instead — DML rights scoped to the one `app_<writer>` schema, so it is
238
+ * narrower on reads (no `raw_*`) and finally writable.
239
+ *
240
+ * Gated by the fail-closed `oltp` manifest capability (`"oltp": { "enabled":
241
+ * true }`) — a pure gate. The target schema is derived from the app's own slug
242
+ * (`oltp-bookings` → `app_oltp_bookings`), never named in the manifest, so a
243
+ * manifest cannot point `ctx.oltp` at another app's schema. The store must be
244
+ * provisioned first (ask whoever operates the org). No database name is passed —
245
+ * the app's own store is implicit.
246
+ *
247
+ * Both methods take **bound parameters** (`$1`, `$2`, …). Never build SQL by
248
+ * concatenating request data — a booking form is exactly the surface that takes
249
+ * end-user input, and placeholders are the only thing that makes it safe. Each
250
+ * call auto-commits; a failed statement rolls back.
251
+ *
252
+ * **Cost:** each call opens its own connection to the tenant (a TCP + TLS
253
+ * handshake, and a wake-up if the compute was idle) and its own transaction, so
254
+ * a per-row loop pays that per row. Prefer one statement over many — a
255
+ * 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.
258
+ *
259
+ * ```ts
260
+ * const [row] = await ctx.oltp.query(
261
+ * "INSERT INTO bookings (name, party_size) VALUES ($1, $2) RETURNING id",
262
+ * [name, partySize],
263
+ * );
264
+ * ```
265
+ */
266
+ interface OxyOltpApi {
267
+ /** Run a row-returning statement (including `INSERT … RETURNING`). */
268
+ query(sql: string, params?: unknown[]): Promise<OxyFunctionRow[]>;
269
+ /** Run a statement for its effect; resolves to the number of rows affected. */
270
+ exec(sql: string, params?: unknown[]): Promise<number>;
271
+ }
272
+ /** `ctx.secrets` — write app-scoped secrets (gated by the `secrets.write` capability). */
273
+ interface OxySecretsApi {
274
+ set(key: string, value: string): Promise<void>;
275
+ }
276
+ /** `ctx.semantic` — airlayer-compiled semantic queries (inherits the pre-agg fast path). */
277
+ interface OxySemanticApi {
278
+ /**
279
+ * Run a semantic query. `scope: "reach"` pins it server-side to the
280
+ * caller's `ctx.user.reach`: one `in` filter per view the query names whose
281
+ * primary entity is bound to the org's locations registry, over the keys the
282
+ * caller's places carry in that system. A caller who reaches everywhere is
283
+ * left alone; a query naming no bound view is refused rather than answered
284
+ * whole. `internal-docs/operating-graph.md` §3.6.
285
+ */
286
+ query(spec: Record<string, unknown> & {
287
+ scope?: "reach";
288
+ }): Promise<unknown>;
289
+ }
290
+ /** `ctx.airway` — seed/await an Airway ELT pipeline run. */
291
+ interface OxyAirwayApi {
292
+ run(pipelineRef: string, variables?: Record<string, unknown> | null): Promise<{
293
+ runId: string;
294
+ }>;
295
+ }
296
+ /**
297
+ * Input to `ctx.email.send`. Platform-injected: the sender mailbox (`from`) is
298
+ * platform-controlled and **not** an accepted field — passing it is a typed
299
+ * error. Provide `html` and/or `text` as the body (render a template to HTML
300
+ * with `render` from `@oxy-hq/sdk/email`).
301
+ */
302
+ interface EmailSendInput {
303
+ /** Recipient address(es). Required. */
304
+ to: string | string[];
305
+ /** CC address(es). */
306
+ cc?: string | string[];
307
+ /** BCC address(es). */
308
+ bcc?: string | string[];
309
+ /** Reply-To address — the only sender-identity field an author may set. */
310
+ replyTo?: string;
311
+ /** Subject line. Required. */
312
+ subject: string;
313
+ /** HTML body. Provide at least one of `html` / `text`. */
314
+ html?: string;
315
+ /** Plain-text body. Provide at least one of `html` / `text`. */
316
+ text?: string;
317
+ /**
318
+ * Optional idempotency key (≤256 chars). Accepted and validated in v1 but a
319
+ * no-op until the persisted idempotency table lands — adopt it now so
320
+ * background (retried) sends become exactly-once once it does.
321
+ */
322
+ idempotencyKey?: string;
323
+ /**
324
+ * Files to attach. Max 20 per send, and **10 MiB decoded in total** — SES
325
+ * caps a whole message near 40 MB, so for anything larger store the file with
326
+ * {@link OxyStorageApi} and email a presigned link instead of inlining it.
327
+ *
328
+ * `content` is base64 by default; for generated text set
329
+ * `encoding: "utf8"` and attach the string as-is.
330
+ */
331
+ attachments?: EmailAttachment[];
332
+ }
333
+ /** One attachment on {@link EmailSendInput}. */
334
+ interface EmailAttachment {
335
+ /** Filename shown to the recipient. Required; path separators are stripped. */
336
+ filename: string;
337
+ /**
338
+ * File contents, interpreted per {@link EmailAttachment.encoding} — base64 by
339
+ * default, which is the only way binary crosses the isolate boundary.
340
+ */
341
+ content: string;
342
+ /**
343
+ * How `content` is encoded. Defaults to `"base64"`.
344
+ *
345
+ * Use `"utf8"` to attach text the function just generated (CSV, JSON, HTML)
346
+ * — it needs no encoder and is byte-exact for non-ASCII. `btoa` is the wrong
347
+ * tool there: it encodes U+0080..U+00FF as *Latin1*, so accented text comes
348
+ * out as mojibake rather than as an error. For binary, take base64 straight
349
+ * from the source — `ctx.storage.get(key, { encoding: "base64" })` or
350
+ * `ctx.fetch(url, { encoding: "base64" })` — or {@link bytesToBase64} for a
351
+ * `Uint8Array` you built yourself.
352
+ */
353
+ encoding?: "base64" | "utf8";
354
+ /** MIME type; defaults to `application/octet-stream`. */
355
+ contentType?: string;
356
+ /** Render inline (e.g. an image referenced as `cid:<contentId>`) instead of as a download. */
357
+ inline?: boolean;
358
+ /** Content-ID for an inline part, referenced from the HTML body as `cid:<contentId>`. */
359
+ contentId?: string;
360
+ }
361
+ /** Result of a successful `ctx.email.send`. */
362
+ interface EmailSendResult {
363
+ /** Provider (SES) message id of the sent message. */
364
+ messageId: string;
365
+ }
366
+ /** `ctx.email` — send email (gated by the `email.send` capability). */
367
+ interface OxyEmailApi {
368
+ send(input: EmailSendInput): Promise<EmailSendResult>;
369
+ }
370
+ /** Input to `ctx.storage.getUploadUrl`. */
371
+ interface StorageUploadUrlInput {
372
+ /**
373
+ * Destination path inside the app's silo, e.g. `"uploads/q1-report.pdf"`.
374
+ * Segments are sanitized server-side and cannot escape the silo. Omit to use
375
+ * `filename`, which is placed under `uploads/`.
376
+ */
377
+ pathname?: string;
378
+ /** Shorthand for `pathname: "uploads/<filename>"`. */
379
+ filename?: string;
380
+ /** MIME type; bound into the presigned PUT signature. Inferred when omitted. */
381
+ contentType?: string;
382
+ /**
383
+ * Exact byte length of the upload, bound into the signature — S3 rejects a
384
+ * body of any other size. Capped by the server's upload ceiling (100 MiB by
385
+ * default).
386
+ */
387
+ contentLength: number;
388
+ /** Presign lifetime in seconds (default 900; max 604800 — SigV4's own limit). */
389
+ expiresInSeconds?: number;
390
+ }
391
+ /** A minted presigned upload. */
392
+ interface StorageUploadUrl {
393
+ /** Presigned PUT — the browser uploads the file bytes directly to this URL. */
394
+ url: string;
395
+ /**
396
+ * The stored key. Record it (e.g. on a row in your warehouse) — it is how you
397
+ * fetch, list or link to the asset later. A random suffix is added so two
398
+ * people uploading `report.pdf` don't collide.
399
+ */
400
+ key: string;
401
+ /** ISO-8601 expiry of the presigned URL. */
402
+ expiresAt: string;
403
+ /**
404
+ * Retention tag for this key, present only when your app declares a matching
405
+ * `storage.retention` rule in `oxy-app.json` (e.g. `"oxy-ttl=30d"`).
406
+ *
407
+ * **When present, the upload MUST send it as the `x-amz-tagging` header** — it
408
+ * is bound into the signature, so omitting it fails the PUT with a signature
409
+ * mismatch rather than storing an untagged object:
410
+ *
411
+ * ```ts
412
+ * const { url, tagging } = await ctx.storage.getUploadUrl({ ... });
413
+ * await fetch(url, {
414
+ * method: "PUT",
415
+ * body: file,
416
+ * headers: {
417
+ * "Content-Type": file.type,
418
+ * ...(tagging ? { "x-amz-tagging": tagging } : {}),
419
+ * },
420
+ * });
421
+ * ```
422
+ *
423
+ * Signing it is deliberate: a browser that could drop the header could opt any
424
+ * upload out of the app's own retention policy.
425
+ */
426
+ tagging?: string;
427
+ }
428
+ /** A minted presigned download. */
429
+ interface StorageDownloadUrl {
430
+ url: string;
431
+ expiresAt: string;
432
+ }
433
+ /** One asset in the app's silo. */
434
+ interface StorageObject {
435
+ key: string;
436
+ size: number;
437
+ contentType?: string | null;
438
+ /** ISO-8601. */
439
+ lastModified?: string | null;
440
+ }
441
+ /** One page of {@link OxyStorageApi.list}. */
442
+ interface StorageListPage {
443
+ objects: StorageObject[];
444
+ /** Pass back as `cursor` to fetch the next page; `null` when complete. */
445
+ cursor: string | null;
446
+ hasMore: boolean;
447
+ }
448
+ /** Options for {@link OxyStorageApi.put}. */
449
+ interface StoragePutOptions {
450
+ /** MIME type. Inferred from the pathname's extension when omitted. */
451
+ contentType?: string;
452
+ /**
453
+ * How `body` is encoded. `"base64"` is what makes **binary** generated assets
454
+ * (PDF, PNG, Parquet) possible — a UTF-8 string would corrupt them.
455
+ */
456
+ encoding?: "utf8" | "base64";
457
+ /** Append a short random component before the extension to avoid collisions. */
458
+ addRandomSuffix?: boolean;
459
+ /**
460
+ * Replace an existing asset at this path. Defaults to `false` — writing over
461
+ * an asset by accident is worse than an error, so this is opt-in.
462
+ */
463
+ allowOverwrite?: boolean;
464
+ /** `Cache-Control: max-age=<seconds>` stored on the object. */
465
+ cacheControlMaxAge?: number;
466
+ }
467
+ /** Result of a `put` (and of `copy`). */
468
+ interface StoragePutResult {
469
+ key: string;
470
+ size: number;
471
+ contentType: string;
472
+ }
473
+ /**
474
+ * `ctx.storage` — this app's **asset store**, covering both kinds of file an app
475
+ * produces, in one silo (`customer-app-storage/<app_id>/`):
476
+ *
477
+ * - **Uploaded** — a human picks a file; `getUploadUrl` mints a presigned PUT and
478
+ * the browser uploads **straight to S3**, so uploads aren't bounded by the
479
+ * request-body limit and the bytes never pass through your function.
480
+ * - **Generated** — your function produces the file (a rendered PDF, a CSV
481
+ * export, a chart PNG) and writes it with `put`, using
482
+ * `{ encoding: "base64" }` for binary.
483
+ *
484
+ * Gated by the fail-closed `storage.read` / `storage.write` capabilities in
485
+ * `oxy-app.json`. Every asset is private; reads are always presigned and
486
+ * time-boxed. Keys are confined to your app — another app's key is rejected.
487
+ *
488
+ * ```ts
489
+ * // Uploaded: mint a URL, browser PUTs to it, then record `key`.
490
+ * const { url, key } = await ctx.storage.getUploadUrl({
491
+ * filename: "q1-report.pdf", contentType: "application/pdf", contentLength: size,
492
+ * });
493
+ *
494
+ * // Generated: write a CSV your function just built.
495
+ * const { key } = await ctx.storage.put("generated/jan.csv", csv);
496
+ *
497
+ * // Either way: email a link that outlives the request.
498
+ * const { url: link } = await ctx.storage.getDownloadUrl(key, {
499
+ * expiresInSeconds: 604800, download: true,
500
+ * });
501
+ * ```
502
+ */
503
+ interface OxyStorageApi {
504
+ /** Mint a presigned PUT for a browser upload (requires `storage.write`). */
505
+ getUploadUrl(input: StorageUploadUrlInput): Promise<StorageUploadUrl>;
506
+ /**
507
+ * Mint a presigned GET (requires `storage.read`). `download: true` forces a
508
+ * save-as via `Content-Disposition`, which is what an emailed link wants.
509
+ */
510
+ getDownloadUrl(key: string, opts?: {
511
+ expiresInSeconds?: number;
512
+ download?: boolean;
513
+ }): Promise<StorageDownloadUrl>;
514
+ /**
515
+ * Write a generated asset (requires `storage.write`). Capped at 6 MiB — for
516
+ * anything larger, mint a presigned upload URL and stream to it.
517
+ */
518
+ put(pathname: string, body: string, opts?: StoragePutOptions): Promise<StoragePutResult>;
519
+ /** Read an asset back; `null` when absent (requires `storage.read`). */
520
+ get(key: string, opts?: {
521
+ encoding?: "utf8" | "base64";
522
+ }): Promise<{
523
+ body: string;
524
+ contentType: string | null;
525
+ size: number;
526
+ encoding: string;
527
+ } | null>;
528
+ /** Metadata without the body; `null` when absent (requires `storage.read`). */
529
+ head(key: string): Promise<StorageObject | null>;
530
+ /**
531
+ * One page of assets (requires `storage.read`). Paginated deliberately — pass
532
+ * the returned `cursor` back to walk a large silo without loading it all.
533
+ */
534
+ list(opts?: {
535
+ prefix?: string;
536
+ limit?: number;
537
+ cursor?: string;
538
+ }): Promise<StorageListPage>;
539
+ /**
540
+ * Delete one or many assets (requires `storage.write`). Idempotent — deleting
541
+ * an absent key is a no-op success. `deleted` is the number of keys **accepted**
542
+ * for deletion (an absent key counts too), not a count of keys that existed.
543
+ */
544
+ delete(keyOrKeys: string | string[]): Promise<{
545
+ deleted: number;
546
+ }>;
547
+ /** Server-side copy within the app's silo (requires `storage.write`). */
548
+ copy(fromKey: string, toPathname: string, opts?: {
549
+ allowOverwrite?: boolean;
550
+ }): Promise<StoragePutResult>;
551
+ }
552
+ /** One of the org's locations, as `ctx.org.places()` returns it. */
553
+ interface OxyOrgPlace {
554
+ id: string;
555
+ org_id: string;
556
+ name: string;
557
+ /** The tenant's word for this level — `region`, `store` — or `null`. */
558
+ kind: string | null;
559
+ /** The place this one sits inside, or `null` for a root. */
560
+ parent_id: string | null;
561
+ status: "pre_launch" | "launching" | "open" | "archived" | "terminated";
562
+ /** IANA zone; what "due by close" means here. */
563
+ timezone: string;
564
+ /** The tenant's own id, if any. */
565
+ external_id: string | null;
566
+ /** `system` → id: what Toast, a camera console, payroll call this place. */
567
+ external_ids: Record<string, string>;
568
+ created_at: string;
569
+ updated_at: string;
570
+ }
571
+ /** One assignment — a person holding a position at a place (or org-wide). */
572
+ interface OxyOrgAssignment {
573
+ id: string;
574
+ user_id: string;
575
+ user_name: string;
576
+ user_kind: "member" | "frontline";
577
+ role_id: string;
578
+ role_name: string;
579
+ /** `location` — held at one place; `franchisor` — held across the org. */
580
+ role_scope: "location" | "franchisor";
581
+ /** `null` for an org-wide position. */
582
+ location_id: string | null;
583
+ location_name: string | null;
584
+ /** Who they report to at that place, if recorded. */
585
+ supervisor_id: string | null;
586
+ supervisor_name: string | null;
587
+ created_at: string;
588
+ }
589
+ /**
590
+ * The data-plane context passed as the second argument to a function's default
591
+ * export. Mirrors the host-assembled `ctx` (`__buildCtx` in `runtime.rs`);
592
+ * every member is a host-provided async function bridged to a Rust backend.
593
+ */
594
+ interface OxyFunctionContext {
595
+ /** Invoking user (route) or system identity (schedule/airway). */
596
+ user: OxyFunctionUser;
597
+ /**
598
+ * The org's people directory. Requires `"org": { "read": true }` in this
599
+ * function's manifest entry — without it the call is rejected before any
600
+ * query reaches the database.
601
+ *
602
+ * For naming a person: an assignee, a roster entry, who submitted something.
603
+ * Returns a display name and a role, and deliberately **no email, no phone,
604
+ * no location**.
605
+ *
606
+ * Who is in it: **people who can reach this app**. Org members, plus frontline
607
+ * workers holding a grant on this app — `kind` tells them apart, so a caller
608
+ * that must not name a worker can refuse on the field rather than by
609
+ * convention. A worker's `role` is `null`: that vocabulary is org membership's
610
+ * and a worker has none.
611
+ *
612
+ * REQUIRED, like every sibling here — `oltp`, `secrets`, `email`, `storage`,
613
+ * `airway` are all gated and all declared required. The binding is
614
+ * unconditional: `__buildCtx` is a static string that attaches `org` whatever
615
+ * the manifest says, and the refusal lives in the op, not in the binding. An
616
+ * optional member would therefore be a lie in the other direction, and under
617
+ * `strict` it makes `ctx.org.people()` — the spelling in every doc here and
618
+ * the only one the host binds — fail with "possibly undefined".
619
+ */
620
+ org: {
621
+ people(): Promise<{
622
+ people: Array<{
623
+ id: string;
624
+ name: string;
625
+ /** The org role, or `null` for a frontline worker. */
626
+ role: string | null;
627
+ kind: "member" | "frontline";
628
+ }>;
629
+ total: number;
630
+ }>;
631
+ /**
632
+ * The org's places — every location, with its hierarchy (`parent_id`,
633
+ * the tenant-named `kind`), lifecycle `status`, `timezone`, and what each
634
+ * integration calls it (`external_ids`, e.g. `{ toast: "…" }`). The whole
635
+ * registry, not a reach-scoped slice: an app that shows "Clovis" needs
636
+ * the row before it knows whether the caller reaches it. Same `org.read`
637
+ * capability as `people()`.
638
+ */
639
+ places(): Promise<{
640
+ places: OxyOrgPlace[];
641
+ total: number;
642
+ }>;
643
+ /**
644
+ * Who holds which position where — the roster, read. Scoped like
645
+ * `people()`: the assignments of people who can reach this app. Same
646
+ * `org.read` capability. Rosters are edited in Settings, not from a
647
+ * function.
648
+ */
649
+ assignments(): Promise<{
650
+ assignments: OxyOrgAssignment[];
651
+ total: number;
652
+ }>;
653
+ };
654
+ /** Read-only view of the app's configured secrets (project-scoped). */
655
+ env: Record<string, string>;
656
+ /** Structured per-invocation logging (captured + surfaced with the response). */
657
+ 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[]>;
660
+ /** Read-only SQL with a higher row cap, yielded to the caller in batches. */
661
+ queryStream(sql: string, opts?: {
662
+ batchSize?: number;
663
+ }): AsyncGenerator<OxyFunctionRow[], void, unknown>;
664
+ /**
665
+ * SSRF-allowlisted outbound HTTP with a response-size cap. Pass
666
+ * `{ encoding: "base64" }` for a binary response — the default UTF-8 decode
667
+ * corrupts it.
668
+ */
669
+ fetch(url: string, init?: OxyFetchInit): Promise<OxyFetchResult>;
670
+ warehouse: OxyWarehouseApi;
671
+ /**
672
+ * Run several statements atomically on one connection: commits when your
673
+ * callback resolves, rolls back when it throws, and rethrows your error
674
+ * either way. Resolves to whatever the callback returns.
675
+ *
676
+ * `database` must be in this function's manifest `destinations` — a
677
+ * transaction is a write, and the same fail-closed allowlist applies. Postgres
678
+ * only; other backends reject `ctx.tx` rather than faking it.
679
+ *
680
+ * **Do not catch a failed statement and return normally.** A statement the
681
+ * server rejects aborts the whole transaction, and `COMMIT` on an aborted
682
+ * transaction does not fail — Postgres applies nothing and reports success —
683
+ * so `ctx.tx` refuses to commit and throws instead, naming the statement that
684
+ * poisoned it. Let the error propagate.
685
+ *
686
+ * ```ts
687
+ * const orderId = await ctx.tx("appdb", async (tx) => {
688
+ * const [{ id }] = await tx.query(
689
+ * "INSERT INTO orders (table_no) VALUES ($1) RETURNING id",
690
+ * [tableNo],
691
+ * );
692
+ * for (const it of items) {
693
+ * await tx.exec(
694
+ * "INSERT INTO order_items (order_id, sku, qty) VALUES ($1, $2, $3)",
695
+ * [id, it.sku, it.qty],
696
+ * );
697
+ * }
698
+ * return id;
699
+ * });
700
+ * ```
701
+ */
702
+ tx<T>(database: string, fn: (tx: OxyTransaction) => Promise<T> | T): Promise<T>;
703
+ /**
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}.
708
+ */
709
+ oltp: OxyOltpApi;
710
+ secrets: OxySecretsApi;
711
+ semantic: OxySemanticApi;
712
+ airway: OxyAirwayApi;
713
+ email: OxyEmailApi;
714
+ storage: OxyStorageApi;
715
+ }
716
+ /** Signature of a function's default export: `export default async (req, ctx) => Response`. */
717
+ type OxyFunctionHandler = (req: OxyFunctionRequest, ctx: OxyFunctionContext) => Promise<Response> | Response;
718
+ //#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.mts.map