Skip to content

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

Package listing

@kody/supabase

src/upload-object.ts

110 lines · 3.5 KB · TypeScript
import { projectAuth } from './setup.ts'
import { assertMutationAllowed, readMutationFlags } from './safety.ts'
import { asLive, projectRequest } from './client.ts'
import {
	optionalBoolean,
	optionalString,
	requireString,
	type DryRunResult,
	type SupabaseAuthInput,
} from './types.ts'

/**
 * Upload an object to Storage. Requires `confirm: true`, or use `dryRun: true`.
 * Pass `content` for UTF-8 text or `contentBase64` for binary.
 * @example
 * import uploadObject from 'kody:@kody/supabase/upload-object'
 * const preview = await uploadObject({
 *   projectRef: 'abcdefghijklmnop',
 *   bucket: 'docs',
 *   path: 'notes/hello.txt',
 *   content: 'hello',
 *   dryRun: true,
 * })
 */
export async function uploadObject(
	input: SupabaseAuthInput & {
		bucket: string
		path: string
		content?: string
		contentBase64?: string
		contentType?: string
		upsert?: boolean
		dryRun?: boolean
		confirm?: boolean
	},
) {
	const bucket = requireString(input.bucket, 'bucket')
	const objectPath = requireString(input.path, 'path').replace(/^\/+/, '')
	const allowed = assertMutationAllowed(readMutationFlags(input), 'upload-object')
	const apiPath = `/storage/v1/object/${encodeURIComponent(bucket)}/${objectPath
		.split('/')
		.map((part) => encodeURIComponent(part))
		.join('/')}`
	if (allowed.dryRun) {
		const preview: DryRunResult = {
			dryRun: true,
			method: 'POST',
			path: apiPath,
			headers: {
				'Content-Type': input.contentType ?? 'application/octet-stream',
				'x-upsert': input.upsert ? 'true' : 'false',
			},
			body: {
				bytes: input.contentBase64
					? Math.ceil((input.contentBase64.length * 3) / 4)
					: (input.content ?? '').length,
			},
		}
		return preview
	}
	if (input.content === undefined && input.contentBase64 === undefined) {
		throw new Error('upload-object requires content or contentBase64.')
	}
	const rawBody =
		input.contentBase64 !== undefined
			? Uint8Array.from(atob(input.contentBase64), (char) => char.charCodeAt(0))
			: input.content
	const live = asLive<{ data: unknown }>(
		await projectRequest({
			auth: projectAuth(input),
			method: 'POST',
			path: apiPath,
			headers: {
				'Content-Type': input.contentType ?? 'application/octet-stream',
				'x-upsert': input.upsert ? 'true' : 'false',
			},
			rawBody,
		}),
	)
	return { uploaded: true, bucket, path: objectPath, result: live.data }
}

/**
 * Upload an object to Storage. Requires `confirm: true`, or use `dryRun: true`.
 * @example
 * import uploadObject from 'kody:@kody/supabase/upload-object'
 * const preview = await uploadObject({ projectRef: 'abcdefghijklmnop', bucket: 'docs', path: 'notes/hello.txt', content: 'hello', dryRun: true })
 */
export default async function uploadObjectEntrypoint(
	params: Partial<SupabaseAuthInput> & Record<string, unknown> = {},
) {
	return await uploadObject({
		account: optionalString(params.account, 'account'),
		secretName: optionalString(params.secretName, 'secretName'),
		serviceRoleSecretName: optionalString(
			params.serviceRoleSecretName,
			'serviceRoleSecretName',
		),
		projectRef: optionalString(params.projectRef, 'projectRef'),
		projectUrl: optionalString(params.projectUrl, 'projectUrl'),
		bucket: requireString(params.bucket, 'bucket'),
		path: requireString(params.path, 'path'),
		content: optionalString(params.content, 'content'),
		contentBase64: optionalString(params.contentBase64, 'contentBase64'),
		contentType: optionalString(params.contentType, 'contentType'),
		upsert: optionalBoolean(params.upsert, 'upsert'),
		dryRun: optionalBoolean(params.dryRun, 'dryRun'),
		confirm: optionalBoolean(params.confirm, 'confirm'),
	})
}