export type Json =
| string
| number
| boolean
| null
| { [key: string]: Json | undefined }
| Json[]
export type JsonRecord = { [key: string]: Json | undefined }
export type HttpMethod =
| 'GET'
| 'HEAD'
| 'OPTIONS'
| 'POST'
| 'PUT'
| 'PATCH'
| 'DELETE'
export type DryRunResult<T extends JsonRecord = JsonRecord> = {
dryRun: true
method: string
path: string
url?: string
headers?: Record<string, string>
query?: Record<string, string>
body?: T | Json | undefined
}
export type SupabaseAuthInput = {
account?: string
secretName?: string
patSecretName?: string
serviceRoleSecretName?: string
projectRef?: string
projectUrl?: string
}
export type MutationFlags = {
dryRun?: boolean
confirm?: boolean
}
export class SupabaseRequestError extends Error {
readonly status: number
readonly method: string
readonly path: string
readonly code?: string
constructor(input: {
message: string
status: number
method: string
path: string
code?: string
}) {
super(input.message)
this.name = 'SupabaseRequestError'
this.status = input.status
this.method = input.method
this.path = input.path
this.code = input.code
}
}
export function isRecord(value: unknown): value is JsonRecord {
return value !== null && typeof value === 'object' && !Array.isArray(value)
}
export function requireRecord(value: unknown, label: string): JsonRecord {
if (!isRecord(value)) {
throw new Error(`${label} must be an object.`)
}
return value
}
export function requireString(
value: unknown,
label: string,
options: { allowEmpty?: boolean } = {},
): string {
if (typeof value !== 'string') {
throw new Error(`${label} must be a string.`)
}
const trimmed = value.trim()
if (!options.allowEmpty && trimmed.length === 0) {
throw new Error(`${label} must not be empty.`)
}
return trimmed
}
export function optionalString(value: unknown, label: string): string | undefined {
if (value === undefined || value === null) return undefined
return requireString(value, label)
}
export function optionalBoolean(value: unknown, label: string): boolean | undefined {
if (value === undefined || value === null) return undefined
if (typeof value !== 'boolean') {
throw new Error(`${label} must be a boolean.`)
}
return value
}
export function optionalStringArray(
value: unknown,
label: string,
): Array<string> | undefined {
if (value === undefined || value === null) return undefined
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
throw new Error(`${label} must be an array of strings.`)
}
return value.map((item) => item.trim()).filter((item) => item.length > 0)
}
export function clampInt(
value: unknown,
min: number,
max: number,
fallback: number,
label = 'limit',
): number {
if (value === undefined || value === null) return fallback
if (typeof value !== 'number' || !Number.isFinite(value)) {
throw new Error(`${label} must be a finite number.`)
}
const rounded = Math.trunc(value)
return Math.min(max, Math.max(min, rounded))
}
export function compactRecord<T extends JsonRecord>(value: T): T {
const next: JsonRecord = {}
for (const [key, item] of Object.entries(value)) {
if (item !== undefined) next[key] = item
}
return next as T
}
export function isReadMethod(method: string): boolean {
return method === 'GET' || method === 'HEAD' || method === 'OPTIONS'
}
export function normalizeHttpMethod(value: unknown, fallback: HttpMethod): HttpMethod {
const method = (optionalString(value, 'method') ?? fallback).toUpperCase()
switch (method) {
case 'GET':
case 'HEAD':
case 'OPTIONS':
case 'POST':
case 'PUT':
case 'PATCH':
case 'DELETE':
return method
default:
throw new Error(`Unsupported HTTP method: ${method}`)
}
}
/**
* Shared Supabase types for this package.
* @example
* import types from 'kody:@kody/supabase/types'
* const catalog = await types()
*/
export default async function typesOverview() {
return {
package: '@kody/supabase',
exports: [
'DryRunResult',
'SupabaseAuthInput',
'SupabaseRequestError',
'HttpMethod',
],
}
}