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'),
})
}