Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/supabase

src/types.ts

175 lines · 4.0 KB · TypeScript
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',
		],
	}
}