/** * 错误归一(骨架,#89098) * * CloudBase 侧存在**三套**错误体,直接把它们暴露给生成应用会让每个调用点都要 * 分别判形状: * * 1. auth(OAuth2) `{ error, error_description, error_uri, details }` * 2. PostgREST(数据) `{ message, details, hint, code }` * 3. functions / 存储 `{ code, message, requestId }` * * 这里统一成一个**判别联合**,让调用方能 `switch (error.kind)` 且被 TS 穷尽检查 —— * 这一点刻意比 Supabase 做得更严:它只给一个笼统 error 类 + 字符串 code,无法穷尽。 */ /** * 归一后的错误类别。 * * `kind` 是稳定的编程契约,`message` 只供展示 —— 不要去 match message 文本。 */ type CloudErrorKind = /** 未登录 / token 失效。TCB 返 `invalid_grant` 时应清空本地凭据(§5.3)。 */ 'unauthenticated' /** 已登录但无权限,通常是 RLS 拒绝。 */ | 'permission-denied' | 'not-found' /** 参数或请求体不合法。 */ | 'invalid-request' /** 一期未放行的端点(captcha / device code 等,§5.4)。 */ | 'unimplemented' /** 额度耗尽或环境已销毁,读写必然失败(§11.3)。 */ | 'credits-exhausted' | 'rate-limited' /** 网关或上游故障。 */ | 'backend-unavailable' /** fetch 层失败(离线、DNS、CORS、超时)。 */ | 'network' /** 无法归类 —— 保留原始信息,不要吞掉。 */ | 'unknown'; /** 归一后的错误。四个模块只抛/返回这一种形状。 */ interface CloudError { kind: CloudErrorKind; /** 面向用户的可读信息。 */ message: string; /** HTTP 状态码;网络层失败时为 0。 */ status: number; /** 上游原始错误码(PostgREST 的 `42501`、OAuth2 的 `invalid_grant` 等),便于排查。 */ code?: string; /** 上游原始错误体,**原样保留** —— 归一不等于丢信息。 */ cause?: unknown; } /** * 成功/失败信封。 * * 必须是**判别联合**(两个成员的 union),不能写成 `{ data?: T; error?: E }` —— * 后者会让 `if (error) return` 之后的 `data` 收窄失效,这是 Supabase 该设计 * 最容易被抄错的地方。 */ type CloudResult = { data: T; error: null; } | { data: null; error: CloudError; }; /** * Auth 模块类型(#89098) * * 形状参考 gotrue-js 的开发者体验(`{ data, error }` 信封、方法命名), * 但**实现自研** —— 我们返给应用的是我方**自签 session token**, * 真实 CloudBase token 只存服务端(设计文档 §5.3 / §2.2)。 */ /** * 终端用户。 * * 字段来自 `GET /auth/v1/user/me`,那是 **TCB 原始 user 对象**(服务端对该端点 * 纯透传、不做形状归一),所以这里只挑几个稳定字段映射,原始体在 `raw` 里 * 原样留着 —— 上游会加字段,写死一个封闭映射等于每次上游加字段都要改 SDK。 */ interface CloudUser { /** TCB 侧 uid(响应里的 `sub`,旧字段名 `uid`)。 */ id: string; email?: string; phone?: string; name?: string; avatarUrl?: string; provider?: string; /** 匿名登录产生的用户。匿名→实名升级后为 false(§5.4)。 */ isAnonymous: boolean; /** 上游原始响应,**原样保留**。归一不等于丢信息。 */ raw?: Record; } /** * 自签 session。 * * --------------------------------------------------------------------------- * 关于 `refreshToken` * * 它是**我方签发的不透明句柄**(`wbrt_` 前缀),不是 provider 的 refresh_token。 * provider 那个绝不进浏览器 —— 它能直连 provider 换会话,我方的 aud/azp 绑定 * 对它完全无效。 * * 句柄**必须**下发给应用:续期由客户端发起(`POST /auth/v1/token`),服务端在 * session 内部用真实 refresh_token 完成换发。不下发就没有调用方,整条续期链路 * 断掉,access token 一过期用户只能重新登录。 * * 句柄绑定 appId + Origin,且**每次成功续期都会轮换、旧句柄立即失效**。 * 唯一例外:服务端单飞锁抢不到时会沿用旧记录,此时句柄不变 —— 所以调用方 * 不能假设「续期后句柄一定变了」。 * --------------------------------------------------------------------------- */ interface CloudSession { accessToken: string; /** 我方不透明刷新句柄(`wbrt_`),见上方说明。 */ refreshToken: string; /** Unix 毫秒。由 `expires_in`(秒)加上收到响应的时刻算出。 */ expiresAt: number; user: CloudUser; } /** * 登录态变化事件。 * * 取值与 gotrue-js 的 `AuthChangeEvent` 对齐 —— 应用开发者与 AI 生成的代码 * 大概率见过那套命名,自造一套只会增加学习成本。 * * `INITIAL_SESSION` 是订阅时立即回放的一次:没有它,调用方无法区分 * 「还没初始化完」与「初始化完了但未登录」,首屏会闪一下登录页。 */ type CloudAuthEvent = 'INITIAL_SESSION' | 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | 'USER_UPDATED' | 'PASSWORD_RECOVERY'; /** 登录态订阅回调。 */ type CloudAuthStateCallback = (event: CloudAuthEvent, session: CloudSession | null) => void; /** * 凭据持久化。 * * ⚠️ 默认且**必须**是 localStorage,**禁止 cookie** —— 兄弟子域可读写共享后缀 * cookie,这是已识别的越权面(§7.1)。TCB 官方 SDK 也用 localStorage, * 沿用即正确,不要「改进」。 * * 做成接口是为了可注入(测试、非浏览器运行时),不是为了给 cookie 留口子。 */ interface CloudAuthStorage { getItem(key: string): string | null; setItem(key: string, value: string): void; removeItem(key: string): void; } /** 密码登录 / 注册凭据。 */ interface PasswordCredentials { password: string; email: string; } /** * 验证码注册凭据。 * * `email` / `phone` 二选一(与发码渠道一致);`password` 可选 —— 短信注册通常 * 不设密码。 */ interface SignUpCredentials { email?: string; phone?: string; password?: string; } /** * 验证码登录 —— 发码阶段。邮箱与手机号**二选一**,由传入字段决定投递渠道。 * * 发码由 provider 完成,我方不实现发码逻辑(§7.5.1)。 */ type OtpCredentials = { email: string; /** * 邮件里「点此登录」链接的落地地址。 * * 必须是该应用**已注册的域名**且为 https,否则服务端 400 —— 不校验时 * A 应用可以让 provider 发一封跳转到攻击者站点的我方品牌邮件。 */ emailRedirectTo?: string; phone?: never; } | { /** * 手机号,SDK 会归一化为 `+86 13800000000` 这类带区号形式。 * * 兼容裸号(`13800000000`)、无空格区号(`+8613800000000`)、国际冠码 * (`0086...`)及各种分隔符;非国内手机号(如港澳号 `+852...`)原样透传, * 不会被改坏。 */ phone: string; email?: never; emailRedirectTo?: never; }; /** * 发码结果。 * * `verificationId` 要带到验码那一步;`isExistingUser` 决定验码后走登录还是注册 * —— 这个判断由 provider 给出,SDK 不猜。 */ interface OtpVerifyCredentials { token: string; /** 注册路径可选设置密码;已存在用户忽略此项。 */ password?: string; } interface OtpChallenge { verificationId: string; isExistingUser: boolean; } /** gotrue 风格的发码 + 验码一体结果。 */ interface OtpSignInChallenge extends OtpChallenge { /** `isExistingUser` 的 gotrue 风格别名。 */ isUser: boolean; /** 输入验证码并完成登录/注册。 */ verify(credentials: OtpVerifyCredentials): Promise>; } /** * 验码阶段的入参。 * * `email` / `phone` 二选一,且必须与发码时用的那个一致 —— 上游按同一标识换会话。 */ interface VerifyOtpParams { verificationId: string; /** 用户输入的验证码。 */ token: string; /** 与发码时一致的邮箱。手机号发码时传 {@link phone} 代替。 */ email?: string; /** 与发码时一致的手机号。邮箱发码时传 {@link email} 代替。 */ phone?: string; /** 注册路径可选设置密码;已存在用户忽略此项。 */ password?: string; } /** 网页第三方登录:新应用用 wechat;google 仅存量。 */ type CloudOAuthProvider = 'google' | 'wechat'; /** * 发起 OAuth 登录的参数。 */ interface OAuthSignInOptions { provider: CloudOAuthProvider; /** * 授权完成后回到应用的哪个地址。缺省用当前页 `origin + pathname`。 * * 必须是该应用已注册域名(https),服务端精确匹配。 */ redirectTo?: string; } /** OAuth 第一步的产物:把用户送去 `url`。 */ interface OAuthRedirect { url: string; } interface PasswordResetUpdate { nonce: string; password: string; } interface PasswordResetChallenge { updateUser(update: PasswordResetUpdate): Promise>; } interface ResetPasswordWithOldCredentials { oldPassword: string; newPassword: string; } /** * WorkBuddy Cloud SDK 公共类型(#89098) * * 契约权威来源(不要在此另行发明字段): * - `services/cmd/agentserver/docs/genie-baas/genie-baas-phase1-design.md` §5 / §6 * - `packages/workbuddy-server/src/cloud-service/types.ts`(`CloudServicePublicConfig`) * * 本文件只有类型与常量,不含任何 I/O。 */ /** * 创建客户端的入参,字段与 `CloudServicePublicConfig` 下发给应用的部分一一对应。 * * ⚠️ 这里只接受公开配置。envId、CloudBase publish_key / api_key 只存服务端, * 任何响应都不下发(设计文档 §5.2),因此不要为它们加配置项。 */ interface WorkBuddyCloudOptions { /** * 数据面基址,例如 `https://app.workbuddy.link`。末尾斜杠会被归一化掉。 * * **省略即用当前页面同源,这是推荐用法** —— 数据面 `/.cloud/**` 与应用页面本就 * 同域,同源永远正确。显式传绝对地址反而有失效模式:它会被写进前端产物,应用 * 重新发布 / 追加认证域名后拿到新域名,产物里的旧地址不变,请求便打到旧域名上, * 而服务端按 Origin 精确匹配必然拒绝(现场表现为跨域预检失败,极难归因)。 * * 需要显式传的场景:本地开发页面跑在 dev server、数据面在别处;以及非浏览器 * 运行时(无 `location.origin` 可回落,此时省略会抛配置错误)。 */ endpoint?: string; /** * Google OAuth Relay 的环境中心基址,例如 * `https://test4-api.workbuddy.cn/v2/as/genie-baas/oauth`。 * * 它与 endpoint 是两条入口:endpoint 是每应用发布域名,承载 `/.cloud/**`; * OAuth Relay 走中心 APISIX,不能根据 endpoint 拼接。 * * **网页微信扫码登录必传**:`signInWithOAuth({ provider: 'wechat' })` 用它拼 * Relay 的 `/authorize`,缺失直接返回 `requires oauthRelayBaseUrl`。Google 登录 * 虽已下线,该字段并未随之停发——后端一直在 `publicConfig` 里返回它。 * * 省略时回落为空串(小程序不走 Relay,可以不传)。 */ oauthRelayBaseUrl?: string; /** * 我方签发的半公开 key,格式 `wbpk_{appId}_{随机}`。 * * 它只标识「哪个应用」、不携带权限,可以打进前端产物;安全性由服务端 Origin * 精确匹配提供(设计文档 §5.2 / §7.3)。仍不应打进日志。 */ publishableKey: string; /** * 自定义 fetch,便于测试与非标准运行时注入。缺省用全局 `fetch`。 */ fetch?: typeof globalThis.fetch; /** * 凭据存储。缺省用 localStorage(不可用时退回内存)。 * * 什么时候需要显式传: * - 测试里要一个**干净的、与当前登录态无关**的客户端(例如验证「未登录 * 时写数据会被拒」——若沿用 localStorage 就会带上现有身份,测不到) * - 非浏览器运行时(Node、小程序)里没有 localStorage * * ⚠️ **不要传 cookie 实现。** 兄弟子域可读写共享后缀 cookie,那是已识别 * 的越权面(设计文档 §7.1)。这个选项存在是为了可注入,不是为了给 cookie * 留口子。 */ storage?: CloudAuthStorage; } /** 归一化后的运行期配置,四个模块共享同一份。 */ interface CloudRuntimeConfig { /** 归一化后的 endpoint(无尾斜杠)。 */ readonly endpoint: string; /** 归一化后的中心 OAuth Relay 基址(无尾斜杠)。 */ readonly oauthRelayBaseUrl: string; readonly publishableKey: string; readonly fetch: typeof globalThis.fetch; } /** * Auth 模块(#89098) * * 数据面 `/.cloud/auth/v1/**`。 * * **四个模块里唯一自研的**。Auth 也是唯一不能纯透传的能力:登录响应里 provider * 的 token 必须被换成我方自签 session,续期在服务端完成(§5.3 / §6.0)。 * * ============================================================================ * 本模块是整个客户端的**身份唯一来源** * * database / storage 的请求身份不由它们自己管 —— client.ts 把 * `() => auth.getAccessToken()` 交给共享 fetch,于是登录一次其它模块自动带上 * 身份,登出一次其它模块自动变匿名。调用方不需要(也不应该)手动搬 token。 * * 这也意味着 `getAccessToken()` 在**每个** database/storage 请求前都会被调用、 * 且可能被高并发调用 —— 它必须便宜且幂等。过期续期的并发保护在 * session-manager.ts。 * ============================================================================ */ interface AuthModuleOptions { /** 凭据存储。缺省用 localStorage(探测失败退回内存)。 */ storage?: CloudAuthStorage; } declare class AuthModule { private readonly fetch; /** 该模块的数据面基址。 */ readonly baseUrl: string; private readonly sessions; private readonly storage; private readonly oauthRelayBaseUrl; private readonly listeners; private nextListenerId; /** * @param fetch 必须是**不带 session provider** 的 fetch。 * * 原因:本模块的 `/v1/token` 就是续期端点;若它走「取 token(必要时先续期)」 * 那条出口,续期就会调用自己 —— 死锁。CloudBase 的 OAuth2Client 为同一个 * 问题留了逃生口,注释里直接写了 "may cause deadlock during initialization"。 * 装配见 client.ts。 */ constructor(config: CloudRuntimeConfig, fetch: typeof globalThis.fetch, opts?: AuthModuleOptions); /** * 取一个当前可用的 access token,必要时先续期。无会话返回 undefined。 * * database / storage 的每个请求都会调它 —— 返回 undefined 表示匿名, * 那是合法状态不是错误(服务端按 anon 角色处理)。 */ getAccessToken(): Promise; /** 邮箱 + 密码登录。当前环境未启用手机号或匿名登录。 */ signInWithPassword(credentials: PasswordCredentials): Promise>; /** * 小程序 wx.login 的 code 换 Genie session。 * 试用与正式是两套独立小程序,必须带当前账号的 appid *(`wx.getAccountInfoSync().miniProgram.appId`)。 * 成功后 session.user.id 为 TCB custom uid:`wx:` + 微信 openid(长度需 ≤32)。 */ signInWithWechat(code: string, appid: string): Promise>; /** * 注册。 * * 上游要求先完成验证码验证,所以必须带一个 `verificationToken`(来自 * `verifyOtp`)。只有用户名+密码的注册会被上游明确拒绝。 * * 账号标识用 `email` 或 `phone`(二选一,与发码渠道一致)。 */ signUp(credentials: SignUpCredentials & { verificationToken: string; }): Promise>; /** * 发验证码(邮箱或短信,由 `credentials` 传的字段决定)。 * * 这一步**不产生会话**,只返回一个 `verificationId`。发码由上游完成, * 我方不实现发码逻辑。 * * 返回的 `isExistingUser` 决定验码之后走登录还是注册 —— 该判断由上游给出, * SDK 不猜。 * * 两条渠道的请求体差异是刻意的(runtime-auth-design §3.1): * - 邮箱:`{ email, usage: 'email' }`,与上游 `VerificationUsage.EMAIL` 的值一致,区分大小写。 * - 手机:`{ phone_number, target: 'ANY' }` —— **不带 `usage`**(普通短信登录 * 发码时省略即可,上游没有对应的短信枚举值),`target: 'ANY'` 表示新老用户 * 都允许发码。 */ sendOtp(credentials: OtpCredentials): Promise>; /** gotrue 风格的 OTP 入口:发码后由 challenge.verify 完成登录。 */ signInWithOtp(credentials: OtpCredentials): Promise>; /** * 验码并登录(或注册后登录)。 * * 两步:先用验证码换一个**一次性** `verification_token`,再用它换会话。 * 中间那个 token 不是会话 —— 拿它当 Bearer 会被拒。 * * 走登录还是注册由 `isExistingUser` 决定(`sendOtp` 的返回值)。刻意不做 * 「先试登录失败再试注册」的兜底:那会把「密码错」这类真实错误掩盖成一次 * 莫名的注册尝试。 * * 登录侧邮箱与手机号共用 `username` 字段 —— 上游 `SignInRequest.username` * 承载邮箱/手机/用户名三种形态(runtime-auth-design §3.2)。 */ verifyOtp(params: VerifyOtpParams & { isExistingUser: boolean; }): Promise>; /** * 发起第三方登录,返回该把用户送去的地址。 * * 走 Cloud Backend Service OAuth Relay。IdP 回调白名单只登记平台统一域名。 * SDK 不自己跳转:返回 url 交调用方 `location.assign`。 * * `google` 已下线,仅存量应用;新应用用 `wechat` 做网页扫码登录。 */ signInWithOAuth(options: OAuthSignInOptions): Promise>; /** 回调输入的 query 归一:接受完整 URL 或裸 query。 */ private parseCallbackQuery; /** * 完成第三方登录:读回调 URL 上的参数换会话。 * * 在回调落地页调一次即可。`code` 缺失时返回 `null` 数据而**不是**错误 —— * 落地页可能被直接访问(用户收藏了它),那不是失败。 * * @deprecated Google 登录已下线,新应用不应再调用该方法。 */ handleOAuthCallback(search?: string): Promise>; /** * 网页微信扫码回调:用 Relay 带回的 code/state 换 Genie session。 * * 仅用于 **微信扫码** 落地页。Google 存量应用必须继续调用 * `handleOAuthCallback`(custom-oauth 路径)。两者不可互换:本方法会拒绝 * `provider != "wechat"` 的回调,报错看起来像前端传错参数。 */ handleWechatWebCallback(search?: string): Promise>; /** 忘记密码:发码,验证码通过后更新密码并自动登录。 */ resetPasswordForEmail(email: string): Promise>; /** 登录后使用旧密码进行 sudo 校验并更新密码。 */ resetPasswordForOld(credentials: ResetPasswordWithOldCredentials): Promise>; /** * 读当前会话。 * * 会在临近过期时**顺手续期** —— 只读本地不续期的话,调用方拿到一个还剩 * 3 秒有效期的 token 去发请求,到服务端就已经过期了。 */ getSession(): Promise>; /** 强制续期一次,不管是否临近过期。 */ refreshSession(): Promise>; /** * 读当前用户。 * * 会发请求(`/v1/user/me`)而不是读本地 session —— 后者的 user 字段来自 * 登录那一刻,可能已过时(别处改了昵称、或匿名已升级为实名)。 */ getUser(): Promise>; /** * 登出。 * * 无论服务端返回什么都清本地:服务端侧是**无条件销毁**的(转发上游之后就 * 销毁,不看上游成败),本地留着只会让界面显示一个已经不能用的登录态。 * 断网时也必须能登出。 */ signOut(): Promise>; /** * 订阅登录态变化。返回取消订阅函数。 * * 订阅时会**立即**回放一次 `INITIAL_SESSION`:没有它,调用方无法区分 * 「还没初始化完」与「初始化完了但未登录」,首屏会闪一下登录页。 */ onAuthStateChange(callback: CloudAuthStateCallback): () => void; /** * 拼出平台 Relay 授权地址;state 由 Relay 自己签发、校验并一次性消费。 */ private startRelayOAuth; /** 用我方句柄换新会话。这是 SessionManager 注入的续期执行体。 */ private exchangeRefreshHandle; /** POST 一个换签端点并把响应解析成会话。 */ private postForSession; /** 发一次 auth 请求并归一错误。 */ private request; /** 广播事件。单个监听器抛错不能影响其它监听器与主流程。 */ private notify; } /** * Error format * * Returned by every PostgREST request that fails. When something fails, the * single most useful field is usually `hint` — Postgres often returns the * actionable fix there, not in `message`. Always log the full object (e.g. * `console.error(error)`); logging only `error.message` hides the hint. * * Read the fields in roughly this order of usefulness: * * - `hint` — actionable guidance from the database when available. For * permission-denied errors (`42501`), this is the literal SQL to fix the * problem, e.g. * `"Grant the required privileges to the current role with: GRANT SELECT ON public.users TO anon;"`. * Missing column? `hint` suggests the column you probably meant. Whenever * Postgres knows the fix, it puts it in `hint`. * - `code` — stable error code from PostgREST (e.g. `PGRST301`) or Postgres * (e.g. `42501`). Branch on this rather than on `message` text. * - `details` — extra context, often the offending value, key, or row. * - `message` — human-readable summary. Useful in UI strings; less useful * for debugging. * * {@link https://postgrest.org/en/stable/api.html?highlight=options#errors-and-http-status-codes} */ declare class PostgrestError extends Error { details: string; hint: string; code: string; /** * @example * ```ts * import PostgrestError from '@supabase/postgrest-js' * * throw new PostgrestError({ * message: 'Row level security prevented the request', * details: 'RLS denied the insert', * hint: 'Check your policies', * code: 'PGRST301', * }) * ``` */ constructor(context: { message: string; details: string; hint: string; code: string; }); toJSON(): { name: string; message: string; details: string; hint: string; code: string; }; } type Fetch = typeof fetch; type GenericRelationship = { foreignKeyName: string; columns: string[]; isOneToOne?: boolean; referencedRelation: string; referencedColumns: string[]; }; type GenericTable = { Row: Record; Insert: Record; Update: Record; Relationships: GenericRelationship[]; }; type GenericUpdatableView = { Row: Record; Insert: Record; Update: Record; Relationships: GenericRelationship[]; }; type GenericNonUpdatableView = { Row: Record; Relationships: GenericRelationship[]; }; type GenericView = GenericUpdatableView | GenericNonUpdatableView; type GenericSetofOption = { isSetofReturn?: boolean | undefined; isOneToOne?: boolean | undefined; isNotNullable?: boolean | undefined; to: string; from: string; }; type GenericFunction = { Args: Record | never; Returns: unknown; SetofOptions?: GenericSetofOption; }; type GenericSchema = { Tables: Record; Views: Record; Functions: Record; }; type ClientServerOptions = { PostgrestVersion?: string; }; type AggregateWithoutColumnFunctions = 'count'; type AggregateWithColumnFunctions = 'sum' | 'avg' | 'min' | 'max' | AggregateWithoutColumnFunctions; type AggregateFunctions = AggregateWithColumnFunctions; type Json = string | number | boolean | null | { [key: string]: Json | undefined; } | Json[]; type PostgresSQLNumberTypes = 'int2' | 'int4' | 'int8' | 'float4' | 'float8' | 'numeric'; type PostgresSQLStringTypes = 'bytea' | 'bpchar' | 'varchar' | 'date' | 'text' | 'citext' | 'time' | 'timetz' | 'timestamp' | 'timestamptz' | 'uuid' | 'vector'; type SingleValuePostgreSQLTypes = PostgresSQLNumberTypes | PostgresSQLStringTypes | 'bool' | 'json' | 'jsonb' | 'void' | 'record' | string; type ArrayPostgreSQLTypes = `_${SingleValuePostgreSQLTypes}`; type TypeScriptSingleValueTypes = T extends 'bool' ? boolean : T extends PostgresSQLNumberTypes ? number : T extends PostgresSQLStringTypes ? string : T extends 'json' | 'jsonb' ? Json : T extends 'void' ? undefined : T extends 'record' ? Record : unknown; type StripUnderscore = T extends `_${infer U}` ? U : T; type PostgreSQLTypes = SingleValuePostgreSQLTypes | ArrayPostgreSQLTypes; type TypeScriptTypes = T extends ArrayPostgreSQLTypes ? TypeScriptSingleValueTypes>>[] : TypeScriptSingleValueTypes; type UnionToIntersection$1 = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; type LastOf$1 = UnionToIntersection$1 T : never> extends () => infer R ? R : never; type Push = [...T, V]; type UnionToTuple, N = [T] extends [never] ? true : false> = N extends true ? [] : Push>, L>; type UnionToArray = UnionToTuple; type ExtractFirstProperty = T extends { [K in keyof T]: infer U; } ? U : never; type ContainsNull = null extends T ? true : false; type IsNonEmptyArray = Exclude extends readonly [unknown, ...unknown[]] ? true : false; type TablesAndViews$1 = Schema['Tables'] & Exclude; /** * Parses a query. * A query is a sequence of nodes, separated by `,`, ensuring that there is * no remaining input after all nodes have been parsed. * * Returns an array of parsed nodes, or an error. */ type ParseQuery = string extends Query ? GenericStringError : ParseNodes> extends [infer Nodes, `${infer Remainder}`] ? Nodes extends Ast.Node[] ? EatWhitespace extends '' ? SimplifyDeep : ParserError<`Unexpected input: ${Remainder}`> : ParserError<'Invalid nodes array structure'> : ParseNodes>; /** * Notes: all `Parse*` types assume that their input strings have their whitespace * removed. They return tuples of ["Return Value", "Remainder of text"] or * a `ParserError`. */ /** * Parses a sequence of nodes, separated by `,`. * * Returns a tuple of ["Parsed fields", "Remainder of text"] or an error. */ type ParseNodes = string extends Input ? GenericStringError : ParseNodesHelper; type ParseNodesHelper = ParseNode extends [infer Node, `${infer Remainder}`] ? Node extends Ast.Node ? EatWhitespace extends `,${infer Remainder}` ? ParseNodesHelper, [...Nodes, Node]> : [[...Nodes, Node], EatWhitespace] : ParserError<'Invalid node type in nodes helper'> : ParseNode; /** * Parses a node. * A node is one of the following: * - `*` * - a field, as defined above * - a renamed field, `renamed_field:field` * - a spread field, `...field` */ type ParseNode = Input extends '' ? ParserError<'Empty string'> : Input extends `*${infer Remainder}` ? [Ast.StarNode, EatWhitespace] : Input extends `...${infer Remainder}` ? ParseField> extends [infer TargetField, `${infer Remainder}`] ? TargetField extends Ast.FieldNode ? [{ type: 'spread'; target: TargetField; }, EatWhitespace] : ParserError<'Invalid target field type in spread'> : ParserError<`Unable to parse spread resource at \`${Input}\``> : ParseIdentifier extends [infer NameOrAlias, `${infer Remainder}`] ? EatWhitespace extends `::${infer _}` ? ParseField : EatWhitespace extends `:${infer Remainder}` ? ParseField> extends [infer Field, `${infer Remainder}`] ? Field extends Ast.FieldNode ? [Omit & { alias: NameOrAlias; }, EatWhitespace] : ParserError<'Invalid field type in alias parsing'> : ParserError<`Unable to parse renamed field at \`${Input}\``> : ParseField : ParserError<`Expected identifier at \`${Input}\``>; /** * Parses a field without preceding alias. * A field is one of the following: * - a top-level `count` field: https://docs.postgrest.org/en/v12/references/api/aggregate_functions.html#the-case-of-count * - a field with an embedded resource * - `field(nodes)` * - `field!hint(nodes)` * - `field!inner(nodes)` * - `field!left(nodes)` * - `field!hint!inner(nodes)` * - `field!hint!left(nodes)` * - a field without an embedded resource (see {@link ParseNonEmbeddedResourceField}) */ type ParseField = Input extends '' ? ParserError<'Empty string'> : ParseIdentifier extends [infer Name, `${infer Remainder}`] ? Name extends 'count' ? ParseCountField : Remainder extends `!inner${infer Remainder}` ? ParseEmbeddedResource> extends [ infer Children, `${infer Remainder}` ] ? Children extends Ast.Node[] ? [ { type: 'field'; name: Name; innerJoin: true; children: Children; }, Remainder ] : ParserError<'Invalid children array in inner join'> : CreateParserErrorIfRequired>, `Expected embedded resource after "!inner" at \`${Remainder}\``> : EatWhitespace extends `!left${infer Remainder}` ? ParseEmbeddedResource> extends [ infer Children, `${infer Remainder}` ] ? Children extends Ast.Node[] ? [ { type: 'field'; name: Name; children: Children; }, EatWhitespace ] : ParserError<'Invalid children array in left join'> : CreateParserErrorIfRequired>, `Expected embedded resource after "!left" at \`${EatWhitespace}\``> : EatWhitespace extends `!${infer Remainder}` ? ParseIdentifier> extends [infer Hint, `${infer Remainder}`] ? EatWhitespace extends `!inner${infer Remainder}` ? ParseEmbeddedResource> extends [ infer Children, `${infer Remainder}` ] ? Children extends Ast.Node[] ? [ { type: 'field'; name: Name; hint: Hint; innerJoin: true; children: Children; }, EatWhitespace ] : ParserError<'Invalid children array in hint inner join'> : ParseEmbeddedResource> : ParseEmbeddedResource> extends [ infer Children, `${infer Remainder}` ] ? Children extends Ast.Node[] ? [ { type: 'field'; name: Name; hint: Hint; children: Children; }, EatWhitespace ] : ParserError<'Invalid children array in hint'> : ParseEmbeddedResource> : ParserError<`Expected identifier after "!" at \`${EatWhitespace}\``> : EatWhitespace extends `(${infer _}` ? ParseEmbeddedResource> extends [ infer Children, `${infer Remainder}` ] ? Children extends Ast.Node[] ? [ { type: 'field'; name: Name; children: Children; }, EatWhitespace ] : ParserError<'Invalid children array in field'> : ParseEmbeddedResource> : ParseNonEmbeddedResourceField : ParserError<`Expected identifier at \`${Input}\``>; type ParseCountField = ParseIdentifier extends ['count', `${infer Remainder}`] ? (EatWhitespace extends `()${infer Remainder_}` ? EatWhitespace : EatWhitespace) extends `${infer Remainder}` ? Remainder extends `::${infer _}` ? ParseFieldTypeCast extends [infer CastType, `${infer Remainder}`] ? [ { type: 'field'; name: 'count'; aggregateFunction: 'count'; castType: CastType; }, Remainder ] : ParseFieldTypeCast : [{ type: 'field'; name: 'count'; aggregateFunction: 'count'; }, Remainder] : never : ParserError<`Expected "count" at \`${Input}\``>; /** * Parses an embedded resource, which is an opening `(`, followed by a sequence of * 0 or more nodes separated by `,`, then a closing `)`. * * Returns a tuple of ["Parsed fields", "Remainder of text"], an error, * or the original string input indicating that no opening `(` was found. */ type ParseEmbeddedResource = Input extends `(${infer Remainder}` ? EatWhitespace extends `)${infer Remainder}` ? [[], EatWhitespace] : ParseNodes> extends [infer Nodes, `${infer Remainder}`] ? Nodes extends Ast.Node[] ? EatWhitespace extends `)${infer Remainder}` ? [Nodes, EatWhitespace] : ParserError<`Expected ")" at \`${EatWhitespace}\``> : ParserError<'Invalid nodes array in embedded resource'> : ParseNodes> : ParserError<`Expected "(" at \`${Input}\``>; /** * Parses a field excluding embedded resources, without preceding field renaming. * This is one of the following: * - `field` * - `field.aggregate()` * - `field.aggregate()::type` * - `field::type` * - `field::type.aggregate()` * - `field::type.aggregate()::type` * - `field->json...` * - `field->json.aggregate()` * - `field->json.aggregate()::type` * - `field->json::type` * - `field->json::type.aggregate()` * - `field->json::type.aggregate()::type` */ type ParseNonEmbeddedResourceField = ParseIdentifier extends [infer Name, `${infer Remainder}`] ? (Remainder extends `->${infer PathAndRest}` ? ParseJsonAccessor extends [ infer PropertyName, infer PropertyType, `${infer Remainder}` ] ? [ { type: 'field'; name: Name; alias: PropertyName; castType: PropertyType; jsonPath: JsonPathToAccessor; }, Remainder ] : ParseJsonAccessor : [{ type: 'field'; name: Name; }, Remainder]) extends infer Parsed ? Parsed extends [infer Field, `${infer Remainder}`] ? (Remainder extends `::${infer _}` ? ParseFieldTypeCast extends [infer CastType, `${infer Remainder}`] ? [Omit & { castType: CastType; }, Remainder] : ParseFieldTypeCast : [Field, Remainder]) extends infer Parsed ? Parsed extends [infer Field, `${infer Remainder}`] ? Remainder extends `.${infer _}` ? ParseFieldAggregation extends [ infer AggregateFunction, `${infer Remainder}` ] ? Remainder extends `::${infer _}` ? ParseFieldTypeCast extends [infer CastType, `${infer Remainder}`] ? [ Omit & { aggregateFunction: AggregateFunction; castType: CastType; }, Remainder ] : ParseFieldTypeCast : [Field & { aggregateFunction: AggregateFunction; }, Remainder] : ParseFieldAggregation : [Field, Remainder] : Parsed : never : Parsed : never : ParserError<`Expected identifier at \`${Input}\``>; /** * Parses a JSON property accessor of the shape `->a->b->c`. The last accessor in * the series may convert to text by using the ->> operator instead of ->. * * Returns a tuple of ["Last property name", "Last property type", "Remainder of text"] */ type ParseJsonAccessor = Input extends `->${infer Remainder}` ? Remainder extends `>${infer Remainder}` ? ParseIdentifier extends [infer Name, `${infer Remainder}`] ? [Name, 'text', EatWhitespace] : ParserError<'Expected property name after `->>`'> : ParseIdentifier extends [infer Name, `${infer Remainder}`] ? ParseJsonAccessor extends [ infer PropertyName, infer PropertyType, `${infer Remainder}` ] ? [PropertyName, PropertyType, EatWhitespace] : [Name, 'json', EatWhitespace] : ParserError<'Expected property name after `->`'> : ParserError<'Expected ->'>; /** * Parses a field typecast (`::type`), returning a tuple of ["Type", "Remainder of text"]. */ type ParseFieldTypeCast = EatWhitespace extends `::${infer Remainder}` ? ParseIdentifier> extends [`${infer CastType}`, `${infer Remainder}`] ? [CastType, EatWhitespace] : ParserError<`Invalid type for \`::\` operator at \`${Remainder}\``> : ParserError<'Expected ::'>; /** * Parses a field aggregation (`.max()`), returning a tuple of ["Aggregate function", "Remainder of text"] */ type ParseFieldAggregation = EatWhitespace extends `.${infer Remainder}` ? ParseIdentifier> extends [ `${infer FunctionName}`, `${infer Remainder}` ] ? FunctionName extends Token.AggregateFunction ? EatWhitespace extends `()${infer Remainder}` ? [FunctionName, EatWhitespace] : ParserError<`Expected \`()\` after \`.\` operator \`${FunctionName}\``> : ParserError<`Invalid type for \`.\` operator \`${FunctionName}\``> : ParserError<`Invalid type for \`.\` operator at \`${Remainder}\``> : ParserError<'Expected .'>; /** * Parses a (possibly double-quoted) identifier. * Identifiers are sequences of 1 or more letters. */ type ParseIdentifier = ParseLetters extends [infer Name, `${infer Remainder}`] ? [Name, EatWhitespace] : ParseQuotedLetters extends [infer Name, `${infer Remainder}`] ? [Name, EatWhitespace] : ParserError<`No (possibly double-quoted) identifier at \`${Input}\``>; /** * Parse a consecutive sequence of 1 or more letter, where letters are `[0-9a-zA-Z_]`. */ type ParseLetters = string extends Input ? GenericStringError : ParseLettersHelper extends [`${infer Letters}`, `${infer Remainder}`] ? Letters extends '' ? ParserError<`Expected letter at \`${Input}\``> : [Letters, Remainder] : ParseLettersHelper; type ParseLettersHelper = string extends Input ? GenericStringError : Input extends `${infer L}${infer Remainder}` ? L extends Token.Letter ? ParseLettersHelper : [Acc, Input] : [Acc, '']; /** * Parse a consecutive sequence of 1 or more double-quoted letters, * where letters are `[^"]`. */ type ParseQuotedLetters = string extends Input ? GenericStringError : Input extends `"${infer Remainder}` ? ParseQuotedLettersHelper extends [`${infer Letters}`, `${infer Remainder}`] ? Letters extends '' ? ParserError<`Expected string at \`${Remainder}\``> : [Letters, Remainder] : ParseQuotedLettersHelper : ParserError<`Not a double-quoted string at \`${Input}\``>; type ParseQuotedLettersHelper = string extends Input ? GenericStringError : Input extends `${infer L}${infer Remainder}` ? L extends '"' ? [Acc, Remainder] : ParseQuotedLettersHelper : ParserError<`Missing closing double-quote in \`"${Acc}${Input}\``>; /** * Trims whitespace from the left of the input. */ type EatWhitespace = string extends Input ? GenericStringError : Input extends `${Token.Whitespace}${infer Remainder}` ? EatWhitespace : Input; /** * Creates a new {@link ParserError} if the given input is not already a parser error. */ type CreateParserErrorIfRequired = Input extends ParserError ? Input : ParserError; /** * Parser errors. */ type ParserError = { error: true; } & Message; type GenericStringError = ParserError<'Received a generic string'>; declare namespace Ast { type Node = FieldNode | StarNode | SpreadNode; type FieldNode = { type: 'field'; name: string; alias?: string; hint?: string; innerJoin?: true; castType?: string; jsonPath?: string; aggregateFunction?: Token.AggregateFunction; children?: Node[]; }; type StarNode = { type: 'star'; }; type SpreadNode = { type: 'spread'; target: FieldNode & { children: Node[]; }; }; } declare namespace Token { export type Whitespace = ' ' | '\n' | '\t'; type LowerAlphabet = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'; type Alphabet = LowerAlphabet | Uppercase; type Digit = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0'; export type Letter = Alphabet | Digit | '_'; export type AggregateFunction = 'count' | 'sum' | 'avg' | 'min' | 'max'; export { }; } type IsAny$1 = 0 extends 1 & T ? true : false; type SelectQueryError = { error: true; } & Message; type DeduplicateRelationships = T extends readonly [ infer First, ...infer Rest ] ? First extends Rest[number] ? DeduplicateRelationships : [First, ...DeduplicateRelationships] : T; type GetFieldNodeResultName = Field['alias'] extends string ? Field['alias'] : Field['aggregateFunction'] extends AggregateFunctions ? Field['aggregateFunction'] : Field['name']; type FilterRelationNodes = UnionToArray<{ [K in keyof Nodes]: Nodes[K] extends Ast.SpreadNode ? Nodes[K]['target'] : Nodes[K] extends Ast.FieldNode ? IsNonEmptyArray extends true ? Nodes[K] : never : never; }[number]>; type ResolveRelationships = UnionToArray<{ [K in keyof Nodes]: Nodes[K] extends Ast.FieldNode ? ResolveRelationship extends infer Relation ? Relation extends { relation: { referencedRelation: string; foreignKeyName: string; match: string; }; from: string; } ? { referencedTable: Relation['relation']['referencedRelation']; fkName: Relation['relation']['foreignKeyName']; from: Relation['from']; match: Relation['relation']['match']; fieldName: GetFieldNodeResultName; } : Relation : never : never; }>[0]; /** * Checks if a relation is implicitly referenced twice, requiring disambiguation */ type IsDoubleReference = T extends { referencedTable: infer RT; fieldName: infer FN; match: infer M; } ? M extends 'col' | 'refrel' ? U extends { referencedTable: RT; fieldName: FN; match: M; } ? true : false : false : false; /** * Compares one element with all other elements in the array to find duplicates */ type CheckDuplicates = Arr extends [infer Head, ...infer Tail] ? IsDoubleReference extends true ? Head | CheckDuplicates : CheckDuplicates : never; /** * Iterates over the elements of the array to find duplicates */ type FindDuplicatesWithinDeduplicated = Arr extends [infer Head, ...infer Tail] ? CheckDuplicates | FindDuplicatesWithinDeduplicated : never; type FindDuplicates = FindDuplicatesWithinDeduplicated>; type CheckDuplicateEmbededReference = FilterRelationNodes extends infer RelationsNodes ? RelationsNodes extends Ast.FieldNode[] ? ResolveRelationships extends infer ResolvedRels ? ResolvedRels extends unknown[] ? FindDuplicates extends infer Duplicates ? Duplicates extends never ? false : Duplicates extends { fieldName: infer FieldName; } ? FieldName extends string ? { [K in FieldName]: SelectQueryError<`table "${RelationName}" specified more than once use hinting for desambiguation`>; } : false : false : false : false : false : false : false; /** * Returns a boolean representing whether there is a foreign key referencing * a given relation. */ type HasFKeyToFRel = Relationships extends [infer R] ? R extends { referencedRelation: FRelName; } ? true : false : Relationships extends [infer R, ...infer Rest] ? HasFKeyToFRel extends true ? true : HasFKeyToFRel : false; /** * Checks if there is more than one relation to a given foreign relation name in the Relationships. */ type HasMultipleFKeysToFRelDeduplicated = Relationships extends [ infer R, ...infer Rest ] ? R extends { referencedRelation: FRelName; } ? HasFKeyToFRel extends true ? true : HasMultipleFKeysToFRelDeduplicated : HasMultipleFKeysToFRelDeduplicated : false; type HasMultipleFKeysToFRel = HasMultipleFKeysToFRelDeduplicated>; type CheckRelationshipError & string, FoundRelation> = FoundRelation extends SelectQueryError ? FoundRelation : FoundRelation extends { relation: { referencedRelation: infer RelatedRelationName; name: string; }; direction: 'reverse'; } ? RelatedRelationName extends string ? HasMultipleFKeysToFRel extends true ? FoundRelation extends { relation: { match: 'col'; }; } ? FoundRelation : SelectQueryError<`Could not embed because more than one relationship was found for '${RelatedRelationName}' and '${CurrentTableOrView}' you need to hint the column with ${RelatedRelationName}! ?`> : FoundRelation : never : FoundRelation extends { relation: { referencedRelation: infer RelatedRelationName; name: string; }; direction: 'forward'; from: infer From; } ? RelatedRelationName extends string ? From extends keyof TablesAndViews$1 & string ? HasMultipleFKeysToFRel[From]['Relationships']> extends true ? SelectQueryError<`Could not embed because more than one relationship was found for '${From}' and '${RelatedRelationName}' you need to hint the column with ${From}! ?`> : FoundRelation : never : never : FoundRelation; /** * Resolves relationships for embedded resources and retrieves the referenced Table */ type ResolveRelationship & string> = ResolveReverseRelationship extends infer ReverseRelationship ? ReverseRelationship extends false ? CheckRelationshipError> : CheckRelationshipError : never; /** * Resolves reverse relationships (from children to parent) */ type ResolveReverseRelationship & string> = FindFieldMatchingRelationships extends infer FoundRelation ? FoundRelation extends never ? false : FoundRelation extends { referencedRelation: infer RelatedRelationName; } ? RelatedRelationName extends string ? RelatedRelationName extends keyof TablesAndViews$1 ? FoundRelation extends { hint: string; } | { match: 'col'; } ? { referencedTable: TablesAndViews$1[RelatedRelationName]; relation: FoundRelation; direction: 'reverse'; from: CurrentTableOrView; } : HasMultipleFKeysToFRel extends true ? SelectQueryError<`Could not embed because more than one relationship was found for '${RelatedRelationName}' and '${CurrentTableOrView}' you need to hint the column with ${RelatedRelationName}! ?`> : { referencedTable: TablesAndViews$1[RelatedRelationName]; relation: FoundRelation; direction: 'reverse'; from: CurrentTableOrView; } : SelectQueryError<`Relation '${RelatedRelationName}' not found in schema.`> : false : false : false; type FindMatchingTableRelationships = Relationships extends [infer R, ...infer Rest] ? Rest extends GenericRelationship[] ? R extends { referencedRelation: infer ReferencedRelation; } ? ReferencedRelation extends keyof Schema['Tables'] ? R extends { foreignKeyName: value; } ? R & { match: 'fkname'; } : R extends { referencedRelation: value; } ? R & { match: 'refrel'; } : R extends { columns: [value]; } ? R & { match: 'col'; } : FindMatchingTableRelationships : FindMatchingTableRelationships : false : false : false; type FindMatchingViewRelationships = Relationships extends [infer R, ...infer Rest] ? Rest extends GenericRelationship[] ? R extends { referencedRelation: infer ReferencedRelation; } ? ReferencedRelation extends keyof Schema['Views'] ? R extends { foreignKeyName: value; } ? R & { match: 'fkname'; } : R extends { referencedRelation: value; } ? R & { match: 'refrel'; } : R extends { columns: [value]; } ? R & { match: 'col'; } : FindMatchingViewRelationships : FindMatchingViewRelationships : false : false : false; type FindMatchingHintTableRelationships = Relationships extends [infer R, ...infer Rest] ? Rest extends GenericRelationship[] ? R extends { referencedRelation: infer ReferencedRelation; } ? ReferencedRelation extends name ? R extends { foreignKeyName: hint; } ? R & { match: 'fkname'; } : R extends { referencedRelation: hint; } ? R & { match: 'refrel'; } : R extends { columns: [hint]; } ? R & { match: 'col'; } : FindMatchingHintTableRelationships : FindMatchingHintTableRelationships : false : false : false; type FindMatchingHintViewRelationships = Relationships extends [infer R, ...infer Rest] ? Rest extends GenericRelationship[] ? R extends { referencedRelation: infer ReferencedRelation; } ? ReferencedRelation extends name ? R extends { foreignKeyName: hint; } ? R & { match: 'fkname'; } : R extends { referencedRelation: hint; } ? R & { match: 'refrel'; } : R extends { columns: [hint]; } ? R & { match: 'col'; } : FindMatchingHintViewRelationships : FindMatchingHintViewRelationships : false : false : false; type IsColumnsNullable, Columns extends (keyof Table['Row'])[]> = Columns extends [infer Column, ...infer Rest] ? Column extends keyof Table['Row'] ? ContainsNull extends true ? true : IsColumnsNullable : false : false; type IsRelationNullable
= IsColumnsNullable; type TableForwardRelationships = TName extends keyof TablesAndViews$1 ? UnionToArray>> extends infer R ? R extends (GenericRelationship & { from: keyof TablesAndViews$1; })[] ? R : [] : [] : []; type RecursivelyFindRelationships> = Keys extends infer K ? K extends keyof TablesAndViews$1 ? FilterRelationships[K]['Relationships'], TName, K> extends never ? RecursivelyFindRelationships> : FilterRelationships[K]['Relationships'], TName, K> | RecursivelyFindRelationships> : false : false; type FilterRelationships = R extends readonly (infer Rel)[] ? Rel extends { referencedRelation: TName; } ? Rel & { from: From; } : never : never; type ResolveForwardRelationship & string> = FindFieldMatchingRelationships[Field['name']]['Relationships'], Ast.FieldNode & { name: CurrentTableOrView; hint: Field['hint']; }> extends infer FoundByName ? FoundByName extends GenericRelationship ? { referencedTable: TablesAndViews$1[Field['name']]; relation: FoundByName; direction: 'forward'; from: Field['name']; type: 'found-by-name'; } : FindFieldMatchingRelationships, Field> extends infer FoundByMatch ? FoundByMatch extends GenericRelationship & { from: keyof TablesAndViews$1; } ? { referencedTable: TablesAndViews$1[FoundByMatch['from']]; relation: FoundByMatch; direction: 'forward'; from: CurrentTableOrView; type: 'found-by-match'; } : FindJoinTableRelationship extends infer FoundByJoinTable ? FoundByJoinTable extends GenericRelationship ? { referencedTable: TablesAndViews$1[FoundByJoinTable['referencedRelation']]; relation: FoundByJoinTable & { match: 'refrel'; }; direction: 'forward'; from: CurrentTableOrView; type: 'found-by-join-table'; } : ResolveEmbededFunctionJoinTableRelationship extends infer FoundEmbededFunctionJoinTableRelation ? FoundEmbededFunctionJoinTableRelation extends GenericFunction ? FoundEmbededFunctionJoinTableRelation['SetofOptions'] extends GenericSetofOption ? FoundEmbededFunctionJoinTableRelation['SetofOptions']['to'] extends '' ? { referencedTable: { Row: Record; Relationships: []; }; relation: { foreignKeyName: `${Field['name']}_${CurrentTableOrView}_scalar_forward`; columns: []; isOneToOne: false; referencedColumns: []; referencedRelation: ''; } & { match: 'func'; isNotNullable: FoundEmbededFunctionJoinTableRelation['SetofOptions']['isNotNullable'] extends true ? true : FoundEmbededFunctionJoinTableRelation['SetofOptions']['isSetofReturn'] extends true ? false : true; isSetofReturn: FoundEmbededFunctionJoinTableRelation['SetofOptions']['isSetofReturn']; }; scalarType: FoundEmbededFunctionJoinTableRelation['Returns']; direction: 'forward'; from: CurrentTableOrView; type: 'found-by-embeded-scalar-function'; } : { referencedTable: TablesAndViews$1[FoundEmbededFunctionJoinTableRelation['SetofOptions']['to']]; relation: { foreignKeyName: `${Field['name']}_${CurrentTableOrView}_${FoundEmbededFunctionJoinTableRelation['SetofOptions']['to']}_forward`; columns: []; isOneToOne: FoundEmbededFunctionJoinTableRelation['SetofOptions']['isOneToOne'] extends true ? true : false; referencedColumns: []; referencedRelation: FoundEmbededFunctionJoinTableRelation['SetofOptions']['to']; } & { match: 'func'; isNotNullable: FoundEmbededFunctionJoinTableRelation['SetofOptions']['isNotNullable'] extends true ? true : FoundEmbededFunctionJoinTableRelation['SetofOptions']['isSetofReturn'] extends true ? false : true; isSetofReturn: FoundEmbededFunctionJoinTableRelation['SetofOptions']['isSetofReturn']; }; direction: 'forward'; from: CurrentTableOrView; type: 'found-by-embeded-function'; } : SelectQueryError<`could not find the relation between ${CurrentTableOrView} and ${Field['name']}`> : SelectQueryError<`could not find the relation between ${CurrentTableOrView} and ${Field['name']}`> : SelectQueryError<`could not find the relation between ${CurrentTableOrView} and ${Field['name']}`> : SelectQueryError<`could not find the relation between ${CurrentTableOrView} and ${Field['name']}`> : SelectQueryError<`could not find the relation between ${CurrentTableOrView} and ${Field['name']}`> : SelectQueryError<`could not find the relation between ${CurrentTableOrView} and ${Field['name']}`>; /** * Given a CurrentTableOrView, finds all join tables to this relation. * For example, if products and categories are linked via product_categories table: * * @example Find join table relationship * Given: * - CurrentTableView = 'products' * - FieldName = "categories" * * It should return this relationship from product_categories: * { * foreignKeyName: "product_categories_category_id_fkey", * columns: ["category_id"], * isOneToOne: false, * referencedRelation: "categories", * referencedColumns: ["id"] * } */ type ResolveJoinTableRelationship & string, FieldName extends string> = { [TableName in keyof TablesAndViews$1]: DeduplicateRelationships[TableName]['Relationships']> extends readonly (infer Rel)[] ? Rel extends { referencedRelation: CurrentTableOrView; } ? DeduplicateRelationships[TableName]['Relationships']> extends readonly (infer OtherRel)[] ? OtherRel extends { referencedRelation: FieldName; } ? OtherRel : never : never : never : never; }[keyof TablesAndViews$1]; type ResolveEmbededFunctionJoinTableRelationship & string, FieldName extends string> = FindMatchingFunctionBySetofFrom extends infer Fn ? Fn extends GenericFunction ? Fn : false : false; type FindJoinTableRelationship & string, FieldName extends string> = ResolveJoinTableRelationship extends infer Result ? [Result] extends [never] ? false : Result : never; /** * Finds a matching relationship based on the FieldNode's name and optional hint. */ type FindFieldMatchingRelationships = Field extends { hint: string; } ? FindMatchingHintTableRelationships extends GenericRelationship ? FindMatchingHintTableRelationships & { branch: 'found-in-table-via-hint'; hint: Field['hint']; } : FindMatchingHintViewRelationships extends GenericRelationship ? FindMatchingHintViewRelationships & { branch: 'found-in-view-via-hint'; hint: Field['hint']; } : SelectQueryError<'Failed to find matching relation via hint'> : FindMatchingTableRelationships extends GenericRelationship ? FindMatchingTableRelationships & { branch: 'found-in-table-via-name'; name: Field['name']; } : FindMatchingViewRelationships extends GenericRelationship ? FindMatchingViewRelationships & { branch: 'found-in-view-via-name'; name: Field['name']; } : SelectQueryError<'Failed to find matching relation via name'>; type JsonPathToAccessor = Path extends `${infer P1}->${infer P2}` ? P2 extends `>${infer Rest}` ? JsonPathToAccessor<`${P1}.${Rest}`> : P2 extends string ? JsonPathToAccessor<`${P1}.${P2}`> : Path : Path extends `>${infer Rest}` ? JsonPathToAccessor : Path extends `${infer P1}::${infer _}` ? JsonPathToAccessor : Path extends `${infer P1}${')' | ','}${infer _}` ? P1 : Path; type JsonPathToType = Path extends '' ? T : ContainsNull extends true ? JsonPathToType, Path> : Path extends `${infer Key}.${infer Rest}` ? Key extends keyof T ? JsonPathToType : never : Path extends keyof T ? T[Path] : never; type IsStringUnion = string extends T ? false : T extends string ? [T] extends [never] ? false : true : false; type MatchingFunctionBySetofFrom = Fn['SetofOptions'] extends GenericSetofOption ? TableName extends Fn['SetofOptions']['from'] ? Fn : never : false; type FindMatchingFunctionBySetofFrom = FnUnion extends infer Fn extends GenericFunction ? MatchingFunctionBySetofFrom : false; type ComputedField, FieldName extends keyof TablesAndViews$1[RelationName]['Row']> = FieldName extends keyof Schema['Functions'] ? [Schema['Functions'][FieldName]['Args']] extends [never] ? never : Schema['Functions'][FieldName] extends { Args: { '': TablesAndViews$1[RelationName]['Row']; }; Returns: any; } ? FieldName : never : never; type GetComputedFields> = { [K in keyof TablesAndViews$1[RelationName]['Row']]: ComputedField; }[keyof TablesAndViews$1[RelationName]['Row']]; /** * Response format * * {@link https://github.com/supabase/supabase-js/issues/32} */ interface PostgrestResponseBase { /** HTTP status code of the response. */ status: number; /** * HTTP reason phrase of the response, e.g. `"OK"`. * * May be an empty string: HTTP/2 and HTTP/3 don't carry reason phrases, so * responses over those protocols (e.g. browser requests to Supabase) have no * `statusText`. Rely on `status` instead of this value. */ statusText: string; } interface PostgrestResponseSuccess extends PostgrestResponseBase { success: true; error: null; data: T; count: number | null; } interface PostgrestResponseFailure extends PostgrestResponseBase { success: false; error: PostgrestError; data: null; count: null; } type PostgrestSingleResponse = PostgrestResponseSuccess | PostgrestResponseFailure; type Prettify = { [K in keyof T]: T[K]; } & {}; type RejectExcessProperties = Row & { [K in Exclude]: never; }; type SimplifyDeep = ConditionalSimplifyDeep | Map, object>; type ConditionalSimplifyDeep = Type extends ExcludeType ? Type : Type extends IncludeType ? { [TypeKey in keyof Type]: ConditionalSimplifyDeep; } : Type; type NonRecursiveType = BuiltIns | Function | (new (...arguments_: any[]) => unknown); type BuiltIns = Primitive | void | Date | RegExp; type Primitive = null | undefined | string | number | boolean | symbol | bigint; type IsValidResultOverride = Result extends any[] ? NewResult extends any[] ? true : ErrorResult : NewResult extends any[] ? ErrorNewResult : true; /** * Utility type to check if array types match between Result and NewResult. * Returns either the valid NewResult type or an error message type. */ type CheckMatchingArrayTypes = Result extends SelectQueryError ? NewResult : IsValidResultOverride> or .returns> (deprecated) for array results or .single() to convert the result to a single object'; }, { Error: 'Type mismatch: Cannot cast single object to array type. Remove Array wrapper from return type or make sure you are not using .single() up in the calling chain'; }> extends infer ValidationResult ? ValidationResult extends true ? ContainsNull extends true ? NewResult | null : NewResult : ValidationResult : never; type Simplify = T extends object ? { [K in keyof T]: T[K]; } : T; type ExplicitKeys = { [K in keyof T]: string extends K ? never : K; }[keyof T]; type MergeExplicit = { [K in ExplicitKeys | ExplicitKeys]: K extends keyof New ? K extends keyof Row ? Row[K] extends SelectQueryError ? New[K] : New[K] extends any[] ? Row[K] extends any[] ? Array, NonNullable>>> : New[K] : IsPlainObject> extends true ? IsPlainObject> extends true ? ContainsNull extends true ? // If the override wants to preserve optionality Simplify, NonNullable>> | null : Simplify>> : New[K] : New[K] : New[K] : K extends keyof Row ? Row[K] : never; }; type MergeDeep = Simplify & (string extends keyof Row ? { [K: string]: Row[string]; } : {})>; type IsPlainObject = T extends any[] ? false : T extends object ? true : false; type MergePartialResult = Options extends { merge: true; } ? Result extends any[] ? NewResult extends any[] ? Array>> : never : Simplify> : NewResult; declare abstract class PostgrestBuilder implements PromiseLike : PostgrestSingleResponse> { protected method: 'GET' | 'HEAD' | 'POST' | 'PATCH' | 'DELETE'; protected url: URL; protected headers: Headers; protected schema?: string; protected body?: unknown; protected shouldThrowOnError: boolean; protected signal?: AbortSignal; protected fetch: Fetch; protected isMaybeSingle: boolean; protected shouldStripNulls: boolean; protected urlLengthLimit: number; protected retryEnabled: boolean; /** * Creates a builder configured for a specific PostgREST request. * * @example Using supabase-js (recommended) * ```ts * import { createClient } from '@supabase/supabase-js' * * const supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key') * const { data, error } = await supabase.from('users').select('*') * ``` * * @category Database * * @example Standalone import for bundle-sensitive environments * ```ts * import { PostgrestQueryBuilder } from '@supabase/postgrest-js' * * const builder = new PostgrestQueryBuilder( * new URL('https://xyzcompany.supabase.co/rest/v1/users'), * { headers: new Headers({ apikey: 'your-publishable-key' }) } * ) * ``` */ constructor(builder: { method: 'GET' | 'HEAD' | 'POST' | 'PATCH' | 'DELETE'; url: URL; headers: HeadersInit; schema?: string; body?: unknown; shouldThrowOnError?: boolean; signal?: AbortSignal; fetch?: Fetch; isMaybeSingle?: boolean; shouldStripNulls?: boolean; urlLengthLimit?: number; retry?: boolean; }); /** * If there's an error with the query, throwOnError will reject the promise by * throwing the error instead of returning it as part of a successful response. * * {@link https://github.com/supabase/supabase-js/issues/92} * * @category Database * @subcategory Using modifiers */ throwOnError(): PostgrestBuilder; /** * Strip null values from the response data. Properties with `null` values * will be omitted from the returned JSON objects. * * Requires PostgREST 11.2.0+. * * {@link https://docs.postgrest.org/en/stable/references/api/resource_representation.html#stripped-nulls} * * @category Database * @subcategory Using modifiers * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select() * .stripNulls() * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text, bio text); * * insert into * characters (id, name, bio) * values * (1, 'Luke', null), * (2, 'Leia', 'Princess of Alderaan'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "id": 1, * "name": "Luke" * }, * { * "id": 2, * "name": "Leia", * "bio": "Princess of Alderaan" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ stripNulls(): this; /** * Set an HTTP header on this single PostgREST request, overriding any header * with the same name set on the client. * * This is an advanced escape hatch for one-off needs (passing a custom * `Authorization` for a single query, attaching a tracing header, etc.). * Most callers do not need it: configure client-wide headers via the * `headers` option when constructing the client, and authentication via * Supabase Auth. * * @param name - HTTP header name * @param value - HTTP header value * * @category Database * @subcategory Using modifiers */ setHeader(name: string, value: string): this; /** * @category Database * @subcategory Using modifiers * * Configure retry behavior for this request. * * By default, retries are enabled for idempotent requests (GET, HEAD, OPTIONS) * that fail with network errors or specific HTTP status codes (503, 520). * Retries use exponential backoff (1s, 2s, 4s) with a maximum of 3 attempts. * * @param enabled - Whether to enable retries for this request * * @example * ```ts * // Disable retries for a specific query * const { data, error } = await supabase * .from('users') * .select() * .retry(false) * ``` */ retry(enabled: boolean): this; then : PostgrestSingleResponse, TResult2 = never>(onfulfilled?: ((value: ThrowOnError extends true ? PostgrestResponseSuccess : PostgrestSingleResponse) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): PromiseLike; /** * Process a fetch response and return the standardized postgrest response. */ private processResponse; /** * Override the type of the returned `data`. * * @typeParam NewResult - The new result type to override with * @deprecated Use overrideTypes() method at the end of your call chain instead * * @category Database * @subcategory Using modifiers */ returns(): PostgrestBuilder, ThrowOnError>; /** * Override the type of the returned `data` field in the response. * * @typeParam NewResult - The new type to cast the response data to * @typeParam Options - Optional type configuration (defaults to { merge: true }) * @typeParam Options.merge - When true, merges the new type with existing return type. When false, replaces the existing types entirely (defaults to true) * @example * ```typescript * // Merge with existing types (default behavior) * const query = supabase * .from('users') * .select() * .overrideTypes<{ custom_field: string }>() * * // Replace existing types completely * const replaceQuery = supabase * .from('users') * .select() * .overrideTypes<{ id: number; name: string }, { merge: false }>() * ``` * @returns A PostgrestBuilder instance with the new type * * @category Database * @subcategory Using modifiers * * @example Complete Override type of successful response * ```ts * const { data } = await supabase * .from('countries') * .select() * .overrideTypes, { merge: false }>() * ``` * * @exampleResponse Complete Override type of successful response * ```ts * let x: typeof data // MyType[] * ``` * * @example Complete Override type of object response * ```ts * const { data } = await supabase * .from('countries') * .select() * .maybeSingle() * .overrideTypes() * ``` * * @exampleResponse Complete Override type of object response * ```ts * let x: typeof data // MyType | null * ``` * * @example Partial Override type of successful response * ```ts * const { data } = await supabase * .from('countries') * .select() * .overrideTypes>() * ``` * * @exampleResponse Partial Override type of successful response * ```ts * let x: typeof data // Array * ``` * * @example Partial Override type of object response * ```ts * const { data } = await supabase * .from('countries') * .select() * .maybeSingle() * .overrideTypes<{ status: "A" | "B" }>() * ``` * * @exampleResponse Partial Override type of object response * ```ts * let x: typeof data // CountryRowProperties & { status: "A" | "B" } | null * ``` * * @example Merge vs replace existing types * ```typescript * // Merge with existing types (default behavior) * const query = supabase * .from('users') * .select() * .overrideTypes<{ custom_field: string }>() * * // Replace existing types completely * const replaceQuery = supabase * .from('users') * .select() * .overrideTypes<{ id: number; name: string }, { merge: false }>() * ``` */ overrideTypes(): PostgrestBuilder extends true ? ContainsNull extends true ? MergePartialResult, Options> | null : MergePartialResult : CheckMatchingArrayTypes, ThrowOnError>; } type IsPostgrest13 = PostgrestVersion extends `13${string}` ? true : false; type IsPostgrest14 = PostgrestVersion extends `14${string}` ? true : false; type IsPostgrestVersionGreaterThan12 = IsPostgrest13 extends true ? true : IsPostgrest14 extends true ? true : false; type MaxAffectedEnabled = IsPostgrestVersionGreaterThan12 extends true ? true : false; type SpreadOnManyEnabled = IsPostgrestVersionGreaterThan12 extends true ? true : false; /** * Main entry point for constructing the result type of a PostgREST query. * * @param Schema - Database schema. * @param Row - The type of a row in the current table. * @param RelationName - The name of the current table or view. * @param Relationships - Relationships of the current table. * @param Query - The select query string literal to parse. */ type GetResult, RelationName, Relationships, Query extends string, ClientOptions extends ClientServerOptions> = IsAny$1 extends true ? ParseQuery extends infer ParsedQuery ? ParsedQuery extends Ast.Node[] ? RelationName extends string ? ProcessNodesWithoutSchema : any : ParsedQuery : any : Relationships extends null ? ParseQuery extends infer ParsedQuery ? ParsedQuery extends Ast.Node[] ? RPCCallNodes : ParsedQuery : Row : ParseQuery extends infer ParsedQuery ? ParsedQuery extends Ast.Node[] ? RelationName extends string ? Relationships extends GenericRelationship[] ? ProcessNodes : SelectQueryError<'Invalid Relationships cannot infer result type'> : SelectQueryError<'Invalid RelationName cannot infer result type'> : ParsedQuery : never; type ProcessSimpleFieldWithoutSchema = Field['aggregateFunction'] extends AggregateFunctions ? { [K in GetFieldNodeResultName]: Field['castType'] extends PostgreSQLTypes ? TypeScriptTypes : number; } : { [K in GetFieldNodeResultName]: Field['castType'] extends PostgreSQLTypes ? TypeScriptTypes : any; }; type ProcessFieldNodeWithoutSchema = IsNonEmptyArray extends true ? { [K in GetFieldNodeResultName]: Node['children'] extends Ast.Node[] ? ProcessNodesWithoutSchema[] : ProcessSimpleFieldWithoutSchema; } : ProcessSimpleFieldWithoutSchema; /** * Processes a single Node without schema and returns the resulting TypeScript type. */ type ProcessNodeWithoutSchema = Node extends Ast.StarNode ? any : Node extends Ast.SpreadNode ? Node['target']['children'] extends Ast.StarNode[] ? any : Node['target']['children'] extends Ast.FieldNode[] ? { [P in Node['target']['children'][number] as GetFieldNodeResultName

]: P['castType'] extends PostgreSQLTypes ? TypeScriptTypes : any; } : any : Node extends Ast.FieldNode ? ProcessFieldNodeWithoutSchema : any; /** * Processes nodes when Schema is any, providing basic type inference */ type ProcessNodesWithoutSchema = {}> = Nodes extends [infer FirstNode, ...infer RestNodes] ? FirstNode extends Ast.Node ? RestNodes extends Ast.Node[] ? ProcessNodeWithoutSchema extends infer FieldResult ? FieldResult extends Record ? ProcessNodesWithoutSchema : FieldResult : any : any : any : Prettify; /** * Processes a single Node from a select chained after a rpc call * * @param Row - The type of a row in the current table. * @param RelationName - The name of the current rpc function * @param NodeType - The Node to process. */ type ProcessRPCNode, RelationName extends string, NodeType extends Ast.Node> = NodeType['type'] extends Ast.StarNode['type'] ? Row : NodeType['type'] extends Ast.FieldNode['type'] ? ProcessSimpleField> : SelectQueryError<'RPC Unsupported node type.'>; /** * Process select call that can be chained after an rpc call */ type RPCCallNodes, Acc extends Record = {}> = Nodes extends [infer FirstNode, ...infer RestNodes] ? FirstNode extends Ast.Node ? RestNodes extends Ast.Node[] ? ProcessRPCNode extends infer FieldResult ? FieldResult extends Record ? RPCCallNodes : FieldResult extends SelectQueryError ? SelectQueryError : SelectQueryError<'Could not retrieve a valid record or error value'> : SelectQueryError<'Processing node failed.'> : SelectQueryError<'Invalid rest nodes array in RPC call'> : SelectQueryError<'Invalid first node in RPC call'> : Prettify; /** * Recursively processes an array of Nodes and accumulates the resulting TypeScript type. * * @param Schema - Database schema. * @param Row - The type of a row in the current table. * @param RelationName - The name of the current table or view. * @param Relationships - Relationships of the current table. * @param Nodes - An array of AST nodes to process. * @param Acc - Accumulator for the constructed type. */ type ProcessNodes, RelationName extends string, Relationships extends GenericRelationship[], Nodes extends Ast.Node[], Acc extends Record = {}> = CheckDuplicateEmbededReference extends false ? Nodes extends [infer FirstNode, ...infer RestNodes] ? FirstNode extends Ast.Node ? RestNodes extends Ast.Node[] ? ProcessNode extends infer FieldResult ? FieldResult extends Record ? ProcessNodes : FieldResult extends SelectQueryError ? SelectQueryError : SelectQueryError<'Could not retrieve a valid record or error value'> : SelectQueryError<'Processing node failed.'> : SelectQueryError<'Invalid rest nodes array type in ProcessNodes'> : SelectQueryError<'Invalid first node type in ProcessNodes'> : Prettify : Prettify>; /** * Processes a single Node and returns the resulting TypeScript type. * * @param Schema - Database schema. * @param Row - The type of a row in the current table. * @param RelationName - The name of the current table or view. * @param Relationships - Relationships of the current table. * @param NodeType - The Node to process. */ type ProcessNode, RelationName extends string, Relationships extends GenericRelationship[], NodeType extends Ast.Node> = NodeType['type'] extends Ast.StarNode['type'] ? GetComputedFields extends never ? Row : Omit> : NodeType['type'] extends Ast.SpreadNode['type'] ? ProcessSpreadNode> : NodeType['type'] extends Ast.FieldNode['type'] ? ProcessFieldNode> : SelectQueryError<'Unsupported node type.'>; /** * Processes a FieldNode and returns the resulting TypeScript type. * * @param Schema - Database schema. * @param Row - The type of a row in the current table. * @param RelationName - The name of the current table or view. * @param Relationships - Relationships of the current table. * @param Field - The FieldNode to process. */ type ProcessFieldNode, RelationName extends string, Relationships extends GenericRelationship[], Field extends Ast.FieldNode> = Field['children'] extends [] ? ProcessEmbeddedResource : IsNonEmptyArray extends true ? ProcessEmbeddedResource : ProcessSimpleField; type ResolveJsonPathType = Path extends string ? JsonPathToType extends never ? TypeScriptTypes : JsonPathToType extends infer PathResult ? PathResult extends string ? PathResult : IsStringUnion extends true ? PathResult : CastType extends 'json' ? PathResult : TypeScriptTypes : TypeScriptTypes : TypeScriptTypes; /** * Processes a simple field (without embedded resources). * * @param Row - The type of a row in the current table. * @param RelationName - The name of the current table or view. * @param Field - The FieldNode to process. */ type ProcessSimpleField, RelationName extends string, Field extends Ast.FieldNode> = Field['name'] extends keyof Row | 'count' ? Field['aggregateFunction'] extends AggregateFunctions ? { [K in GetFieldNodeResultName]: Field['castType'] extends PostgreSQLTypes ? TypeScriptTypes : number; } : { [K in GetFieldNodeResultName]: Field['castType'] extends PostgreSQLTypes ? ResolveJsonPathType : Row[Field['name']]; } : SelectQueryError<`column '${Field['name']}' does not exist on '${RelationName}'.`>; /** * Processes an embedded resource (relation). * * @param Schema - Database schema. * @param Row - The type of a row in the current table. * @param RelationName - The name of the current table or view. * @param Relationships - Relationships of the current table. * @param Field - The FieldNode to process. */ type ProcessEmbeddedResource & string> = ResolveRelationship extends infer Resolved ? Resolved extends { scalarType: infer ScalarType; relation: { isSetofReturn?: boolean; isNotNullable?: boolean; }; } ? { [K in GetFieldNodeResultName]: Resolved['relation']['isSetofReturn'] extends true ? ScalarType : Resolved['relation']['isNotNullable'] extends true ? ScalarType : ScalarType | null; } : Resolved extends { referencedTable: Pick; relation: GenericRelationship & { match: 'refrel' | 'col' | 'fkname' | 'func'; }; direction: string; } ? Field['children'] extends [] ? {} : ProcessEmbeddedResourceResult : { [K in GetFieldNodeResultName]: Resolved; } : { [K in GetFieldNodeResultName]: SelectQueryError<'Failed to resolve relationship.'> & string; }; /** * Helper type to process the result of an embedded resource. */ type ProcessEmbeddedResourceResult; relation: GenericRelationship & { match: 'refrel' | 'col' | 'fkname' | 'func'; isNotNullable?: boolean; referencedRelation: string; isSetofReturn?: boolean; }; direction: string; }, Field extends Ast.FieldNode, CurrentTableOrView extends keyof TablesAndViews$1> = ProcessNodes extends Ast.Node[] ? Exclude : []> extends infer ProcessedChildren ? { [K in GetFieldNodeResultName]: Resolved['direction'] extends 'forward' ? Field extends { innerJoin: true; } ? Resolved['relation']['isOneToOne'] extends true ? ProcessedChildren : ProcessedChildren[] : Resolved['relation']['isOneToOne'] extends true ? Resolved['relation']['match'] extends 'func' ? Resolved['relation']['isNotNullable'] extends true ? Resolved['relation']['isSetofReturn'] extends true ? ProcessedChildren : { [P in keyof ProcessedChildren]: ProcessedChildren[P] | null; } : ProcessedChildren | null : ProcessedChildren | null : ProcessedChildren[] : Resolved['relation']['referencedRelation'] extends CurrentTableOrView ? Resolved['relation'] extends { hint: string; } ? ProcessedChildren[] : Resolved['relation']['match'] extends 'col' ? IsRelationNullable[CurrentTableOrView], Resolved['relation']> extends true ? ProcessedChildren | null : ProcessedChildren : ProcessedChildren[] : IsRelationNullable[CurrentTableOrView], Resolved['relation']> extends true ? Field extends { innerJoin: true; } ? ProcessedChildren : ProcessedChildren | null : ProcessedChildren; } : { [K in GetFieldNodeResultName]: SelectQueryError<'Failed to process embedded resource nodes.'> & string; }; /** * Processes a SpreadNode by processing its target node. * * @param Schema - Database schema. * @param Row - The type of a row in the current table. * @param RelationName - The name of the current table or view. * @param Relationships - Relationships of the current table. * @param Spread - The SpreadNode to process. */ type ProcessSpreadNode, RelationName extends string, Relationships extends GenericRelationship[], Spread extends Ast.SpreadNode> = ProcessNode extends infer Result ? Result extends SelectQueryError ? SelectQueryError : ExtractFirstProperty extends unknown[] ? SpreadOnManyEnabled extends true ? ProcessManyToManySpreadNodeResult : { [K in Spread['target']['name']]: SelectQueryError<`"${RelationName}" and "${Spread['target']['name']}" do not form a many-to-one or one-to-one relationship spread not possible`>; } : ProcessSpreadNodeResult : never; /** * Helper type to process the result of a many-to-many spread node. * Converts all fields in the spread object into arrays. */ type ProcessManyToManySpreadNodeResult = Result extends Record | null> ? Result : ExtractFirstProperty extends infer SpreadedObject ? SpreadedObject extends Array> ? { [K in keyof SpreadedObject[number]]: Array; } : SelectQueryError<'An error occurred spreading the many-to-many object'> : SelectQueryError<'An error occurred spreading the many-to-many object'>; /** * Helper type to process the result of a spread node. */ type ProcessSpreadNodeResult = Result extends Record | null> ? Result : ExtractFirstProperty extends infer SpreadedObject ? ContainsNull extends true ? Exclude<{ [K in keyof SpreadedObject]: SpreadedObject[K] | null; }, null> : Exclude<{ [K in keyof SpreadedObject]: SpreadedObject[K]; }, null> : SelectQueryError<'An error occurred spreading the object'>; declare class PostgrestTransformBuilder, Result, RelationName = unknown, Relationships = unknown, Method = unknown, ThrowOnError extends boolean = false> extends PostgrestBuilder { throwOnError(): PostgrestTransformBuilder; /** * Perform a SELECT on the query result. * * By default, `.insert()`, `.update()`, `.upsert()`, and `.delete()` do not * return modified rows. By calling this method, modified rows are returned in * `data`. * * @param columns - The columns to retrieve, separated by commas * * @category Database * @subcategory Using modifiers * * @example With `upsert()` * ```ts * const { data, error } = await supabase * .from('characters') * .upsert({ id: 1, name: 'Han Solo' }) * .select() * ``` * * @exampleSql With `upsert()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Han'); * ``` * * @exampleResponse With `upsert()` * ```json * { * "data": [ * { * "id": 1, * "name": "Han Solo" * } * ], * "status": 201, * "statusText": "" * } * ``` */ select>(columns?: Query): PostgrestFilterBuilder; /** * Order the query result by `column`. * * You can call this method multiple times to order by multiple columns. * * You can order referenced tables, but it only affects the ordering of the * parent table if you use `!inner` in the query. * * @param column - The column to order by * @param options - Named parameters * @param options.ascending - If `true`, the result will be in ascending order * @param options.nullsFirst - If `true`, `null`s appear first. If `false`, * `null`s appear last. * @param options.referencedTable - Set this to order a referenced table by * its columns * * @category Database * @subcategory Using modifiers */ order(column: ColumnName, options?: { ascending?: boolean; nullsFirst?: boolean; referencedTable?: undefined; }): this; order(column: string, options?: { ascending?: boolean; nullsFirst?: boolean; referencedTable?: string; }): this; /** * @deprecated Use `options.referencedTable` instead of `options.foreignTable` */ order(column: ColumnName, options?: { ascending?: boolean; nullsFirst?: boolean; foreignTable?: undefined; }): this; /** * @deprecated Use `options.referencedTable` instead of `options.foreignTable` */ order(column: string, options?: { ascending?: boolean; nullsFirst?: boolean; foreignTable?: string; }): this; /** * Limit the query result by `rows`. * * @param rows - The maximum number of rows to return * @param options - Named parameters * @param options.referencedTable - Set this to limit rows of referenced * tables instead of the parent table * @param options.foreignTable - Deprecated, use `options.referencedTable` * instead * * @category Database * @subcategory Using modifiers * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select('name') * .limit(1) * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "name": "Luke" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @example On a referenced table * ```ts * const { data, error } = await supabase * .from('orchestral_sections') * .select(` * name, * instruments ( * name * ) * `) * .limit(1, { referencedTable: 'instruments' }) * ``` * * @exampleSql On a referenced table * ```sql * create table * orchestral_sections (id int8 primary key, name text); * create table * instruments ( * id int8 primary key, * section_id int8 not null references orchestral_sections, * name text * ); * * insert into * orchestral_sections (id, name) * values * (1, 'strings'); * insert into * instruments (id, section_id, name) * values * (1, 1, 'harp'), * (2, 1, 'violin'); * ``` * * @exampleResponse On a referenced table * ```json * { * "data": [ * { * "name": "strings", * "instruments": [ * { * "name": "violin" * } * ] * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ limit(rows: number, { foreignTable, referencedTable, }?: { foreignTable?: string; referencedTable?: string; }): this; /** * Limit the query result by starting at an offset `from` and ending at the offset `to`. * Only records within this range are returned. * This respects the query order and if there is no order clause the range could behave unexpectedly. * The `from` and `to` values are 0-based and inclusive: `range(1, 3)` will include the second, third * and fourth rows of the query. * * @param from - The starting index from which to limit the result * @param to - The last index to which to limit the result * @param options - Named parameters * @param options.referencedTable - Set this to limit rows of referenced * tables instead of the parent table * @param options.foreignTable - Deprecated, use `options.referencedTable` * instead * * @category Database * @subcategory Using modifiers * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select('name') * .range(0, 1) * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "name": "Luke" * }, * { * "name": "Leia" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ range(from: number, to: number, { foreignTable, referencedTable, }?: { foreignTable?: string; referencedTable?: string; }): this; /** * Set the AbortSignal for the fetch request. * * @param signal - The AbortSignal to use for the fetch request * * @category Database * @subcategory Using modifiers * * @remarks * You can use this to set a timeout for the request. * * @exampleDescription Aborting requests in-flight * You can use an [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to abort requests. * Note that `status` and `statusText` don't mean anything for aborted requests as the request wasn't fulfilled. * * @example Aborting requests in-flight * ```ts * const ac = new AbortController() * * const { data, error } = await supabase * .from('very_big_table') * .select() * .abortSignal(ac.signal) * * // Abort the request after 100 ms * setTimeout(() => ac.abort(), 100) * ``` * * @exampleResponse Aborting requests in-flight * ```json * { * "error": { * "message": "AbortError: The user aborted a request.", * "details": "", * "hint": "The request was aborted locally via the provided AbortSignal.", * "code": "" * }, * "status": 0, * "statusText": "" * } * * ``` * * @example Set a timeout * ```ts * const { data, error } = await supabase * .from('very_big_table') * .select() * .abortSignal(AbortSignal.timeout(1000 /* ms *\/)) * ``` * * @exampleResponse Set a timeout * ```json * { * "error": { * "message": "FetchError: The user aborted a request.", * "details": "", * "hint": "", * "code": "" * }, * "status": 0, * "statusText": "" * } * * ``` */ abortSignal(signal: AbortSignal): this; /** * Return `data` as a single object instead of an array of objects. * * Query result must be one row (e.g. using `.limit(1)`), otherwise this * returns an error. * * @category Database * @subcategory Using modifiers * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select('name') * .limit(1) * .single() * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": { * "name": "Luke" * }, * "status": 200, * "statusText": "OK" * } * ``` */ single(): PostgrestBuilder; /** * Return `data` as a single object instead of an array of objects. * * Query result must be zero or one row (e.g. using `.limit(1)`), otherwise * this returns an error. * * @category Database * @subcategory Using modifiers * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select() * .eq('name', 'Katniss') * .maybeSingle() * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse With `select()` * ```json * { * "status": 200, * "statusText": "OK" * } * ``` */ maybeSingle(): PostgrestBuilder; /** * Return `data` as a string in CSV format. * * @category Database * @subcategory Using modifiers * * @exampleDescription Return data as CSV * By default, the data is returned in JSON format, but can also be returned as Comma Separated Values. * * @example Return data as CSV * ```ts * const { data, error } = await supabase * .from('characters') * .select() * .csv() * ``` * * @exampleSql Return data as CSV * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse Return data as CSV * ```json * { * "data": "id,name\n1,Luke\n2,Leia\n3,Han", * "status": 200, * "statusText": "OK" * } * ``` */ csv(): PostgrestBuilder; /** * Return `data` as an object in [GeoJSON](https://geojson.org) format. * * @category Database * @subcategory Using modifiers */ geojson(): PostgrestBuilder, ThrowOnError>; /** * Return `data` as the EXPLAIN plan for the query. * * You need to enable the * [db_plan_enabled](https://supabase.com/docs/guides/database/debugging-performance#enabling-explain) * setting before using this method. * * @param options - Named parameters * * @param options.analyze - If `true`, the query will be executed and the * actual run time will be returned * * @param options.verbose - If `true`, the query identifier will be returned * and `data` will include the output columns of the query * * @param options.settings - If `true`, include information on configuration * parameters that affect query planning * * @param options.buffers - If `true`, include information on buffer usage * * @param options.wal - If `true`, include information on WAL record generation * * @param options.format - The format of the output, can be `"text"` (default) * or `"json"` * * @category Database * @subcategory Using modifiers * * @exampleDescription Get the execution plan * By default, the data is returned in TEXT format, but can also be returned as JSON by using the `format` parameter. * * @example Get the execution plan * ```ts * const { data, error } = await supabase * .from('characters') * .select() * .explain() * ``` * * @exampleSql Get the execution plan * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse Get the execution plan * ```js * Aggregate (cost=33.34..33.36 rows=1 width=112) * -> Limit (cost=0.00..18.33 rows=1000 width=40) * -> Seq Scan on characters (cost=0.00..22.00 rows=1200 width=40) * ``` * * @exampleDescription Get the execution plan with analyze and verbose * By default, the data is returned in TEXT format, but can also be returned as JSON by using the `format` parameter. * * @example Get the execution plan with analyze and verbose * ```ts * const { data, error } = await supabase * .from('characters') * .select() * .explain({analyze:true,verbose:true}) * ``` * * @exampleSql Get the execution plan with analyze and verbose * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse Get the execution plan with analyze and verbose * ```js * Aggregate (cost=33.34..33.36 rows=1 width=112) (actual time=0.041..0.041 rows=1 loops=1) * Output: NULL::bigint, count(ROW(characters.id, characters.name)), COALESCE(json_agg(ROW(characters.id, characters.name)), '[]'::json), NULLIF(current_setting('response.headers'::text, true), ''::text), NULLIF(current_setting('response.status'::text, true), ''::text) * -> Limit (cost=0.00..18.33 rows=1000 width=40) (actual time=0.005..0.006 rows=3 loops=1) * Output: characters.id, characters.name * -> Seq Scan on public.characters (cost=0.00..22.00 rows=1200 width=40) (actual time=0.004..0.005 rows=3 loops=1) * Output: characters.id, characters.name * Query Identifier: -4730654291623321173 * Planning Time: 0.407 ms * Execution Time: 0.119 ms * ``` */ explain({ analyze, verbose, settings, buffers, wal, format, }?: { analyze?: boolean; verbose?: boolean; settings?: boolean; buffers?: boolean; wal?: boolean; format?: 'json' | 'text'; }): PostgrestBuilder | PostgrestBuilder[], ThrowOnError>; /** * Dry-run this request: execute the query but discard the changes. * * Server-side, PostgREST runs the query inside a transaction and rolls it back * instead of committing. The response still contains the data that *would* have * been returned — `RETURNING` clauses execute and RLS, triggers, and constraints * are all evaluated — but no row is actually inserted, updated, or deleted. * * This affects only the single request it is chained to. The JS caller has no * handle on the transaction: supabase-js does not group multiple queries into * one transaction. For multi-statement transactional logic, use a database * function (`supabase.rpc(...)`). * * Sets the `Prefer: tx=rollback` header. See PostgREST's docs on transaction * preferences for the underlying mechanism. * * @category Database * @subcategory Using modifiers * * @example Validate an insert without persisting * ```ts * const { data, error } = await supabase * .from('countries') * .insert({ name: 'France' }) * .select() * .rollback() * // `data` shows what would have been inserted; nothing is saved. * ``` */ rollback(): this; /** * Override the type of the returned `data`. * * @typeParam NewResult - The new result type to override with * @deprecated Use overrideTypes() method at the end of your call chain instead * * @category Database * @subcategory Using modifiers * * @remarks * - Deprecated: use overrideTypes method instead * * @example Override type of successful response * ```ts * const { data } = await supabase * .from('countries') * .select() * .returns>() * ``` * * @exampleResponse Override type of successful response * ```js * let x: typeof data // MyType[] * ``` * * @example Override type of object response * ```ts * const { data } = await supabase * .from('countries') * .select() * .maybeSingle() * .returns() * ``` * * @exampleResponse Override type of object response * ```js * let x: typeof data // MyType | null * ``` */ returns(): PostgrestTransformBuilder, RelationName, Relationships, Method, ThrowOnError>; /** * Set the maximum number of rows that can be affected by the query. * Only available in PostgREST v13+ and only works with PATCH and DELETE methods. * * @param rows - The maximum number of rows that can be affected * * @category Database * @subcategory Using modifiers */ maxAffected(rows: number): MaxAffectedEnabled extends true ? Method extends 'PATCH' | 'DELETE' | 'RPC' ? this : InvalidMethodError<'maxAffected method only available on update or delete'> : InvalidMethodError<'maxAffected method only available on postgrest 13+'>; } type FilterOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ilike' | 'is' | 'isdistinct' | 'in' | 'cs' | 'cd' | 'sl' | 'sr' | 'nxl' | 'nxr' | 'adj' | 'ov' | 'fts' | 'plfts' | 'phfts' | 'wfts' | 'match' | 'imatch'; type IsStringOperator = Path extends `${string}->>${string}` ? true : false; type ResolveFilterValue, ColumnName extends string> = ColumnName extends `${infer RelationshipTable}.${infer Remainder}` ? Remainder extends `${infer _}.${infer _}` ? ResolveFilterValue : ResolveFilterRelationshipValue : ColumnName extends keyof Row ? Row[ColumnName] : IsStringOperator extends true ? string : JsonPathToType> extends infer JsonPathValue ? JsonPathValue extends never ? never : JsonPathValue : never; type ResolveFilterRelationshipValue = Schema['Tables'] & Schema['Views'] extends infer TablesAndViews ? RelationshipTable extends keyof TablesAndViews ? 'Row' extends keyof TablesAndViews[RelationshipTable] ? RelationshipColumn extends keyof TablesAndViews[RelationshipTable]['Row'] ? TablesAndViews[RelationshipTable]['Row'][RelationshipColumn] : unknown : unknown : unknown : never; type InvalidMethodError = { Error: S; }; type NonNullableColumn, Col extends string> = Col extends keyof T ? { [K in keyof T]: K extends Col ? NonNullable : T[K]; } : T; type NarrowResultColumn = T extends (infer Item)[] ? Item extends Record ? Col extends keyof Item ? { [K in keyof Item]: K extends Col ? NonNullable : Item[K]; }[] : T : T : T extends Record ? Col extends keyof T ? { [K in keyof T]: K extends Col ? NonNullable : T[K]; } : T : T; declare class PostgrestFilterBuilder, Result, RelationName = unknown, Relationships = unknown, Method = unknown, ThrowOnError extends boolean = false> extends PostgrestTransformBuilder { throwOnError(): PostgrestFilterBuilder; /** * Match only rows where `column` is equal to `value`. * * To check if the value of `column` is NULL, you should use `.is()` instead. * * @param column - The column to filter on * @param value - The value to filter with * * @category Database * @subcategory Using filters * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select() * .eq('name', 'Leia') * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "id": 2, * "name": "Leia" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ eq(column: ColumnName extends keyof Row ? ColumnName : ColumnName extends `${string}.${string}` | `${string}->${string}` ? ColumnName : string extends ColumnName ? string : keyof Row, value: ResolveFilterValue extends never ? NonNullable : ResolveFilterValue extends infer ResolvedFilterValue ? NonNullable : never): this; /** * Match only rows where `column` is not equal to `value`. * * This filter does not include rows where `column` is `NULL`. To match null * values, use `.is(column, null)` instead. * * @param column - The column to filter on * @param value - The value to filter with * * @category Database * @subcategory Using filters * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select() * .neq('name', 'Leia') * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "id": 1, * "name": "Luke" * }, * { * "id": 3, * "name": "Han" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ neq(column: ColumnName extends keyof Row ? ColumnName : ColumnName extends `${string}.${string}` | `${string}->${string}` ? ColumnName : string extends ColumnName ? string : keyof Row, value: ResolveFilterValue extends never ? unknown : ResolveFilterValue extends infer Resolved ? Resolved : never): this; /** * Match only rows where `column` is greater than `value`. * * @param column - The column to filter on * @param value - The value to filter with * * @category Database * @subcategory Using filters * * @exampleDescription With `select()` * When using [reserved words](https://www.postgresql.org/docs/current/sql-keywords-appendix.html) for column names you need * to add double quotes e.g. `.gt('"order"', 2)` * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select() * .gt('id', 2) * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "id": 3, * "name": "Han" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ gt(column: ColumnName, value: Row[ColumnName]): this; gt(column: string, value: unknown): this; /** * Match only rows where `column` is greater than or equal to `value`. * * @param column - The column to filter on * @param value - The value to filter with * * @category Database * @subcategory Using filters * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select() * .gte('id', 2) * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "id": 2, * "name": "Leia" * }, * { * "id": 3, * "name": "Han" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ gte(column: ColumnName, value: Row[ColumnName]): this; gte(column: string, value: unknown): this; /** * Match only rows where `column` is less than `value`. * * @param column - The column to filter on * @param value - The value to filter with * * @category Database * @subcategory Using filters * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select() * .lt('id', 2) * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "id": 1, * "name": "Luke" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ lt(column: ColumnName, value: Row[ColumnName]): this; lt(column: string, value: unknown): this; /** * Match only rows where `column` is less than or equal to `value`. * * @param column - The column to filter on * @param value - The value to filter with * * @category Database * @subcategory Using filters * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select() * .lte('id', 2) * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "id": 1, * "name": "Luke" * }, * { * "id": 2, * "name": "Leia" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ lte(column: ColumnName, value: Row[ColumnName]): this; lte(column: string, value: unknown): this; /** * Match only rows where `column` matches `pattern` case-sensitively. * * @param column - The column to filter on * @param pattern - The pattern to match with * * @category Database * @subcategory Using filters * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select() * .like('name', '%Lu%') * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "id": 1, * "name": "Luke" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ like(column: ColumnName, pattern: string): this; like(column: string, pattern: string): this; /** * Match only rows where `column` matches all of `patterns` case-sensitively. * * @param column - The column to filter on * @param patterns - The patterns to match with * * @category Database * @subcategory Using filters */ likeAllOf(column: ColumnName, patterns: readonly string[]): this; likeAllOf(column: string, patterns: readonly string[]): this; /** * Match only rows where `column` matches any of `patterns` case-sensitively. * * @param column - The column to filter on * @param patterns - The patterns to match with * * @category Database * @subcategory Using filters */ likeAnyOf(column: ColumnName, patterns: readonly string[]): this; likeAnyOf(column: string, patterns: readonly string[]): this; /** * Match only rows where `column` matches `pattern` case-insensitively. * * @param column - The column to filter on * @param pattern - The pattern to match with * * @category Database * @subcategory Using filters * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select() * .ilike('name', '%lu%') * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "id": 1, * "name": "Luke" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ ilike(column: ColumnName, pattern: string): this; ilike(column: string, pattern: string): this; /** * Match only rows where `column` matches all of `patterns` case-insensitively. * * @param column - The column to filter on * @param patterns - The patterns to match with * * @category Database * @subcategory Using filters */ ilikeAllOf(column: ColumnName, patterns: readonly string[]): this; ilikeAllOf(column: string, patterns: readonly string[]): this; /** * Match only rows where `column` matches any of `patterns` case-insensitively. * * @param column - The column to filter on * @param patterns - The patterns to match with * * @category Database * @subcategory Using filters */ ilikeAnyOf(column: ColumnName, patterns: readonly string[]): this; ilikeAnyOf(column: string, patterns: readonly string[]): this; /** * Match only rows where `column` matches the PostgreSQL regex `pattern` * case-sensitively (using the `~` operator). * * @param column - The column to filter on * @param pattern - The PostgreSQL regular expression pattern to match with */ regexMatch(column: ColumnName, pattern: string): this; regexMatch(column: string, pattern: string): this; /** * Match only rows where `column` matches the PostgreSQL regex `pattern` * case-insensitively (using the `~*` operator). * * @param column - The column to filter on * @param pattern - The PostgreSQL regular expression pattern to match with */ regexIMatch(column: ColumnName, pattern: string): this; regexIMatch(column: string, pattern: string): this; /** * Match only rows where `column` IS `value`. * * For non-boolean columns, this is only relevant for checking if the value of * `column` is NULL by setting `value` to `null`. * * For boolean columns, you can also set `value` to `true` or `false` and it * will behave the same way as `.eq()`. * * @param column - The column to filter on * @param value - The value to filter with * * @category Database * @subcategory Using filters * * @exampleDescription Checking for nullness, true or false * Using the `eq()` filter doesn't work when filtering for `null`. * * Instead, you need to use `is()`. * * @example Checking for nullness, true or false * ```ts * const { data, error } = await supabase * .from('countries') * .select() * .is('name', null) * ``` * * @exampleSql Checking for nullness, true or false * ```sql * create table * countries (id int8 primary key, name text); * * insert into * countries (id, name) * values * (1, 'null'), * (2, null); * ``` * * @exampleResponse Checking for nullness, true or false * ```json * { * "data": [ * { * "id": 2, * "name": "null" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ is(column: ColumnName, value: Row[ColumnName] & (boolean | null)): this; is(column: string, value: boolean | null): this; /** * Match only rows where `column` IS DISTINCT FROM `value`. * * Unlike `.neq()`, this treats `NULL` as a comparable value. Two `NULL` values * are considered equal (not distinct), and comparing `NULL` with any non-NULL * value returns true (distinct). * * @param column - The column to filter on * @param value - The value to filter with */ isDistinct(column: ColumnName, value: ResolveFilterValue extends never ? unknown : ResolveFilterValue extends infer ResolvedFilterValue ? ResolvedFilterValue : never): this; /** * Match only rows where `column` is included in the `values` array. * * @param column - The column to filter on * @param values - The values array to filter with * * @category Database * @subcategory Using filters * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select() * .in('name', ['Leia', 'Han']) * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "id": 2, * "name": "Leia" * }, * { * "id": 3, * "name": "Han" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ in(column: ColumnName, values: ReadonlyArray extends never ? unknown : ResolveFilterValue extends infer ResolvedFilterValue ? ResolvedFilterValue : never>): this; /** * Match only rows where `column` is NOT included in the `values` array. * * @param column - The column to filter on * @param values - The values array to filter with */ notIn(column: ColumnName, values: ReadonlyArray extends never ? unknown : ResolveFilterValue extends infer ResolvedFilterValue ? ResolvedFilterValue : never>): this; /** * Only relevant for jsonb, array, and range columns. Match only rows where * `column` contains every element appearing in `value`. * * @param column - The jsonb, array, or range column to filter on * @param value - The jsonb, array, or range value to filter with * * @category Database * @subcategory Using filters * * @example On array columns * ```ts * const { data, error } = await supabase * .from('issues') * .select() * .contains('tags', ['is:open', 'priority:low']) * ``` * * @exampleSql On array columns * ```sql * create table * issues ( * id int8 primary key, * title text, * tags text[] * ); * * insert into * issues (id, title, tags) * values * (1, 'Cache invalidation is not working', array['is:open', 'severity:high', 'priority:low']), * (2, 'Use better names', array['is:open', 'severity:low', 'priority:medium']); * ``` * * @exampleResponse On array columns * ```json * { * "data": [ * { * "title": "Cache invalidation is not working" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription On range columns * Postgres supports a number of [range * types](https://www.postgresql.org/docs/current/rangetypes.html). You * can filter on range columns using the string representation of range * values. * * @example On range columns * ```ts * const { data, error } = await supabase * .from('reservations') * .select() * .contains('during', '[2000-01-01 13:00, 2000-01-01 13:30)') * ``` * * @exampleSql On range columns * ```sql * create table * reservations ( * id int8 primary key, * room_name text, * during tsrange * ); * * insert into * reservations (id, room_name, during) * values * (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), * (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); * ``` * * @exampleResponse On range columns * ```json * { * "data": [ * { * "id": 1, * "room_name": "Emerald", * "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @example On `jsonb` columns * ```ts * const { data, error } = await supabase * .from('users') * .select('name') * .contains('address', { postcode: 90210 }) * ``` * * @exampleSql On `jsonb` columns * ```sql * create table * users ( * id int8 primary key, * name text, * address jsonb * ); * * insert into * users (id, name, address) * values * (1, 'Michael', '{ "postcode": 90210, "street": "Melrose Place" }'), * (2, 'Jane', '{}'); * ``` * * @exampleResponse On `jsonb` columns * ```json * { * "data": [ * { * "name": "Michael" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ contains(column: ColumnName, value: string | ReadonlyArray | Record): this; contains(column: string, value: string | readonly unknown[] | Record): this; /** * Only relevant for jsonb, array, and range columns. Match only rows where * every element appearing in `column` is contained by `value`. * * @param column - The jsonb, array, or range column to filter on * @param value - The jsonb, array, or range value to filter with * * @category Database * @subcategory Using filters * * @example On array columns * ```ts * const { data, error } = await supabase * .from('classes') * .select('name') * .containedBy('days', ['monday', 'tuesday', 'wednesday', 'friday']) * ``` * * @exampleSql On array columns * ```sql * create table * classes ( * id int8 primary key, * name text, * days text[] * ); * * insert into * classes (id, name, days) * values * (1, 'Chemistry', array['monday', 'friday']), * (2, 'History', array['monday', 'wednesday', 'thursday']); * ``` * * @exampleResponse On array columns * ```json * { * "data": [ * { * "name": "Chemistry" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription On range columns * Postgres supports a number of [range * types](https://www.postgresql.org/docs/current/rangetypes.html). You * can filter on range columns using the string representation of range * values. * * @example On range columns * ```ts * const { data, error } = await supabase * .from('reservations') * .select() * .containedBy('during', '[2000-01-01 00:00, 2000-01-01 23:59)') * ``` * * @exampleSql On range columns * ```sql * create table * reservations ( * id int8 primary key, * room_name text, * during tsrange * ); * * insert into * reservations (id, room_name, during) * values * (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), * (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); * ``` * * @exampleResponse On range columns * ```json * { * "data": [ * { * "id": 1, * "room_name": "Emerald", * "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @example On `jsonb` columns * ```ts * const { data, error } = await supabase * .from('users') * .select('name') * .containedBy('address', {}) * ``` * * @exampleSql On `jsonb` columns * ```sql * create table * users ( * id int8 primary key, * name text, * address jsonb * ); * * insert into * users (id, name, address) * values * (1, 'Michael', '{ "postcode": 90210, "street": "Melrose Place" }'), * (2, 'Jane', '{}'); * ``` * * @exampleResponse On `jsonb` columns * ```json * { * "data": [ * { * "name": "Jane" * } * ], * "status": 200, * "statusText": "OK" * } * * ``` */ containedBy(column: ColumnName, value: string | ReadonlyArray | Record): this; containedBy(column: string, value: string | readonly unknown[] | Record): this; /** * Only relevant for range columns. Match only rows where every element in * `column` is greater than any element in `range`. * * @param column - The range column to filter on * @param range - The range to filter with * * @category Database * @subcategory Using filters * * @exampleDescription With `select()` * Postgres supports a number of [range * types](https://www.postgresql.org/docs/current/rangetypes.html). You * can filter on range columns using the string representation of range * values. * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('reservations') * .select() * .rangeGt('during', '[2000-01-02 08:00, 2000-01-02 09:00)') * ``` * * @exampleSql With `select()` * ```sql * create table * reservations ( * id int8 primary key, * room_name text, * during tsrange * ); * * insert into * reservations (id, room_name, during) * values * (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), * (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "id": 2, * "room_name": "Topaz", * "during": "[\"2000-01-02 09:00:00\",\"2000-01-02 10:00:00\")" * } * ], * "status": 200, * "statusText": "OK" * } * * ``` */ rangeGt(column: ColumnName, range: string): this; rangeGt(column: string, range: string): this; /** * Only relevant for range columns. Match only rows where every element in * `column` is either contained in `range` or greater than any element in * `range`. * * @param column - The range column to filter on * @param range - The range to filter with * * @category Database * @subcategory Using filters * * @exampleDescription With `select()` * Postgres supports a number of [range * types](https://www.postgresql.org/docs/current/rangetypes.html). You * can filter on range columns using the string representation of range * values. * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('reservations') * .select() * .rangeGte('during', '[2000-01-02 08:30, 2000-01-02 09:30)') * ``` * * @exampleSql With `select()` * ```sql * create table * reservations ( * id int8 primary key, * room_name text, * during tsrange * ); * * insert into * reservations (id, room_name, during) * values * (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), * (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "id": 2, * "room_name": "Topaz", * "during": "[\"2000-01-02 09:00:00\",\"2000-01-02 10:00:00\")" * } * ], * "status": 200, * "statusText": "OK" * } * * ``` */ rangeGte(column: ColumnName, range: string): this; rangeGte(column: string, range: string): this; /** * Only relevant for range columns. Match only rows where every element in * `column` is less than any element in `range`. * * @param column - The range column to filter on * @param range - The range to filter with * * @category Database * @subcategory Using filters * * @exampleDescription With `select()` * Postgres supports a number of [range * types](https://www.postgresql.org/docs/current/rangetypes.html). You * can filter on range columns using the string representation of range * values. * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('reservations') * .select() * .rangeLt('during', '[2000-01-01 15:00, 2000-01-01 16:00)') * ``` * * @exampleSql With `select()` * ```sql * create table * reservations ( * id int8 primary key, * room_name text, * during tsrange * ); * * insert into * reservations (id, room_name, during) * values * (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), * (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "id": 1, * "room_name": "Emerald", * "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ rangeLt(column: ColumnName, range: string): this; rangeLt(column: string, range: string): this; /** * Only relevant for range columns. Match only rows where every element in * `column` is either contained in `range` or less than any element in * `range`. * * @param column - The range column to filter on * @param range - The range to filter with * * @category Database * @subcategory Using filters * * @exampleDescription With `select()` * Postgres supports a number of [range * types](https://www.postgresql.org/docs/current/rangetypes.html). You * can filter on range columns using the string representation of range * values. * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('reservations') * .select() * .rangeLte('during', '[2000-01-01 14:00, 2000-01-01 16:00)') * ``` * * @exampleSql With `select()` * ```sql * create table * reservations ( * id int8 primary key, * room_name text, * during tsrange * ); * * insert into * reservations (id, room_name, during) * values * (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), * (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "id": 1, * "room_name": "Emerald", * "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" * } * ], * "status": 200, * "statusText": "OK" * } * * ``` */ rangeLte(column: ColumnName, range: string): this; rangeLte(column: string, range: string): this; /** * Only relevant for range columns. Match only rows where `column` is * mutually exclusive to `range` and there can be no element between the two * ranges. * * @param column - The range column to filter on * @param range - The range to filter with * * @category Database * @subcategory Using filters * * @exampleDescription With `select()` * Postgres supports a number of [range * types](https://www.postgresql.org/docs/current/rangetypes.html). You * can filter on range columns using the string representation of range * values. * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('reservations') * .select() * .rangeAdjacent('during', '[2000-01-01 12:00, 2000-01-01 13:00)') * ``` * * @exampleSql With `select()` * ```sql * create table * reservations ( * id int8 primary key, * room_name text, * during tsrange * ); * * insert into * reservations (id, room_name, during) * values * (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), * (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "id": 1, * "room_name": "Emerald", * "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ rangeAdjacent(column: ColumnName, range: string): this; rangeAdjacent(column: string, range: string): this; /** * Only relevant for array and range columns. Match only rows where * `column` and `value` have an element in common. * * @param column - The array or range column to filter on * @param value - The array or range value to filter with * * @category Database * @subcategory Using filters * * @example On array columns * ```ts * const { data, error } = await supabase * .from('issues') * .select('title') * .overlaps('tags', ['is:closed', 'severity:high']) * ``` * * @exampleSql On array columns * ```sql * create table * issues ( * id int8 primary key, * title text, * tags text[] * ); * * insert into * issues (id, title, tags) * values * (1, 'Cache invalidation is not working', array['is:open', 'severity:high', 'priority:low']), * (2, 'Use better names', array['is:open', 'severity:low', 'priority:medium']); * ``` * * @exampleResponse On array columns * ```json * { * "data": [ * { * "title": "Cache invalidation is not working" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription On range columns * Postgres supports a number of [range * types](https://www.postgresql.org/docs/current/rangetypes.html). You * can filter on range columns using the string representation of range * values. * * @example On range columns * ```ts * const { data, error } = await supabase * .from('reservations') * .select() * .overlaps('during', '[2000-01-01 12:45, 2000-01-01 13:15)') * ``` * * @exampleSql On range columns * ```sql * create table * reservations ( * id int8 primary key, * room_name text, * during tsrange * ); * * insert into * reservations (id, room_name, during) * values * (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), * (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); * ``` * * @exampleResponse On range columns * ```json * { * "data": [ * { * "id": 1, * "room_name": "Emerald", * "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ overlaps(column: ColumnName, value: string | ReadonlyArray): this; overlaps(column: string, value: string | readonly unknown[]): this; /** * Only relevant for text and tsvector columns. Match only rows where * `column` matches the query string in `query`. * * @param column - The text or tsvector column to filter on * @param query - The query text to match with * @param options - Named parameters * @param options.config - The text search configuration to use * @param options.type - Change how the `query` text is interpreted * * @category Database * @subcategory Using filters * * @remarks * - For more information, see [Postgres full text search](/docs/guides/database/full-text-search). * * @example Text search * ```ts * const result = await supabase * .from("texts") * .select("content") * .textSearch("content", `'eggs' & 'ham'`, { * config: "english", * }); * ``` * * @exampleSql Text search * ```sql * create table texts ( * id bigint * primary key * generated always as identity, * content text * ); * * insert into texts (content) values * ('Four score and seven years ago'), * ('The road goes ever on and on'), * ('Green eggs and ham') * ; * ``` * * @exampleResponse Text search * ```json * { * "data": [ * { * "content": "Green eggs and ham" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Basic normalization * Uses PostgreSQL's `plainto_tsquery` function. * * @example Basic normalization * ```ts * const { data, error } = await supabase * .from('quotes') * .select('catchphrase') * .textSearch('catchphrase', `'fat' & 'cat'`, { * type: 'plain', * config: 'english' * }) * ``` * * @exampleDescription Full normalization * Uses PostgreSQL's `phraseto_tsquery` function. * * @example Full normalization * ```ts * const { data, error } = await supabase * .from('quotes') * .select('catchphrase') * .textSearch('catchphrase', `'fat' & 'cat'`, { * type: 'phrase', * config: 'english' * }) * ``` * * @exampleDescription Websearch * Uses PostgreSQL's `websearch_to_tsquery` function. * This function will never raise syntax errors, which makes it possible to use raw user-supplied input for search, and can be used * with advanced operators. * * - `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery. * - `"quoted text"`: text inside quote marks will be converted to terms separated by `<->` operators, as if processed by phraseto_tsquery. * - `OR`: the word “or” will be converted to the | operator. * - `-`: a dash will be converted to the ! operator. * * @example Websearch * ```ts * const { data, error } = await supabase * .from('quotes') * .select('catchphrase') * .textSearch('catchphrase', `'fat or cat'`, { * type: 'websearch', * config: 'english' * }) * ``` */ textSearch(column: ColumnName, query: string, options?: { config?: string; type?: 'plain' | 'phrase' | 'websearch'; }): this; textSearch(column: string, query: string, options?: { config?: string; type?: 'plain' | 'phrase' | 'websearch'; }): this; /** * Match only rows where each column in `query` keys is equal to its * associated value. Shorthand for multiple `.eq()`s. * * @param query - The object to filter with, with column names as keys mapped * to their filter values * * @category Database * @subcategory Using filters * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select('name') * .match({ id: 2, name: 'Leia' }) * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "name": "Leia" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ match(query: Record): this; match(query: Record): this; /** * Match only rows which doesn't satisfy the filter. * * Unlike most filters, `opearator` and `value` are used as-is and need to * follow [PostgREST * syntax](https://postgrest.org/en/stable/api.html#operators). You also need * to make sure they are properly sanitized. * * @param column - The column to filter on * @param operator - The operator to be negated to filter with, following * PostgREST syntax * @param value - The value to filter with, following PostgREST syntax * * @category Database * @subcategory Using filters */ not(column: ColumnName, operator: 'is', value: null): PostgrestFilterBuilder, NarrowResultColumn, RelationName, Relationships, Method, ThrowOnError> & this; not(column: ColumnName, operator: FilterOperator, value: Row[ColumnName]): this; not(column: string, operator: string, value: unknown): this; /** * Match only rows which satisfy at least one of the filters. * * Unlike most filters, `filters` is used as-is and needs to follow [PostgREST * syntax](https://postgrest.org/en/stable/api.html#operators). You also need * to make sure it's properly sanitized. * * It's currently not possible to do an `.or()` filter across multiple tables. * * @param filters - The filters to use, following PostgREST syntax * @param options - Named parameters * @param options.referencedTable - Set this to filter on referenced tables * instead of the parent table * @param options.foreignTable - Deprecated, use `referencedTable` instead * * @category Database * @subcategory Using filters * * @remarks * or() expects you to use the raw PostgREST syntax for the filter names and values. * * ```ts * .or('id.in.(5,6,7), arraycol.cs.{"a","b"}') // Use `()` for `in` filter, `{}` for array values and `cs` for `contains()`. * .or('id.in.(5,6,7), arraycol.cd.{"a","b"}') // Use `cd` for `containedBy()` * ``` * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select('name') * .or('id.eq.2,name.eq.Han') * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "name": "Leia" * }, * { * "name": "Han" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @example Use `or` with `and` * ```ts * const { data, error } = await supabase * .from('characters') * .select('name') * .or('id.gt.3,and(id.eq.1,name.eq.Luke)') * ``` * * @exampleSql Use `or` with `and` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse Use `or` with `and` * ```json * { * "data": [ * { * "name": "Luke" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @example Use `or` on referenced tables * ```ts * const { data, error } = await supabase * .from('orchestral_sections') * .select(` * name, * instruments!inner ( * name * ) * `) * .or('section_id.eq.1,name.eq.guzheng', { referencedTable: 'instruments' }) * ``` * * @exampleSql Use `or` on referenced tables * ```sql * create table * orchestral_sections (id int8 primary key, name text); * create table * instruments ( * id int8 primary key, * section_id int8 not null references orchestral_sections, * name text * ); * * insert into * orchestral_sections (id, name) * values * (1, 'strings'), * (2, 'woodwinds'); * insert into * instruments (id, section_id, name) * values * (1, 2, 'flute'), * (2, 1, 'violin'); * ``` * * @exampleResponse Use `or` on referenced tables * ```json * { * "data": [ * { * "name": "strings", * "instruments": [ * { * "name": "violin" * } * ] * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ or(filters: string, { foreignTable, referencedTable, }?: { foreignTable?: string; referencedTable?: string; }): this; /** * Match only rows which satisfy the filter. This is an escape hatch - you * should use the specific filter methods wherever possible. * * Unlike most filters, `opearator` and `value` are used as-is and need to * follow [PostgREST * syntax](https://postgrest.org/en/stable/api.html#operators). You also need * to make sure they are properly sanitized. * * @param column - The column to filter on * @param operator - The operator to filter with, following PostgREST syntax * @param value - The value to filter with, following PostgREST syntax * * @category Database * @subcategory Using filters * * @remarks * filter() expects you to use the raw PostgREST syntax for the filter values. * * ```ts * .filter('id', 'in', '(5,6,7)') // Use `()` for `in` filter * .filter('arraycol', 'cs', '{"a","b"}') // Use `cs` for `contains()`, `{}` for array values * ``` * * @example With `select()` * ```ts * const { data, error } = await supabase * .from('characters') * .select() * .filter('name', 'in', '("Han","Yoda")') * ``` * * @exampleSql With `select()` * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse With `select()` * ```json * { * "data": [ * { * "id": 3, * "name": "Han" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @example On a referenced table * ```ts * const { data, error } = await supabase * .from('orchestral_sections') * .select(` * name, * instruments!inner ( * name * ) * `) * .filter('instruments.name', 'eq', 'flute') * ``` * * @exampleSql On a referenced table * ```sql * create table * orchestral_sections (id int8 primary key, name text); * create table * instruments ( * id int8 primary key, * section_id int8 not null references orchestral_sections, * name text * ); * * insert into * orchestral_sections (id, name) * values * (1, 'strings'), * (2, 'woodwinds'); * insert into * instruments (id, section_id, name) * values * (1, 2, 'flute'), * (2, 1, 'violin'); * ``` * * @exampleResponse On a referenced table * ```json * { * "data": [ * { * "name": "woodwinds", * "instruments": [ * { * "name": "flute" * } * ] * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ filter(column: ColumnName, operator: `${'' | 'not.'}${FilterOperator}`, value: unknown): this; filter(column: string, operator: string, value: unknown): this; } declare class PostgrestQueryBuilder { url: URL; headers: Headers; schema?: string; signal?: AbortSignal; fetch?: Fetch; urlLengthLimit: number; /** * Enable or disable automatic retries for transient errors. * When enabled, idempotent requests (GET/HEAD/OPTIONS) that fail with network * errors or HTTP 503/520 responses are automatically retried with exponential * backoff (1s, 2s, 4s, up to 3 attempts). Defaults to `true` when not specified. */ retry?: boolean; /** * Creates a query builder scoped to a Postgres table or view. * * @category Database * * @param url - The URL for the query * @param options - Named parameters * @param options.headers - Custom headers * @param options.schema - Postgres schema to use * @param options.fetch - Custom fetch implementation * @param options.urlLengthLimit - Maximum URL length before warning * @param options.retry - Enable automatic retries for transient errors (default: true) * * @example Using supabase-js (recommended) * ```ts * import { createClient } from '@supabase/supabase-js' * * const supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key') * const { data, error } = await supabase.from('users').select('*') * ``` * * @example Standalone import for bundle-sensitive environments * ```ts * import { PostgrestQueryBuilder } from '@supabase/postgrest-js' * * const query = new PostgrestQueryBuilder( * new URL('https://xyzcompany.supabase.co/rest/v1/users'), * { headers: { apikey: 'your-publishable-key' }, retry: true } * ) * ``` */ constructor(url: URL, { headers, schema, fetch, urlLengthLimit, retry, }: { headers?: HeadersInit; schema?: string; fetch?: Fetch; urlLengthLimit?: number; retry?: boolean; }); /** * Clone URL and headers to prevent shared state between operations. */ private cloneRequestState; /** * Perform a SELECT query on the table or view. * * @param columns - The columns to retrieve, separated by commas. Columns can be renamed when returned with `customName:columnName` * * @param options - Named parameters * * @param options.head - When set to `true`, `data` will not be returned. * Useful if you only need the count. * * @param options.count - Count algorithm to use to count rows in the table or view. * * `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the * hood. * * `"planned"`: Approximated but fast count algorithm. Uses the Postgres * statistics under the hood. * * `"estimated"`: Uses exact count for low numbers and planned count for high * numbers. * * @remarks * When using `count` with `.range()` or `.limit()`, the returned `count` is the total number of rows * that match your filters, not the number of rows in the current page. Use this to build pagination UI. * * - By default, Supabase projects return a maximum of 1,000 rows. This setting can be changed in your project's [API settings](/dashboard/project/_/settings/api). It's recommended that you keep it low to limit the payload size of accidental or malicious requests. You can use `range()` queries to paginate through your data. * - `select()` can be combined with [Filters](/docs/reference/javascript/using-filters) * - `select()` can be combined with [Modifiers](/docs/reference/javascript/using-modifiers) * - `apikey` is a reserved keyword if you're using the [Supabase Platform](/docs/guides/platform) and [should be avoided as a column name](https://github.com/supabase/supabase/issues/5465). * * @category Database * * @example Getting your data * ```js * const { data, error } = await supabase * .from('characters') * .select() * ``` * * @exampleSql Getting your data * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Harry'), * (2, 'Frodo'), * (3, 'Katniss'); * ``` * * @exampleResponse Getting your data * ```json * { * "data": [ * { * "id": 1, * "name": "Harry" * }, * { * "id": 2, * "name": "Frodo" * }, * { * "id": 3, * "name": "Katniss" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Handling errors * The most useful field on a Postgres error is usually `hint` — when the database knows the fix, it puts the literal SQL there. For example, a permission-denied error (`code: '42501'`) arrives with a `hint` like `"Grant the required privileges to the current role with: GRANT SELECT ON public.characters TO anon;"`. Log the full `error` object so the hint isn't hidden behind `error.message`. * * @example Handling errors * ```js * const { data, error } = await supabase.from('characters').select() * if (error) { * // Logs the full error: message, code, details, and hint. * console.error(error) * return * } * ``` * * @exampleResponse Handling errors * ```json * { * "error": { * "code": "42501", * "details": null, * "hint": "Grant the required privileges to the current role with: GRANT SELECT ON public.characters TO anon;", * "message": "permission denied for table characters" * }, * "status": 401, * "statusText": "" * } * ``` * * @example Selecting specific columns * ```js * const { data, error } = await supabase * .from('characters') * .select('name') * ``` * * @exampleSql Selecting specific columns * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Frodo'), * (2, 'Harry'), * (3, 'Katniss'); * ``` * * @exampleResponse Selecting specific columns * ```json * { * "data": [ * { * "name": "Frodo" * }, * { * "name": "Harry" * }, * { * "name": "Katniss" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Query referenced tables * If your database has foreign key relationships, you can query related tables too. * * @example Query referenced tables * ```js * const { data, error } = await supabase * .from('orchestral_sections') * .select(` * name, * instruments ( * name * ) * `) * ``` * * @exampleSql Query referenced tables * ```sql * create table * orchestral_sections (id int8 primary key, name text); * create table * instruments ( * id int8 primary key, * section_id int8 not null references orchestral_sections, * name text * ); * * insert into * orchestral_sections (id, name) * values * (1, 'strings'), * (2, 'woodwinds'); * insert into * instruments (id, section_id, name) * values * (1, 2, 'flute'), * (2, 1, 'violin'); * ``` * * @exampleResponse Query referenced tables * ```json * { * "data": [ * { * "name": "strings", * "instruments": [ * { * "name": "violin" * } * ] * }, * { * "name": "woodwinds", * "instruments": [ * { * "name": "flute" * } * ] * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Query referenced tables with spaces in their names * If your table name contains spaces, you must use double quotes in the `select` statement to reference the table. * * @example Query referenced tables with spaces in their names * ```js * const { data, error } = await supabase * .from('orchestral sections') * .select(` * name, * "musical instruments" ( * name * ) * `) * ``` * * @exampleSql Query referenced tables with spaces in their names * ```sql * create table * "orchestral sections" (id int8 primary key, name text); * create table * "musical instruments" ( * id int8 primary key, * section_id int8 not null references "orchestral sections", * name text * ); * * insert into * "orchestral sections" (id, name) * values * (1, 'strings'), * (2, 'woodwinds'); * insert into * "musical instruments" (id, section_id, name) * values * (1, 2, 'flute'), * (2, 1, 'violin'); * ``` * * @exampleResponse Query referenced tables with spaces in their names * ```json * { * "data": [ * { * "name": "strings", * "musical instruments": [ * { * "name": "violin" * } * ] * }, * { * "name": "woodwinds", * "musical instruments": [ * { * "name": "flute" * } * ] * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Query referenced tables through a join table * If you're in a situation where your tables are **NOT** directly * related, but instead are joined by a _join table_, you can still use * the `select()` method to query the related data. The join table needs * to have the foreign keys as part of its composite primary key. * * @example Query referenced tables through a join table * ```ts * const { data, error } = await supabase * .from('users') * .select(` * name, * teams ( * name * ) * `) * * ``` * @exampleSql Query referenced tables through a join table * ```sql * create table * users ( * id int8 primary key, * name text * ); * create table * teams ( * id int8 primary key, * name text * ); * -- join table * create table * users_teams ( * user_id int8 not null references users, * team_id int8 not null references teams, * -- both foreign keys must be part of a composite primary key * primary key (user_id, team_id) * ); * * insert into * users (id, name) * values * (1, 'Kiran'), * (2, 'Evan'); * insert into * teams (id, name) * values * (1, 'Green'), * (2, 'Blue'); * insert into * users_teams (user_id, team_id) * values * (1, 1), * (1, 2), * (2, 2); * ``` * * @exampleResponse Query referenced tables through a join table * ```json * { * "data": [ * { * "name": "Kiran", * "teams": [ * { * "name": "Green" * }, * { * "name": "Blue" * } * ] * }, * { * "name": "Evan", * "teams": [ * { * "name": "Blue" * } * ] * } * ], * "status": 200, * "statusText": "OK" * } * * ``` * * @exampleDescription Query the same referenced table multiple times * If you need to query the same referenced table twice, use the name of the * joined column to identify which join to use. You can also give each * column an alias. * * @example Query the same referenced table multiple times * ```ts * const { data, error } = await supabase * .from('messages') * .select(` * content, * from:sender_id(name), * to:receiver_id(name) * `) * * // To infer types, use the name of the table (in this case `users`) and * // the name of the foreign key constraint. * const { data, error } = await supabase * .from('messages') * .select(` * content, * from:users!messages_sender_id_fkey(name), * to:users!messages_receiver_id_fkey(name) * `) * ``` * * @exampleSql Query the same referenced table multiple times * ```sql * create table * users (id int8 primary key, name text); * * create table * messages ( * sender_id int8 not null references users, * receiver_id int8 not null references users, * content text * ); * * insert into * users (id, name) * values * (1, 'Kiran'), * (2, 'Evan'); * * insert into * messages (sender_id, receiver_id, content) * values * (1, 2, '👋'); * ``` * ``` * * @exampleResponse Query the same referenced table multiple times * ```json * { * "data": [ * { * "content": "👋", * "from": { * "name": "Kiran" * }, * "to": { * "name": "Evan" * } * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Query nested foreign tables through a join table * You can use the result of a joined table to gather data in * another foreign table. With multiple references to the same foreign * table you must specify the column on which to conduct the join. * * @example Query nested foreign tables through a join table * ```ts * const { data, error } = await supabase * .from('games') * .select(` * game_id:id, * away_team:teams!games_away_team_fkey ( * users ( * id, * name * ) * ) * `) * * ``` * * @exampleSql Query nested foreign tables through a join table * ```sql * ```sql * create table * users ( * id int8 primary key, * name text * ); * create table * teams ( * id int8 primary key, * name text * ); * -- join table * create table * users_teams ( * user_id int8 not null references users, * team_id int8 not null references teams, * * primary key (user_id, team_id) * ); * create table * games ( * id int8 primary key, * home_team int8 not null references teams, * away_team int8 not null references teams, * name text * ); * * insert into users (id, name) * values * (1, 'Kiran'), * (2, 'Evan'); * insert into * teams (id, name) * values * (1, 'Green'), * (2, 'Blue'); * insert into * users_teams (user_id, team_id) * values * (1, 1), * (1, 2), * (2, 2); * insert into * games (id, home_team, away_team, name) * values * (1, 1, 2, 'Green vs Blue'), * (2, 2, 1, 'Blue vs Green'); * ``` * * @exampleResponse Query nested foreign tables through a join table * ```json * { * "data": [ * { * "game_id": 1, * "away_team": { * "users": [ * { * "id": 1, * "name": "Kiran" * }, * { * "id": 2, * "name": "Evan" * } * ] * } * }, * { * "game_id": 2, * "away_team": { * "users": [ * { * "id": 1, * "name": "Kiran" * } * ] * } * } * ], * "status": 200, * "statusText": "OK" * } * * ``` * * @exampleDescription Filtering through referenced tables * If the filter on a referenced table's column is not satisfied, the referenced * table returns `[]` or `null` but the parent table is not filtered out. * If you want to filter out the parent table rows, use the `!inner` hint * * @example Filtering through referenced tables * ```ts * const { data, error } = await supabase * .from('instruments') * .select('name, orchestral_sections(*)') * .eq('orchestral_sections.name', 'percussion') * ``` * * @exampleSql Filtering through referenced tables * ```sql * create table * orchestral_sections (id int8 primary key, name text); * create table * instruments ( * id int8 primary key, * section_id int8 not null references orchestral_sections, * name text * ); * * insert into * orchestral_sections (id, name) * values * (1, 'strings'), * (2, 'woodwinds'); * insert into * instruments (id, section_id, name) * values * (1, 2, 'flute'), * (2, 1, 'violin'); * ``` * * @exampleResponse Filtering through referenced tables * ```json * { * "data": [ * { * "name": "flute", * "orchestral_sections": null * }, * { * "name": "violin", * "orchestral_sections": null * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Querying referenced table with count * You can get the number of rows in a related table by using the * **count** property. * * @example Querying referenced table with count * ```ts * const { data, error } = await supabase * .from('orchestral_sections') * .select(`*, instruments(count)`) * ``` * * @exampleSql Querying referenced table with count * ```sql * create table orchestral_sections ( * "id" "uuid" primary key default "extensions"."uuid_generate_v4"() not null, * "name" text * ); * * create table characters ( * "id" "uuid" primary key default "extensions"."uuid_generate_v4"() not null, * "name" text, * "section_id" "uuid" references public.orchestral_sections on delete cascade * ); * * with section as ( * insert into orchestral_sections (name) * values ('strings') returning id * ) * insert into instruments (name, section_id) values * ('violin', (select id from section)), * ('viola', (select id from section)), * ('cello', (select id from section)), * ('double bass', (select id from section)); * ``` * * @exampleResponse Querying referenced table with count * ```json * [ * { * "id": "693694e7-d993-4360-a6d7-6294e325d9b6", * "name": "strings", * "instruments": [ * { * "count": 4 * } * ] * } * ] * ``` * * @exampleDescription Querying with count option * You can get the number of rows by using the * [count](/docs/reference/javascript/select#parameters) option. * * @example Querying with count option * ```ts * const { count, error } = await supabase * .from('characters') * .select('*', { count: 'exact', head: true }) * ``` * * @exampleSql Querying with count option * ```sql * create table * characters (id int8 primary key, name text); * * insert into * characters (id, name) * values * (1, 'Luke'), * (2, 'Leia'), * (3, 'Han'); * ``` * * @exampleResponse Querying with count option * ```json * { * "count": 3, * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Querying JSON data * You can select and filter data inside of * [JSON](/docs/guides/database/json) columns. Postgres offers some * [operators](/docs/guides/database/json#query-the-jsonb-data) for * querying JSON data. * * @example Querying JSON data * ```ts * const { data, error } = await supabase * .from('users') * .select(` * id, name, * address->city * `) * ``` * * @exampleSql Querying JSON data * ```sql * create table * users ( * id int8 primary key, * name text, * address jsonb * ); * * insert into * users (id, name, address) * values * (1, 'Frodo', '{"city":"Hobbiton"}'); * ``` * * @exampleResponse Querying JSON data * ```json * { * "data": [ * { * "id": 1, * "name": "Frodo", * "city": "Hobbiton" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Querying referenced table with inner join * If you don't want to return the referenced table contents, you can leave the parenthesis empty. * Like `.select('name, orchestral_sections!inner()')`. * * @example Querying referenced table with inner join * ```ts * const { data, error } = await supabase * .from('instruments') * .select('name, orchestral_sections!inner(name)') * .eq('orchestral_sections.name', 'woodwinds') * .limit(1) * ``` * * @exampleSql Querying referenced table with inner join * ```sql * create table orchestral_sections ( * "id" "uuid" primary key default "extensions"."uuid_generate_v4"() not null, * "name" text * ); * * create table instruments ( * "id" "uuid" primary key default "extensions"."uuid_generate_v4"() not null, * "name" text, * "section_id" "uuid" references public.orchestral_sections on delete cascade * ); * * with section as ( * insert into orchestral_sections (name) * values ('woodwinds') returning id * ) * insert into instruments (name, section_id) values * ('flute', (select id from section)), * ('clarinet', (select id from section)), * ('bassoon', (select id from section)), * ('piccolo', (select id from section)); * ``` * * @exampleResponse Querying referenced table with inner join * ```json * { * "data": [ * { * "name": "flute", * "orchestral_sections": {"name": "woodwinds"} * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Switching schemas per query * In addition to setting the schema during initialization, you can also switch schemas on a per-query basis. * Make sure you've set up your [database privileges and API settings](/docs/guides/api/using-custom-schemas). * * @example Switching schemas per query * ```ts * const { data, error } = await supabase * .schema('myschema') * .from('mytable') * .select() * ``` * * @exampleSql Switching schemas per query * ```sql * create schema myschema; * * create table myschema.mytable ( * id uuid primary key default gen_random_uuid(), * data text * ); * * insert into myschema.mytable (data) values ('mydata'); * ``` * * @exampleResponse Switching schemas per query * ```json * { * "data": [ * { * "id": "4162e008-27b0-4c0f-82dc-ccaeee9a624d", * "data": "mydata" * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ select>(columns?: Query, options?: { head?: boolean; count?: 'exact' | 'planned' | 'estimated' | (string & {}); }): PostgrestFilterBuilder; /** * Perform an INSERT into the table or view. * * By default, inserted rows are not returned. To return it, chain the call * with `.select()`. * * @param values - The values to insert. Pass an object to insert a single row * or an array to insert multiple rows. * * @param options - Named parameters * * @param options.count - Count algorithm to use to count inserted rows. * * `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the * hood. * * `"planned"`: Approximated but fast count algorithm. Uses the Postgres * statistics under the hood. * * `"estimated"`: Uses exact count for low numbers and planned count for high * numbers. * * @param options.defaultToNull - Make missing fields default to `null`. * Otherwise, use the default value for the column. Only applies for bulk * inserts. * * @category Database * * @example Create a record * ```ts * const { error } = await supabase * .from('countries') * .insert({ id: 1, name: 'Mordor' }) * ``` * * @exampleSql Create a record * ```sql * create table * countries (id int8 primary key, name text); * ``` * * @exampleResponse Create a record * ```json * { * "status": 201, * "statusText": "" * } * ``` * * @exampleDescription Handling errors * `error.hint` from Postgres often contains the actionable fix (e.g. `"Grant the required privileges to the current role with: GRANT INSERT ON public.countries TO anon;"` for a `42501` permission-denied error). Log the full `error` object so it isn't hidden behind `error.message`. * * @example Handling errors * ```js * const { error } = await supabase.from('countries').insert({ id: 1, name: 'Mordor' }) * if (error) console.error(error) * ``` * * @example Create a record and return it * ```ts * const { data, error } = await supabase * .from('countries') * .insert({ id: 1, name: 'Mordor' }) * .select() * ``` * * @exampleSql Create a record and return it * ```sql * create table * countries (id int8 primary key, name text); * ``` * * @exampleResponse Create a record and return it * ```json * { * "data": [ * { * "id": 1, * "name": "Mordor" * } * ], * "status": 201, * "statusText": "" * } * ``` * * @exampleDescription Bulk create * A bulk create operation is handled in a single transaction. * If any of the inserts fail, none of the rows are inserted. * * @example Bulk create * ```ts * const { error } = await supabase * .from('countries') * .insert([ * { id: 1, name: 'Mordor' }, * { id: 1, name: 'The Shire' }, * ]) * ``` * * @exampleSql Bulk create * ```sql * create table * countries (id int8 primary key, name text); * ``` * * @exampleResponse Bulk create * ```json * { * "error": { * "code": "23505", * "details": "Key (id)=(1) already exists.", * "hint": null, * "message": "duplicate key value violates unique constraint \"countries_pkey\"" * }, * "status": 409, * "statusText": "" * } * ``` */ insert(values: RejectExcessProperties | RejectExcessProperties[], { count, defaultToNull, }?: { count?: 'exact' | 'planned' | 'estimated' | (string & {}); defaultToNull?: boolean; }): PostgrestFilterBuilder; /** * Perform an UPSERT on the table or view. Depending on the column(s) passed * to `onConflict`, `.upsert()` allows you to perform the equivalent of * `.insert()` if a row with the corresponding `onConflict` columns doesn't * exist, or if it does exist, perform an alternative action depending on * `ignoreDuplicates`. * * By default, upserted rows are not returned. To return it, chain the call * with `.select()`. * * @param values - The values to upsert with. Pass an object to upsert a * single row or an array to upsert multiple rows. * * @param options - Named parameters * * @param options.onConflict - Comma-separated UNIQUE column(s) to specify how * duplicate rows are determined. Two rows are duplicates if all the * `onConflict` columns are equal. * * @param options.ignoreDuplicates - If `true`, duplicate rows are ignored. If * `false`, duplicate rows are merged with existing rows. * * @param options.count - Count algorithm to use to count upserted rows. * * `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the * hood. * * `"planned"`: Approximated but fast count algorithm. Uses the Postgres * statistics under the hood. * * `"estimated"`: Uses exact count for low numbers and planned count for high * numbers. * * @param options.defaultToNull - Make missing fields default to `null`. * Otherwise, use the default value for the column. This only applies when * inserting new rows, not when merging with existing rows under * `ignoreDuplicates: false`. This also only applies when doing bulk upserts. * * @example Upsert a single row using a unique key * ```ts * // Upserting a single row, overwriting based on the 'username' unique column * const { data, error } = await supabase * .from('users') * .upsert({ username: 'supabot' }, { onConflict: 'username' }) * * // Example response: * // { * // data: [ * // { id: 4, message: 'bar', username: 'supabot' } * // ], * // error: null * // } * ``` * * @example Upsert with conflict resolution and exact row counting * ```ts * // Upserting and returning exact count * const { data, error, count } = await supabase * .from('users') * .upsert( * { * id: 3, * message: 'foo', * username: 'supabot' * }, * { * onConflict: 'username', * count: 'exact' * } * ) * * // Example response: * // { * // data: [ * // { * // id: 42, * // handle: "saoirse", * // display_name: "Saoirse" * // } * // ], * // count: 1, * // error: null * // } * ``` * * @category Database * * @remarks * - Primary keys must be included in `values` to use upsert. * * @example Upsert your data * ```ts * const { data, error } = await supabase * .from('instruments') * .upsert({ id: 1, name: 'piano' }) * .select() * ``` * * @exampleSql Upsert your data * ```sql * create table * instruments (id int8 primary key, name text); * * insert into * instruments (id, name) * values * (1, 'harpsichord'); * ``` * * @exampleResponse Upsert your data * ```json * { * "data": [ * { * "id": 1, * "name": "piano" * } * ], * "status": 201, * "statusText": "" * } * ``` * * @exampleDescription Handling errors * `error.hint` from Postgres often contains the actionable fix (e.g. `"Grant the required privileges to the current role with: GRANT INSERT, UPDATE ON public.instruments TO anon;"` for a `42501` permission-denied error). Log the full `error` object so it isn't hidden behind `error.message`. * * @example Handling errors * ```js * const { data, error } = await supabase.from('instruments').upsert({ id: 1, name: 'piano' }).select() * if (error) console.error(error) * ``` * * @example Bulk Upsert your data * ```ts * const { data, error } = await supabase * .from('instruments') * .upsert([ * { id: 1, name: 'piano' }, * { id: 2, name: 'harp' }, * ]) * .select() * ``` * * @exampleSql Bulk Upsert your data * ```sql * create table * instruments (id int8 primary key, name text); * * insert into * instruments (id, name) * values * (1, 'harpsichord'); * ``` * * @exampleResponse Bulk Upsert your data * ```json * { * "data": [ * { * "id": 1, * "name": "piano" * }, * { * "id": 2, * "name": "harp" * } * ], * "status": 201, * "statusText": "" * } * ``` * * @exampleDescription Upserting into tables with constraints * In the following query, `upsert()` implicitly uses the `id` * (primary key) column to determine conflicts. If there is no existing * row with the same `id`, `upsert()` inserts a new row, which * will fail in this case as there is already a row with `handle` `"saoirse"`. * Using the `onConflict` option, you can instruct `upsert()` to use * another column with a unique constraint to determine conflicts. * * @example Upserting into tables with constraints * ```ts * const { data, error } = await supabase * .from('users') * .upsert({ id: 42, handle: 'saoirse', display_name: 'Saoirse' }) * .select() * ``` * * @exampleSql Upserting into tables with constraints * ```sql * create table * users ( * id int8 generated by default as identity primary key, * handle text not null unique, * display_name text * ); * * insert into * users (id, handle, display_name) * values * (1, 'saoirse', null); * ``` * * @exampleResponse Upserting into tables with constraints * ```json * { * "error": { * "code": "23505", * "details": "Key (handle)=(saoirse) already exists.", * "hint": null, * "message": "duplicate key value violates unique constraint \"users_handle_key\"" * }, * "status": 409, * "statusText": "" * } * ``` */ upsert(values: RejectExcessProperties | RejectExcessProperties[], { onConflict, ignoreDuplicates, count, defaultToNull, }?: { onConflict?: string; ignoreDuplicates?: boolean; count?: 'exact' | 'planned' | 'estimated' | (string & {}); defaultToNull?: boolean; }): PostgrestFilterBuilder; /** * Perform an UPDATE on the table or view. * * By default, updated rows are not returned. To return it, chain the call * with `.select()` after filters. * * @param values - The values to update with * * @param options - Named parameters * * @param options.count - Count algorithm to use to count updated rows. * * `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the * hood. * * `"planned"`: Approximated but fast count algorithm. Uses the Postgres * statistics under the hood. * * `"estimated"`: Uses exact count for low numbers and planned count for high * numbers. * * @category Database * * @remarks * - `update()` should always be combined with [Filters](/docs/reference/javascript/using-filters) to target the item(s) you wish to update. * * @example Updating your data * ```ts * const { error } = await supabase * .from('instruments') * .update({ name: 'piano' }) * .eq('id', 1) * ``` * * @exampleSql Updating your data * ```sql * create table * instruments (id int8 primary key, name text); * * insert into * instruments (id, name) * values * (1, 'harpsichord'); * ``` * * @exampleResponse Updating your data * ```json * { * "status": 204, * "statusText": "" * } * ``` * * @exampleDescription Handling errors * `error.hint` from Postgres often contains the actionable fix (e.g. `"Grant the required privileges to the current role with: GRANT UPDATE ON public.instruments TO anon;"` for a `42501` permission-denied error). Log the full `error` object so it isn't hidden behind `error.message`. * * @example Handling errors * ```js * const { error } = await supabase.from('instruments').update({ name: 'piano' }).eq('id', 1) * if (error) console.error(error) * ``` * * @example Update a record and return it * ```ts * const { data, error } = await supabase * .from('instruments') * .update({ name: 'piano' }) * .eq('id', 1) * .select() * ``` * * @exampleSql Update a record and return it * ```sql * create table * instruments (id int8 primary key, name text); * * insert into * instruments (id, name) * values * (1, 'harpsichord'); * ``` * * @exampleResponse Update a record and return it * ```json * { * "data": [ * { * "id": 1, * "name": "piano" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Updating JSON data * Postgres offers some * [operators](/docs/guides/database/json#query-the-jsonb-data) for * working with JSON data. Currently, it is only possible to update the entire JSON document. * * @example Updating JSON data * ```ts * const { data, error } = await supabase * .from('users') * .update({ * address: { * street: 'Melrose Place', * postcode: 90210 * } * }) * .eq('address->postcode', 90210) * .select() * ``` * * @exampleSql Updating JSON data * ```sql * create table * users ( * id int8 primary key, * name text, * address jsonb * ); * * insert into * users (id, name, address) * values * (1, 'Michael', '{ "postcode": 90210 }'); * ``` * * @exampleResponse Updating JSON data * ```json * { * "data": [ * { * "id": 1, * "name": "Michael", * "address": { * "street": "Melrose Place", * "postcode": 90210 * } * } * ], * "status": 200, * "statusText": "OK" * } * ``` */ update(values: RejectExcessProperties, { count, }?: { count?: 'exact' | 'planned' | 'estimated' | (string & {}); }): PostgrestFilterBuilder; /** * Perform a DELETE on the table or view. * * By default, deleted rows are not returned. To return it, chain the call * with `.select()` after filters. * * @param options - Named parameters * * @param options.count - Count algorithm to use to count deleted rows. * * `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the * hood. * * `"planned"`: Approximated but fast count algorithm. Uses the Postgres * statistics under the hood. * * `"estimated"`: Uses exact count for low numbers and planned count for high * numbers. * * @category Database * * @remarks * - `delete()` should always be combined with [filters](/docs/reference/javascript/using-filters) to target the item(s) you wish to delete. * - If you use `delete()` with filters and you have * [RLS](/docs/learn/auth-deep-dive/auth-row-level-security) enabled, only * rows visible through `SELECT` policies are deleted. Note that by default * no rows are visible, so you need at least one `SELECT`/`ALL` policy that * makes the rows visible. * - When using `delete().in()`, specify an array of values to target multiple rows with a single query. This is particularly useful for batch deleting entries that share common criteria, such as deleting users by their IDs. Ensure that the array you provide accurately represents all records you intend to delete to avoid unintended data removal. * * @example Delete a single record * ```ts * const response = await supabase * .from('countries') * .delete() * .eq('id', 1) * ``` * * @exampleSql Delete a single record * ```sql * create table * countries (id int8 primary key, name text); * * insert into * countries (id, name) * values * (1, 'Mordor'); * ``` * * @exampleResponse Delete a single record * ```json * { * "status": 204, * "statusText": "" * } * ``` * * @exampleDescription Handling errors * `error.hint` from Postgres often contains the actionable fix (e.g. `"Grant the required privileges to the current role with: GRANT DELETE ON public.countries TO anon;"` for a `42501` permission-denied error). Log the full `error` object so it isn't hidden behind `error.message`. * * @example Handling errors * ```js * const { error } = await supabase.from('countries').delete().eq('id', 1) * if (error) console.error(error) * ``` * * @example Delete a record and return it * ```ts * const { data, error } = await supabase * .from('countries') * .delete() * .eq('id', 1) * .select() * ``` * * @exampleSql Delete a record and return it * ```sql * create table * countries (id int8 primary key, name text); * * insert into * countries (id, name) * values * (1, 'Mordor'); * ``` * * @exampleResponse Delete a record and return it * ```json * { * "data": [ * { * "id": 1, * "name": "Mordor" * } * ], * "status": 200, * "statusText": "OK" * } * ``` * * @example Delete multiple records * ```ts * const response = await supabase * .from('countries') * .delete() * .in('id', [1, 2, 3]) * ``` * * @exampleSql Delete multiple records * ```sql * create table * countries (id int8 primary key, name text); * * insert into * countries (id, name) * values * (1, 'Rohan'), (2, 'The Shire'), (3, 'Mordor'); * ``` * * @exampleResponse Delete multiple records * ```json * { * "status": 204, * "statusText": "" * } * ``` */ delete({ count, }?: { count?: 'exact' | 'planned' | 'estimated' | (string & {}); }): PostgrestFilterBuilder; } type IsMatchingArgs = [FnArgs] extends [Record] ? PassedArgs extends Record ? true : false : keyof PassedArgs extends keyof FnArgs ? PassedArgs extends FnArgs ? true : false : false; type MatchingFunctionArgs = Fn extends { Args: infer A extends GenericFunction['Args']; } ? IsMatchingArgs extends true ? Fn : never : false; type FindMatchingFunctionByArgs = FnUnion extends infer Fn extends GenericFunction ? MatchingFunctionArgs : false; type TablesAndViews = Schema['Tables'] & Exclude; type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; type LastOf = UnionToIntersection T : never> extends () => infer R ? R : never; type IsAny = 0 extends 1 & T ? true : false; type ExactMatch = [T] extends [S] ? ([S] extends [T] ? true : false) : false; type ExtractExactFunction = Fns extends infer F ? F extends GenericFunction ? ExactMatch extends true ? F : never : never : never; type IsNever = [T] extends [never] ? true : false; type RpcFunctionNotFound = { Row: any; Result: { error: true; } & "Couldn't infer function definition matching provided arguments"; RelationName: FnName; Relationships: null; }; type CrossSchemaError = { error: true; } & `Function returns SETOF from a different schema ('${TableRef}'). Use .overrideTypes() to specify the return type explicitly.`; type GetRpcFunctionFilterBuilderByArgs = { 0: Schema['Functions'][FnName]; 1: IsAny extends true ? any : IsNever extends true ? IsNever> extends true ? LastOf : ExtractExactFunction : Args extends Record ? LastOf : Args extends GenericFunction['Args'] ? IsNever>> extends true ? LastOf : LastOf> : ExtractExactFunction extends GenericFunction ? ExtractExactFunction : any; }[1] extends infer Fn ? IsAny extends true ? { Row: any; Result: any; RelationName: FnName; Relationships: null; } : Fn extends GenericFunction ? { Row: Fn['SetofOptions'] extends GenericSetofOption ? Fn['SetofOptions']['to'] extends keyof TablesAndViews ? TablesAndViews[Fn['SetofOptions']['to']]['Row'] : Fn['Returns'] extends any[] ? Fn['Returns'][number] extends Record ? Fn['Returns'][number] : CrossSchemaError : Fn['Returns'] extends Record ? Fn['Returns'] : CrossSchemaError : Fn['Returns'] extends any[] ? Fn['Returns'][number] extends Record ? Fn['Returns'][number] : never : Fn['Returns'] extends Record ? Fn['Returns'] : never; Result: Fn['SetofOptions'] extends GenericSetofOption ? Fn['SetofOptions']['isSetofReturn'] extends true ? Fn['SetofOptions']['isOneToOne'] extends true ? Fn['Returns'][] : Fn['Returns'] : Fn['Returns'] : Fn['Returns']; RelationName: Fn['SetofOptions'] extends GenericSetofOption ? Fn['SetofOptions']['to'] : FnName; Relationships: Fn['SetofOptions'] extends GenericSetofOption ? Fn['SetofOptions']['to'] extends keyof Schema['Tables'] ? Schema['Tables'][Fn['SetofOptions']['to']]['Relationships'] : Fn['SetofOptions']['to'] extends keyof Schema['Views'] ? Schema['Views'][Fn['SetofOptions']['to']]['Relationships'] : null : null; } : Fn extends false ? RpcFunctionNotFound : RpcFunctionNotFound : RpcFunctionNotFound; /** * PostgREST client. * * @typeParam Database - Types for the schema from the [type * generator](https://supabase.com/docs/reference/javascript/next/typescript-support) * * @typeParam SchemaName - Postgres schema to switch to. Must be a string * literal, the same one passed to the constructor. If the schema is not * `"public"`, this must be supplied manually. */ declare class PostgrestClient = 'public' extends keyof Omit ? 'public' : string & keyof Omit, Schema extends GenericSchema = Omit[SchemaName] extends GenericSchema ? Omit[SchemaName] : any> { url: string; headers: Headers; schemaName?: SchemaName; fetch?: Fetch; urlLengthLimit: number; retry?: boolean; /** * Creates a PostgREST client. * * @param url - URL of the PostgREST endpoint * @param options - Named parameters * @param options.headers - Custom headers * @param options.schema - Postgres schema to switch to * @param options.fetch - Custom fetch * @param options.timeout - Optional timeout in milliseconds for all requests. When set, requests will automatically abort after this duration to prevent indefinite hangs. * @param options.urlLengthLimit - Maximum URL length in characters before warnings/errors are triggered. Defaults to 8000. * @param options.retry - Enable or disable automatic retries for transient errors. * When enabled, idempotent requests (GET, HEAD, OPTIONS) that fail with network * errors or HTTP 503/520 responses will be automatically retried up to 3 times * with exponential backoff (1s, 2s, 4s). Defaults to `true`. * @example Using supabase-js (recommended) * ```ts * import { createClient } from '@supabase/supabase-js' * * const supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key') * const { data, error } = await supabase.from('profiles').select('*') * ``` * * @category Database * * @remarks * - A `timeout` option (in milliseconds) can be set to automatically abort requests that take too long. * - A `urlLengthLimit` option (default: 8000) can be set to control when URL length warnings are included in error messages for aborted requests. * * @example Standalone import for bundle-sensitive environments * ```ts * import { PostgrestClient } from '@supabase/postgrest-js' * * const postgrest = new PostgrestClient('https://xyzcompany.supabase.co/rest/v1', { * headers: { apikey: 'your-publishable-key' }, * schema: 'public', * timeout: 30000, // 30 second timeout * }) * ``` */ constructor(url: string, { headers, schema, fetch, timeout, urlLengthLimit, retry, }?: { headers?: HeadersInit; schema?: SchemaName; fetch?: Fetch; timeout?: number; urlLengthLimit?: number; retry?: boolean; }); /** * Perform a query on a table or a view. * * @param relation - The table or view name to query * * @category Database */ from(relation: TableName): PostgrestQueryBuilder; from(relation: ViewName): PostgrestQueryBuilder; /** * Select a schema to query or perform an function (rpc) call. * * The schema needs to be on the list of exposed schemas inside Supabase. * * @param schema - The schema to query * * @category Database */ schema>(schema: DynamicSchema): PostgrestClient; /** * Perform a function call. * * @param fn - The function name to call * @param args - The arguments to pass to the function call * @param options - Named parameters * @param options.head - When set to `true`, `data` will not be returned. * Useful if you only need the count. * @param options.get - When set to `true`, the function will be called with * read-only access mode. * @param options.count - Count algorithm to use to count rows returned by the * function. Only applicable for [set-returning * functions](https://www.postgresql.org/docs/current/functions-srf.html). * * `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the * hood. * * `"planned"`: Approximated but fast count algorithm. Uses the Postgres * statistics under the hood. * * `"estimated"`: Uses exact count for low numbers and planned count for high * numbers. * * @example * ```ts * // For cross-schema functions where type inference fails, use overrideTypes: * const { data } = await supabase * .schema('schema_b') * .rpc('function_a', {}) * .overrideTypes<{ id: string; user_id: string }[]>() * ``` * * @category Database * * @example Call a Postgres function without arguments * ```ts * const { data, error } = await supabase.rpc('hello_world') * ``` * * @exampleSql Call a Postgres function without arguments * ```sql * create function hello_world() returns text as $$ * select 'Hello world'; * $$ language sql; * ``` * * @exampleResponse Call a Postgres function without arguments * ```json * { * "data": "Hello world", * "status": 200, * "statusText": "OK" * } * ``` * * @example Call a Postgres function with arguments * ```ts * const { data, error } = await supabase.rpc('echo', { say: '👋' }) * ``` * * @exampleSql Call a Postgres function with arguments * ```sql * create function echo(say text) returns text as $$ * select say; * $$ language sql; * ``` * * @exampleResponse Call a Postgres function with arguments * ```json * { * "data": "👋", * "status": 200, * "statusText": "OK" * } * * ``` * * @exampleDescription Bulk processing * You can process large payloads by passing in an array as an argument. * * @example Bulk processing * ```ts * const { data, error } = await supabase.rpc('add_one_each', { arr: [1, 2, 3] }) * ``` * * @exampleSql Bulk processing * ```sql * create function add_one_each(arr int[]) returns int[] as $$ * select array_agg(n + 1) from unnest(arr) as n; * $$ language sql; * ``` * * @exampleResponse Bulk processing * ```json * { * "data": [ * 2, * 3, * 4 * ], * "status": 200, * "statusText": "OK" * } * ``` * * @exampleDescription Call a Postgres function with filters * Postgres functions that return tables can also be combined with [Filters](/docs/reference/javascript/using-filters) and [Modifiers](/docs/reference/javascript/using-modifiers). * * @example Call a Postgres function with filters * ```ts * const { data, error } = await supabase * .rpc('list_stored_countries') * .eq('id', 1) * .single() * ``` * * @exampleSql Call a Postgres function with filters * ```sql * create table * countries (id int8 primary key, name text); * * insert into * countries (id, name) * values * (1, 'Rohan'), * (2, 'The Shire'); * * create function list_stored_countries() returns setof countries as $$ * select * from countries; * $$ language sql; * ``` * * @exampleResponse Call a Postgres function with filters * ```json * { * "data": { * "id": 1, * "name": "Rohan" * }, * "status": 200, * "statusText": "OK" * } * ``` * * @example Call a read-only Postgres function * ```ts * const { data, error } = await supabase.rpc('hello_world', undefined, { get: true }) * ``` * * @exampleSql Call a read-only Postgres function * ```sql * create function hello_world() returns text as $$ * select 'Hello world'; * $$ language sql; * ``` * * @exampleResponse Call a read-only Postgres function * ```json * { * "data": "Hello world", * "status": 200, * "statusText": "OK" * } * ``` */ rpc = GetRpcFunctionFilterBuilderByArgs>(fn: FnName, args?: Args, { head, get, count, }?: { head?: boolean; get?: boolean; count?: 'exact' | 'planned' | 'estimated' | (string & {}); }): PostgrestFilterBuilder; } /** * Database 模块封装(#89098) * * 数据面 `/.cloud/database/rest/**`,PostgREST 语义(设计文档 §6.1)。 * * ============================================================================ * 为什么有这层 facade,而不是把 vendor 的 PostgrestClient 裸抛给应用 * * 其余三个模块(auth/storage/llm)都在 SDK 侧收一层 facade,公开自命名类型、 * 藏住 vendor —— database 若独独裸透传,会有两个后果: * * 1. vendor 类型泄漏。`index.ts` 明确规定不导出 `PostgrestClient` 之类的名字 * (泄漏后消费方会写出依赖第三方命名的代码,之后换不掉)。裸抛就等于把 * 整个 vendor 类型面暴露到公开 d.ts。 * 2. 入口失控。vendor 的 `.schema()` 能切到任意 schema,但数据面只放行 public * (网关 `provider/tcb/data.go` 的 deniedSchemas 会拒 auth、pg_ 前缀、information_schema)。 * 裸抛会让应用写出「类型上成立、运行期必被网关拒」的代码。 * * 因此这里收一层**薄** facade:只转发 `from` / `rpc`(保留 vendor builder 的 * 全部链式与 `{data,error}` 语义,体验对齐 supabase-js),去掉 `.schema()`, * 并以自己的泛型入口 `Database` 承接强类型(对齐 supabase 的 createClient)。 * * 这里**没有自研请求层**,底座仍是 `src/vendor/postgrest`。原因: * - 官方 `@cloudbase/js-sdk` 用不了 —— `getBaseEndPoint` 会把配置值截取到 host, * 我们的 `/.cloud/database/rest` 前缀被丢弃(`runtime-auth-design.md` §4.1)。 * - vendor 的客户端只把 base URL 当字符串保存,路径原样保留,正是所需行为。 * ============================================================================ */ /** * WorkBuddy 数据库模块。PostgREST 语义、`{ data, error }` 信封, * 查询构造器与 supabase-js 的 `.from()` 完全一致。 * * 泛型 `Database` 承接 [类型生成器](https://supabase.com/docs/guides/api/rest/generating-types) * 产出的 schema 类型,用法与 supabase 的 `createClient()` 一致; * 不传时退化为无类型(`any` Row),运行期行为不变。 */ declare class WorkBuddyDatabaseModule { /** * 底层 PostgREST 客户端。刻意保持 private —— 对外只暴露 `from` / `rpc`, * 不让 vendor 客户端(及其 `.schema()` 等入口)泄漏到应用。 */ private readonly client; constructor(config: CloudRuntimeConfig, fetch: typeof globalThis.fetch); /** * 在一张表或视图上发起查询。返回值即 supabase-js 的查询构造器, * 支持 `select/insert/upsert/update/delete` 及全部 filter/transform 链, * `await` 得到 `{ data, error }`。语义与 supabase-js `.from()` 一致。 * * 只放行 public schema —— 不提供 `.schema()` 切换(数据面会拒系统 schema)。 */ readonly from: PostgrestClient['from']; /** * 调用一个 PostgreSQL 函数(RPC)。语义同 supabase-js `.rpc()`: * 返回可继续 `.select()/.order()/.limit()` 的构造器(集合返回型函数)。 */ readonly rpc: PostgrestClient['rpc']; /** * 归一化后的数据面基址(`/.cloud/database/rest`,无尾斜杠)。 * 仅用于诊断/契约测试;应用不应据此手拼请求。 */ get url(): string; } /** * OpenAI-compatible type definitions for the LLM module (#89098). * * These are the minimal subset of OpenAI Chat Completions types needed for * browser SDK consumers. Defined locally — never pull in the `openai` runtime * package (it carries Bearer auth semantics that conflict with the shared * fetch's publishableKey model). */ /** * Context-window object shape from the upstream product contract * (`ModelContextWindow` in packages/product). * * `PublicLLMModel.contextWindow` is a controlled union: the server sends * either a plain number (e.g. `200000`) or this object form. Unknown * sub-fields are preserved as-is so that upstream contract evolution does * not lose information on the SDK side either. */ interface PublicModelContextWindow { /** Supported context lengths, e.g. `[200000, 1000000]`. */ supportedLengths?: number[]; /** Default context length among `supportedLengths`. */ defaultLength?: number; /** Unknown sub-fields are preserved for forward compatibility. */ [key: string]: unknown; } /** * Public model directory entry returned by `cloud.llm.models.list()`. * * This is a browser-safe projection. It contains only display metadata, * explicit capabilities, limits and public pricing supplied by GenieBaas. * Sensitive connection and credential configuration is not included in the * directory. Optional capability or limit fields being absent means "unknown", * not "unsupported" or zero. */ interface PublicLLMModel { /** Use this exact value as `model` in `chat.completions.create()`. */ id: string; /** Display name; the service falls back to `id` when no name is configured. */ name: string; provider?: string; vendor?: string; family?: string; version?: string; description?: string; descriptionZh?: string; descriptionEn?: string; iconUrl?: string; /** * Context window: a plain number (e.g. `200000`) or the object form from * the upstream product contract. Absent means the server did not provide * a valid value (invalid forms are dropped, the model entry is kept). */ contextWindow?: number | PublicModelContextWindow; maxInputTokens?: number; maxOutputTokens?: number; modalities?: PublicModelModalities; capabilities?: PublicModelCapabilities; /** * Whether the directory currently marks this model as selectable. * * This is a directory selection hint; the actual call result is * determined by the request response, not by this field. */ enabled: boolean; /** * Explicit disabled flag aligned with the console `disabled` field. * Present only when the server explicitly provides it: * - `disabled: true` → model is disabled, `enabled` will be `false` * - `disabled: false` → model is explicitly enabled, `enabled` will be `true` * - absent → server did not provide; `enabled` defaults to `true` */ disabled?: boolean; isDefault?: boolean; sortOrder?: number; pricing?: PublicModelPricing; /** Credit display text, e.g. "2 credits/1K tokens". */ credits?: string; /** Token threshold for starting context processing. */ maxAllowedSize?: number; /** Whether multimodal capability is disabled. */ disabledMultimodal?: boolean; /** Whether the model supports image input. */ supportsImages?: boolean; /** Whether the model supports tool calls. */ supportsToolCall?: boolean; /** Whether the model supports reasoning. */ supportsReasoning?: boolean; /** Whether the model is reasoning-only. */ onlyReasoning?: boolean; /** Reasoning configuration; sparse sub-fields are all optional. */ reasoning?: PublicModelReasoning; /** Sampling temperature. */ temperature?: number; /** Top-K sampling parameter. Field name kept as-is from productConfig (snake_case). */ top_k?: number; /** Top-P (nucleus) sampling parameter. Field name kept as-is from productConfig (snake_case). */ top_p?: number; /** Repetition penalty parameter. Field name kept as-is from productConfig (snake_case). */ repetition_penalty?: number; } /** * Public reasoning configuration for a model. * * Sparse sub-fields: present only when the server explicitly provides them. * Explicit `false` is preserved and distinct from absence. */ interface PublicModelReasoning { /** Reasoning effort level (legacy, prefer defaultEffort). */ effort?: string; /** Default effort level shown in UI. */ defaultEffort?: string; /** Supported effort levels for UI display. */ supportedEfforts?: string[]; /** Reasoning summary mode. */ summary?: 'auto' | 'concise' | 'detailed'; /** Whether the user can disable thinking. */ canDisableThinking?: boolean; } interface PublicModelModalities { input?: string[]; output?: string[]; } interface PublicModelCapabilities { chat?: boolean; vision?: boolean; audioInput?: boolean; audioOutput?: boolean; toolCalling?: boolean; streaming?: boolean; jsonMode?: boolean; } /** Public display pricing only; balances, discounts and internal costs are excluded. */ interface PublicModelPricing { currency?: string; inputPerMillionTokens?: number; outputPerMillionTokens?: number; displayText?: string; } type ChatCompletionRole = 'system' | 'user' | 'assistant' | 'tool'; interface ChatCompletionTextContentPart { type: 'text'; text: string; } interface ChatCompletionImageContentPart { type: 'image_url'; image_url: { url: string; detail?: 'auto' | 'low' | 'high'; }; } type ChatCompletionContentPart = ChatCompletionTextContentPart | ChatCompletionImageContentPart; type ChatCompletionMessageContent = string | ChatCompletionContentPart[]; interface ChatCompletionMessage { role: ChatCompletionRole; content: ChatCompletionMessageContent | null; name?: string; tool_call_id?: string; } interface ChatCompletionToolCall { id: string; type: 'function'; function: { name: string; arguments: string; }; } interface ChatCompletionAssistantMessage { role: 'assistant'; content: string | null; tool_calls?: ChatCompletionToolCall[]; } type ChatCompletionRequestMessage = ChatCompletionMessage | ChatCompletionAssistantMessage; interface ChatCompletionStreamOptions { include_usage?: boolean; } interface ChatCompletionTool { type: 'function'; function: { name: string; description?: string; parameters?: Record; strict?: boolean; }; } type ChatCompletionToolChoice = 'none' | 'auto' | 'required' | { type: 'function'; function: { name: string; }; }; interface ChatCompletionResponseFormat { type: 'json_object' | 'text'; } interface ChatCompletionCreateParams { model: string; /** * ModelHub requires the first message to be an application-owned system prompt. * Include subsequent user/assistant turns after it; the SDK preserves this order. */ messages: ChatCompletionRequestMessage[]; /** * Must be `true`. The API only supports streaming chat completions; the * SDK rejects any value that is not `true` before sending a request. */ stream: true; stream_options?: ChatCompletionStreamOptions; temperature?: number; top_p?: number; /** * Not part of the OpenAI contract; forwarded to ModelHub's `ChatReq.repetition_penalty`. * Only takes effect on models that support it — see the `repetition_penalty` hint on the * corresponding `PublicLLMModel` from `models.list()`. */ repetition_penalty?: number; max_tokens?: number; max_completion_tokens?: number; stop?: string | string[]; presence_penalty?: number; frequency_penalty?: number; seed?: number; response_format?: ChatCompletionResponseFormat; tools?: ChatCompletionTool[]; tool_choice?: ChatCompletionToolChoice; parallel_tool_calls?: boolean; reasoning_effort?: 'minimal' | 'low' | 'medium' | 'high'; /** Caller-provided AbortSignal to cancel the request. */ signal?: AbortSignal; /** Application-managed conversation identifier sent as the X-Conversation-ID header. */ conversationId?: string; } /** * Alias for the streaming chat completion parameters. * * `stream: true` is required by the API contract; non-streaming is unsupported. */ interface ChatCompletionCreateParamsStreaming extends ChatCompletionCreateParams { stream: true; } interface CompletionUsage { prompt_tokens: number; completion_tokens: number; total_tokens: number; /** * Optional reasoning-token breakdown reported by the model provider. * * Presence and semantics depend entirely on the upstream provider and * model; the SDK surfaces it when present but does not interpret or * validate it. It is diagnostic metadata, not a billing fact. */ reasoning_tokens?: number; /** * Optional prompt-prefix cache hit/miss metadata reported by the provider. * * Field names and shapes vary across providers; the SDK only forwards * the values it receives. Absence does not mean caching is unsupported. */ prompt_tokens_details?: Record; /** * Optional completion-token breakdown metadata reported by the provider. * * Same caveat as `prompt_tokens_details`: provider-specific diagnostic * metadata, not a billing fact. */ completion_tokens_details?: Record; /** * Optional provider-specific credit/quota diagnostic reported by some * upstream gateways. The SDK forwards it when present; it is not a * billing statement and must not be treated as authoritative cost. */ credit?: number; /** * Catch-all for additional usage-extension fields the provider may report * (e.g. cache hit/miss, region credits, tier multipliers). The SDK does * not interpret these; they are surfaced for diagnostics only. */ extra_fields?: Record; } /** * Streaming tool-call delta. * * In a streaming response, a tool call is split across multiple chunks. * The first chunk carrying a given `index` typically provides `id` and * `function.name`; subsequent chunks with the same `index` append * `function.arguments` fragments. All fields except `index` are optional * because any single chunk may carry only a partial fragment. * * The SDK forwards these deltas as-is; the business layer is responsible * for accumulating them by `index` and deciding when to execute. */ interface ChatCompletionChunkToolCall { /** Positional index of this tool call within the choice; required for delta assembly. */ index: number; id?: string; type?: 'function'; function?: { name?: string; arguments?: string; }; } interface ChatCompletionChunkDelta { role?: ChatCompletionRole; content?: string | null; /** * Reasoning/thinking content delta reported by models that expose a * separate reasoning channel. The SDK forwards it as-is; it is not * part of the assistant's visible reply and should be accumulated * and rendered separately from `content`. */ reasoning_content?: string | null; /** * Streaming tool-call deltas. Unlike the non-streaming * `ChatCompletionToolCall`, each entry is a fragment identified by * `index`; fields other than `index` are optional. */ tool_calls?: ChatCompletionChunkToolCall[]; /** * Optional legacy function-call delta placeholder. Some upstream * providers emit an empty `function_call` object as a compatibility * marker; the SDK forwards it without interpretation. */ function_call?: { name?: string; arguments?: string; } | null; /** * Optional refusal signal reported by the model. When present, the * model declined to answer; the value is a fragment of the refusal * text and should be accumulated like `content`. */ refusal?: string | null; } interface ChatCompletionChunkChoice { index: number; delta: ChatCompletionChunkDelta; finish_reason: 'stop' | 'length' | 'tool_calls' | 'content_filter' | null; logprobs?: unknown; } interface ChatCompletionChunk { id: string; object: 'chat.completion.chunk'; created: number; model: string; choices: ChatCompletionChunkChoice[]; usage?: CompletionUsage; } /** * LLM Chat Completions — POST /.cloud/llm/chat/completions (#89098) * * Supports streaming (SSE) only. The API contract requires `stream: true`; * non-streaming is not supported. The SDK rejects `stream !== true` before * any network request is issued. * * Uses the shared fetch injected by `WorkBuddyCloudClient` — never * constructs its own fetch or adds Authorization/ModelHub headers. */ /** LLM chat completions namespace. */ declare class ChatCompletionsAPI { private readonly baseUrl; private readonly fetch; constructor(baseUrl: string, fetch: typeof globalThis.fetch); /** * Create a chat completion. * * The API only supports streaming. `input.stream` must be `true`; * any other value (or omitting `stream`) is rejected before a network * request is sent. Returns an async iterable of `ChatCompletionChunk` * objects decoded from the SSE stream. */ create(input: ChatCompletionCreateParamsStreaming): AsyncIterable; create(input: ChatCompletionCreateParams): AsyncIterable; /** * Streaming chat completion — returns an async generator of chunks. * * Reads the SSE stream incrementally, yielding ChatCompletionChunk objects. * `stream_options` is passed through unchanged when present. * * The `X-Request-Id` response header is captured and attached (as * `requestId`) to every `CloudOpenAIError` thrown from within the stream: * `event: error`, chunk-embedded error, JSON parse failure, and missing * `[DONE]` interruption. Non-2xx HTTP errors continue to go through * `httpError`, which independently extracts `X-Request-Id`. * * Throws CloudOpenAIError on: * - `event: error` in the stream * - Stream ends without `[DONE]` (gateway_stream_interrupted) * - JSON parse errors in data frames * - Caller abort (AbortSignal) — exits silently, no throw */ private createStreaming; } /** * LLM Models — GET /.cloud/llm/models (#89098) * * Fetches the browser-safe public model directory for this application, * preserving its explicit public fields for display and selection. */ /** LLM models namespace. */ declare class ModelsAPI { private readonly baseUrl; private readonly fetch; constructor(baseUrl: string, fetch: typeof globalThis.fetch); /** * List the browser-safe public model directory for this application. * * `id` is the value to pass as `model` to `chat.completions.create()`. * Optional metadata is included only when the server explicitly provides * it; absence means unknown, not unsupported. The actual call result * is determined by the response, not by the directory entry. */ list(signal?: AbortSignal): Promise; } /** * LLM 模块入口(#89098) * * 数据面 `/.cloud/llm`: * GET /.cloud/llm/models * POST /.cloud/llm/chat/completions * * 公开 API: * cloud.llm.models.list() * cloud.llm.chat.completions.create() * * 流式响应走 SSE(`text/event-stream`),分块解析由 `./llm/sse.ts` 处理。 */ declare class LlmModule { private readonly fetch; /** 该模块的数据面基址。 */ readonly baseUrl: string; /** Models namespace。 */ readonly models: ModelsAPI; /** Chat namespace。 */ readonly chat: { completions: ChatCompletionsAPI; }; constructor(config: CloudRuntimeConfig, fetch: typeof globalThis.fetch); } /** * WorkBuddy Cloud Storage facade(#89098) * * 生成应用只使用环境内固定的 `runtime` 逻辑 Bucket。物理 COS Bucket 与 envId * BasePath 由 TCB 管理,SDK 不感知;Bucket 创建/删除/清空属于可信控制面, * 不暴露给终端应用。 */ declare const RUNTIME_STORAGE_BUCKET = "runtime"; type CloudStorageFileBody = ArrayBuffer | ArrayBufferView | Blob | File | FormData | ReadableStream | URLSearchParams | string; interface CloudStorageFileOptions { cacheControl?: string; contentType?: string; upsert?: boolean; duplex?: string; metadata?: Record; } interface CloudStorageListOptions { limit?: number; offset?: number; sortBy?: { column?: string; order?: string; }; search?: string; } interface CloudStorageCursorListOptions { limit?: number; prefix?: string; cursor?: string; with_delimiter?: boolean; sortBy?: { column: 'name' | 'updated_at' | 'created_at'; order?: 'asc' | 'desc'; }; } interface CloudStorageMetadata { eTag?: string; size?: number; mimetype?: string; cacheControl?: string; lastModified?: string; contentLength?: number; httpStatusCode?: number; [key: string]: unknown; } interface CloudStorageFile { name: string; id: string | null; updated_at: string | null; created_at: string | null; last_accessed_at: string | null; metadata: CloudStorageMetadata | null; } interface CloudStorageObjectInfo { id: string; version: string; name: string; bucketId: string; createdAt: string; size?: number; cacheControl?: string; contentType?: string; etag?: string; lastModified?: string; metadata?: CloudStorageMetadata; } interface CloudStorageCursorObject { name: string; key?: string; id: string; updated_at: string; created_at: string; metadata: CloudStorageMetadata | null; } interface CloudStorageCursorFolder { name: string; key?: string; } interface CloudStorageListResult { hasNext: boolean; folders: CloudStorageCursorFolder[]; objects: CloudStorageCursorObject[]; nextCursor?: string; } interface CloudStorageError { name: string; message: string; status?: number; statusCode?: string; code?: string; cause?: unknown; } type CloudStorageResult = { data: T; error: null; } | { data: null; error: CloudStorageError; }; declare class WorkBuddyStorageDownload implements Promise> { private readonly blob; private readonly stream; readonly [Symbol.toStringTag] = "WorkBuddyStorageDownload"; constructor(blob: () => Promise>, stream: () => Promise>); asStream(): Promise>; then, TResult2 = never>(onfulfilled?: ((value: CloudStorageResult) => TResult1 | PromiseLike) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null): Promise; catch(onrejected?: ((reason: unknown) => TResult | PromiseLike) | null): Promise | TResult>; finally(onfinally?: (() => void) | null): Promise>; } declare class WorkBuddyStorageBucket { private readonly fileApi; constructor(fileApi: unknown); upload(path: string, body: CloudStorageFileBody, options?: CloudStorageFileOptions): Promise>; update(path: string, body: CloudStorageFileBody, options?: CloudStorageFileOptions): Promise>; list(prefix?: string, options?: CloudStorageListOptions): Promise>; listPage(options?: CloudStorageCursorListOptions): Promise>; info(path: string): Promise>; exists(path: string): Promise>; download(path: string): WorkBuddyStorageDownload; remove(paths: string[]): Promise>; copy(fromPath: string, toPath: string): Promise>; move(fromPath: string, toPath: string): Promise>; createSignedUrl(path: string, expiresIn?: number): Promise>; createSignedUrls(paths: string[], expiresIn?: number): Promise>>; createSignedUploadUrl(path: string, options?: { upsert?: boolean; }): Promise>; uploadToSignedUrl(path: string, token: string, body: CloudStorageFileBody, options?: CloudStorageFileOptions): Promise>; } declare class WorkBuddyStorageModule extends WorkBuddyStorageBucket { private readonly runtime; constructor(client: unknown); /** * 兼容 bucket-native 调用形态,但只允许固定的 `runtime`。 * 终端应用不能枚举、创建或切换逻辑 Bucket。 */ from(bucketID?: typeof RUNTIME_STORAGE_BUCKET): WorkBuddyStorageBucket; userPath(userID: string, relativePath: string): string; sharedPath(ownerID: string, relativePath: string): string; } /** * 客户端核心入口(#89098) */ /** * 一个应用一个实例。**初始化一次**,四个模块共用同一份配置与同一个 fetch。 * * ============================================================================ * 身份怎么流到 database / storage * * AuthModule 是**唯一**的身份来源。共享 fetch 拿到的是 * `() => this.auth.getAccessToken()` 这个函数 —— 每个请求发出前都会调它一次, * 于是登录/登出/续期后,database 与 storage 自动带上最新身份,不需要重建 * client,也不需要调用方手动搬 token。 * * ⚠️ **client 上刻意没有 `sessionToken` 之类的字段。** 会话的唯一真源是 * AuthModule 背后的 localStorage:在这里缓存一份副本会在「另一个标签页登出/ * 续期」时变脏,而拿着脏句柄去续期会让会话永久锁死(见 * modules/auth/session-manager.ts 顶部说明)。supabase-js 与 CloudBase SDK * 都是同一个结论 —— 状态归 auth,client 层只持有取值函数。 * ============================================================================ */ declare class WorkBuddyCloudClient { private readonly config; /** PostgREST 语义的数据库模块(`/.cloud/database/rest`)。 */ readonly database: WorkBuddyDatabaseModule; readonly auth: AuthModule; /** 对象存储客户端(`/.cloud/storage`)。 */ readonly storage: WorkBuddyStorageModule; readonly llm: LlmModule; constructor(config: CloudRuntimeConfig, options?: Pick); /** 归一化后的数据面基址(无尾斜杠)。 */ get endpoint(): string; /** 归一化后的环境中心 OAuth Relay 基址(无尾斜杠)。 */ get oauthRelayBaseUrl(): string; } /** * 微信小程序宿主 API 的最小占位类型(WIP,非官方 @types/miniprogram)。 * * ⚠️ 占位说明:这里只声明本适配层实际用到的字段/方法,不是完整的小程序 * 宿主类型。等接入方(专家版本以外、小白版本的发布沙盒)实际跑通真机 * 联调后,应该换成官方 `miniprogram-api-typings` 或联调中发现的真实字段 * 补齐,而不是长期依赖这份手写占位。 */ /** `wx.request` 成功回调收到的响应形状(按官方文档字段命名)。 */ interface WxRequestSuccessResult { data: string | ArrayBuffer | Record; statusCode: number; header: Record; } /** `wx.request` 失败回调收到的错误形状。 */ interface WxRequestFailResult { errMsg: string; } /** 分块传输时 `onChunkReceived` 收到的单个 chunk。 */ interface WxChunkReceivedResult { data: ArrayBuffer; } /** * `onHeadersReceived` 收到的响应头事件形状。 * * ⚠️ 占位说明:微信官方文档只保证这个事件带 `header`,不带 `statusCode`—— * 分块响应的真实状态码只能等 `success`/`fail` 在流结束时才能拿到(见 * `fetch.ts` 顶部注释),这里提前拿到的只是 header,用于避免首个 chunk * resolve 时 headers 硬编码成 `{}`。 */ interface WxHeadersReceivedResult { header: Record; } interface WxRequestTask { abort(): void; onChunkReceived(callback: (result: WxChunkReceivedResult) => void): void; offChunkReceived?(callback: (result: WxChunkReceivedResult) => void): void; onHeadersReceived?(callback: (result: WxHeadersReceivedResult) => void): void; } interface WxRequestOptions { url: string; method?: string; header?: Record; data?: string | ArrayBuffer | Record; /** * TODO(占位):官方字段名是 `enableChunked`,2023 年后基础库才支持, * 具体最低基础库版本号未核实,接入时需要在目标基础库版本上验证。 */ enableChunked?: boolean; responseType?: 'text' | 'arraybuffer'; success?(result: WxRequestSuccessResult): void; fail?(result: WxRequestFailResult): void; complete?(): void; } /** 本适配层依赖的 `wx` 全局对象最小子集。 */ interface MiniProgramWxLike { request(options: WxRequestOptions): WxRequestTask; getStorageSync(key: string): unknown; setStorageSync(key: string, value: unknown): void; removeStorageSync(key: string): void; } /** * 小程序 `fetch` 适配(对应核心 SDK `types.ts` 里 * `WorkBuddyCloudOptions.fetch` 的注入口子)。 * * 用 `wx.request` 实现四模块共用的单一出口(`src/http/fetch.ts` * `createCloudFetch` 包出来的那个 fetch)。区分两种模式: * * - 普通请求:`wx.request` 一次性拿到完整响应体。 * - 流式请求(LLM chat streaming,识别标志是请求头 * `Accept: text/event-stream`,见 `modules/llm/chat.ts:122`): * `enableChunked: true` + `onChunkReceived`,把收到的 chunk 喂进 * `polyfills.ts` 里的占位 `ReadableStream`,这样下游 * `iterSSEEvents`(`modules/llm/sse.ts`)完全不用改。 */ /** * 创建小程序版 fetch,可直接传给 `WorkBuddyCloudOptions.fetch`。 * * @param wxInstance 默认取全局 `wx`;传参主要用于单测注入 mock。 */ declare function createMiniProgramFetch(wxInstance?: MiniProgramWxLike): typeof globalThis.fetch; /** * 小程序 `CloudAuthStorage` 适配(对应核心 SDK `types.ts` 的 * "非浏览器运行时(Node、小程序)里没有 localStorage" 那条注释)。 */ /** * 用 `wx.getStorageSync`/`setStorageSync`/`removeStorageSync` 实现 `CloudAuthStorage`。 * * ⚠️ 占位说明:小程序同步存储 API 在容量超限或用户拒绝授权等场景可能抛异常, * 这里按"存储不可用时静默降级、不阻塞登录态读写"的策略吞掉异常并打印 * warning——具体降级策略(是否应该报错给上层)留给接入方按真机表现调整, * 现在只是先跑通不炸。 */ declare function createMiniProgramStorage(wxInstance?: MiniProgramWxLike): CloudAuthStorage; /** * 小程序运行时缺失的 Web API 最小占位实现(WIP)。 * * 背景:核心 SDK(`src/http/fetch.ts` 的 `createCloudFetch`)直接 `new Headers(...)`, * 不是通过注入口子拿到的 —— 这是核心 SDK 对"浏览器环境"的一处硬依赖,不是本 * 适配层能绕开的。因此本模块在小程序里补一份全局 `Headers`;`ReadableStream` * 同理,`iterSSEEvents`(`modules/llm/sse.ts`)直接调 `body.getReader()`。 * * ⚠️ 占位说明:以下两个类都只实现了 SDK 自己会用到的方法子集,不是 spec 完整 * 实现(例如 `MiniProgramReadableStream` 不支持 tee/pipeTo,也不是真正的 * 生产者暂停式 backpressure——只是按字节数设了一个缓冲上限,超限直接 abort * 整条流,把「内存持续膨胀」变成「明确报错」)。 * 只在目标小程序基础库确实缺失对应全局对象时才会安装,且没有验证过真机基础库 * 版本号 —— 接入时用 `wx.getSystemInfoSync` 之类的方式确认目标基础库是否已经 * 自带这两个全局对象,自带的话应该跳过这份占位,直接用原生实现更可靠。 */ /** 大小写不敏感的最小 Headers 占位实现。 */ declare class MiniProgramHeaders { private readonly map; constructor(init?: HeadersInit); private normalize; set(name: string, value: string): void; get(name: string): string | null; has(name: string): boolean; append(name: string, value: string): void; delete(name: string): void; forEach(callback: (value: string, key: string) => void): void; entries(): IterableIterator<[string, string]>; /** 转成 `wx.request` 需要的 plain object header。 */ toPlainObject(): Record; } /** * 极简 ReadableStream 占位:单读者、按到达顺序 push,不支持 tee/pipeTo。 * * 只满足 `iterSSEEvents` 的调用面:`getReader().read()` / `.cancel()` / `.releaseLock()`。 * * 背压:队列按字节数计数,超过 `maxBufferedBytes` 时整条流直接 `abort`—— * 不是真正的生产者暂停式背压(占位实现做不到暂停 `wx.request` 的分块推送), * 但至少把「内存持续膨胀」变成「明确报错」,调用方能感知并重试/降级。 */ declare class MiniProgramReadableStream { private readonly queue; private queuedBytes; private readonly maxBufferedBytes; private closed; private error; private pendingResolve; private pendingReject; private cancelled; constructor(options?: { maxBufferedBytes?: number; }); /** 供 fetch 适配层喂数据用,不是公开 API。超过字节上限时自动 abort。 */ push(chunk: Uint8Array): void; /** 供 fetch 适配层在请求正常结束时调用,不是公开 API。 */ close(): void; /** * 供 fetch 适配层在检测到「流已经开始但实际是错误响应/请求异常终止」 * 时调用,不是公开 API。跟 `close()` 的区别:`close()` 是正常 EOF, * `abort()` 让当前挂起的 `read()` 与后续所有 `read()` 都 reject, * 避免调用方把截断的错误响应体误判为一段正常收完的 SSE 内容。 */ abort(err: Error): void; getReader(): { read(): Promise<{ done: boolean; value?: Uint8Array; }>; cancel(): Promise; releaseLock(): void; }; } /** 是否已经具备(原生或已安装占位)小程序流式响应所需的 `ReadableStream`。 */ declare function isStreamingSupported(): boolean; /** * 按需安装 `Headers` / `ReadableStream` 占位实现到 `globalThis`。 * * 幂等:已存在同名全局对象时跳过,不覆盖原生实现。 * 必须在调用 `createWorkBuddyCloud(...)` 之前执行一次。 */ declare function ensureMiniProgramPolyfills(): void; /** * WorkBuddy Cloud SDK 小程序适配层公开入口。 * * 推荐用法(应用侧)—— 一站式工厂,把 polyfill、wx fetch、wx storage 与根客户端 * 按固定顺序原子装配: * * ```ts * import { createMiniProgramWorkBuddyCloud } from '@genie/workbuddy-cloud-sdk/miniprogram'; * * const cloud = createMiniProgramWorkBuddyCloud({ * endpoint: 'https://app.workbuddy.link', // 小程序没有 location.origin,必须显式传 * publishableKey: 'wbpk_xxx', * }); * ``` * * 三个适配件(`ensureMiniProgramPolyfills` / `createMiniProgramFetch` / * `createMiniProgramStorage`)也继续单独导出,供确有精细控制需要的场景手动组合 * (顺序约束见各文件顶部注释);一般接入直接用上面的工厂。 * * 见 `platform/miniprogram/fetch.ts`、`storage.ts`、`polyfills.ts` 顶部注释 * 里标注的占位/待验证事项——这一层还没有真机联调过。 */ /** * `createMiniProgramWorkBuddyCloud` 的入参。 * * 只收 `publicConfig` 那三项公开配置 —— 刻意**不**透传根 `WorkBuddyCloudOptions` * 的 `fetch` / `storage` 注入口:那两个口子是给「非浏览器但也非小程序」的运行时 * 组合用的;在小程序里放开它们,接入方就能绕过 wx 适配器塞进一个浏览器实现, * 错误要等到真机上才暴露。 */ interface MiniProgramWorkBuddyCloudOptions { /** * 数据面基址,来自 `publicConfig.endpoint`。 * * 小程序宿主没有 `location.origin`,根 SDK 的同源回落在这里不存在——省略 * 会在初始化时直接抛 `WorkBuddyCloudConfigError`。保持可选只为与根 * `WorkBuddyCloudOptions` 的类型面对齐,实际接入**永远要传**。 */ endpoint?: string; /** * 环境中心 OAuth Relay 基址,来自 `publicConfig.oauthRelayBaseUrl`。 * * 小程序登录走 `signInWithWechat`(`wx.login` + 第三方平台 code),不经过 * Relay,所以这里可以不传。Relay 只有网页扫码登录才用得上。 */ oauthRelayBaseUrl?: string; /** 应用标识 key(`wbpk_` 前缀),来自 `publicConfig.publishableKey`。 */ publishableKey: string; /** * 宿主 `wx` 对象,缺省取全局 `wx`。 * * 应用代码不需要传;留这个口子是为了单测注入 mock,以及宿主把 `wx` 挂在 * 非全局位置时显式传入。fetch 与 storage 适配器**共用同一个实例**。 */ wx?: MiniProgramWxLike; } /** * 创建小程序版 WorkBuddy Cloud 客户端(小程序接入的推荐入口)。 * * 装配顺序固定,任何一步都不能省: * * 1. `ensureMiniProgramPolyfills()` —— 根 SDK 的共享出口在请求期直接 * `new Headers()`,小程序基础库缺这个全局对象时必须先补上; * 2. `createMiniProgramFetch(wx)` + `createMiniProgramStorage(wx)` —— * 网络与凭据存储绑定同一个 wx 实例; * 3. `createWorkBuddyCloud(...)` —— 返回的 client 与 Web 端同构, * `cloud.auth / database / storage / llm` 用法完全一致。 * * 把这个顺序收进 SDK 是刻意的:三个适配件手动组合时,漏掉 polyfill 或调换顺序, * 得到的都是一个「构造能过、第一个请求才炸」的 client,真机上极难归因。 * * 依赖方向说明:这里从 `../../client.js`(而非根 barrel `../../index.js`)取 * `createWorkBuddyCloud`,返回类型只用 `import type` 引入 —— 保证小程序出口对 * 核心 SDK 的依赖严格单向(根入口永远不反向 import 本模块),`./miniprogram` * 的 ESM/CJS 出口也就不会与根入口形成运行时环。 * * @throws 没有可用 wx 对象、或三项公开配置不合法(复用根 SDK 的 * `WorkBuddyCloudConfigError`)时立即抛出。 * * ⚠️ `cloud.storage` 的文件下载在小程序运行时**不可用**:本适配层的 * `fetch` 不支持 `Response.blob()/arrayBuffer()`(小程序没有全局 `Blob`, * 详见 `fetch.ts` 里 `buildResponse` 的注释),调用会拿到一条明确报错的 * rejected promise,而不是静默失败或返回错位的数据;`cloud.auth/database/llm` * 不受影响。 */ declare function createMiniProgramWorkBuddyCloud(options: MiniProgramWorkBuddyCloudOptions): WorkBuddyCloudClient; export { MiniProgramHeaders, MiniProgramReadableStream, type MiniProgramWorkBuddyCloudOptions, type MiniProgramWxLike, type WxChunkReceivedResult, type WxRequestFailResult, type WxRequestOptions, type WxRequestSuccessResult, type WxRequestTask, createMiniProgramFetch, createMiniProgramStorage, createMiniProgramWorkBuddyCloud, ensureMiniProgramPolyfills, isStreamingSupported };