Buckets:
| import { AwsClient } from "aws4fetch" | |
| import { lazy } from "@opencode-ai/core/util/lazy" | |
| export namespace Storage { | |
| export interface Adapter { | |
| read(path: string): Promise<string | undefined> | |
| write(path: string, value: string): Promise<void> | |
| remove(path: string): Promise<void> | |
| list(options?: { prefix?: string; limit?: number; after?: string; before?: string }): Promise<string[]> | |
| } | |
| function createAdapter(client: AwsClient, endpoint: string, bucket: string): Adapter { | |
| const base = `${endpoint}/${bucket}` | |
| return { | |
| async read(path: string): Promise<string | undefined> { | |
| const response = await client.fetch(`${base}/${path}`) | |
| if (response.status === 404) return undefined | |
| if (!response.ok) throw new Error(`Failed to read ${path}: ${response.status}`) | |
| return response.text() | |
| }, | |
| async write(path: string, value: string): Promise<void> { | |
| const response = await client.fetch(`${base}/${path}`, { | |
| method: "PUT", | |
| body: value, | |
| headers: { | |
| "Content-Type": "application/json", | |
| }, | |
| }) | |
| if (!response.ok) throw new Error(`Failed to write ${path}: ${response.status}`) | |
| }, | |
| async remove(path: string): Promise<void> { | |
| const response = await client.fetch(`${base}/${path}`, { | |
| method: "DELETE", | |
| }) | |
| if (!response.ok) throw new Error(`Failed to remove ${path}: ${response.status}`) | |
| }, | |
| async list(options?: { prefix?: string; limit?: number; after?: string; before?: string }): Promise<string[]> { | |
| const prefix = options?.prefix || "" | |
| const params = new URLSearchParams({ "list-type": "2", prefix }) | |
| if (options?.limit) params.set("max-keys", options.limit.toString()) | |
| if (options?.after) { | |
| const afterPath = prefix + options.after + ".json" | |
| params.set("start-after", afterPath) | |
| } | |
| const response = await client.fetch(`${base}?${params}`) | |
| if (!response.ok) throw new Error(`Failed to list ${prefix}: ${response.status}`) | |
| const xml = await response.text() | |
| const keys: string[] = [] | |
| const regex = /<Key>([^<]+)<\/Key>/g | |
| let match | |
| while ((match = regex.exec(xml)) !== null) { | |
| keys.push(match[1]) | |
| } | |
| if (options?.before) { | |
| const beforePath = prefix + options.before + ".json" | |
| return keys.filter((key) => key < beforePath) | |
| } | |
| return keys | |
| }, | |
| } | |
| } | |
| function s3(): Adapter { | |
| const bucket = process.env.OPENCODE_STORAGE_BUCKET! | |
| const region = process.env.OPENCODE_STORAGE_REGION || "us-east-1" | |
| const client = new AwsClient({ | |
| region, | |
| accessKeyId: process.env.OPENCODE_STORAGE_ACCESS_KEY_ID!, | |
| secretAccessKey: process.env.OPENCODE_STORAGE_SECRET_ACCESS_KEY!, | |
| }) | |
| return createAdapter(client, `https://s3.${region}.amazonaws.com`, bucket) | |
| } | |
| function r2() { | |
| const accountId = process.env.OPENCODE_STORAGE_ACCOUNT_ID! | |
| const client = new AwsClient({ | |
| accessKeyId: process.env.OPENCODE_STORAGE_ACCESS_KEY_ID!, | |
| secretAccessKey: process.env.OPENCODE_STORAGE_SECRET_ACCESS_KEY!, | |
| }) | |
| return createAdapter(client, `https://${accountId}.r2.cloudflarestorage.com`, process.env.OPENCODE_STORAGE_BUCKET!) | |
| } | |
| const adapter = lazy(() => { | |
| const type = process.env.OPENCODE_STORAGE_ADAPTER | |
| if (type === "r2") return r2() | |
| if (type === "s3") return s3() | |
| throw new Error("No storage adapter configured") | |
| }) | |
| function resolve(key: string[]) { | |
| return key.join("/") + ".json" | |
| } | |
| export async function read<T>(key: string[]) { | |
| const result = await adapter().read(resolve(key)) | |
| if (!result) return undefined | |
| return JSON.parse(result) as T | |
| } | |
| export function write<T>(key: string[], value: T) { | |
| return adapter().write(resolve(key), JSON.stringify(value)) | |
| } | |
| export function remove(key: string[]) { | |
| return adapter().remove(resolve(key)) | |
| } | |
| export async function list(options?: { prefix?: string[]; limit?: number; after?: string; before?: string }) { | |
| const p = options?.prefix ? options.prefix.join("/") + (options.prefix.length ? "/" : "") : "" | |
| const result = await adapter().list({ | |
| prefix: p, | |
| limit: options?.limit, | |
| after: options?.after, | |
| before: options?.before, | |
| }) | |
| return result.map((x) => x.replace(/\.json$/, "").split("/")) | |
| } | |
| export async function update<T>(key: string[], fn: (draft: T) => void) { | |
| const val = await read<T>(key) | |
| if (!val) throw new Error("Not found") | |
| fn(val) | |
| await write(key, val) | |
| return val | |
| } | |
| } | |
Xet Storage Details
- Size:
- 4.67 kB
- Xet hash:
- b876f78df2f67380352181f4003984c6ed8b9fa38a545b377f11c2626124c042
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.