11/**
22 * @bitcode /artifacts
33 *
4- * Concrete ArtifactStorage (S3 primary, Supabase Storage fallback) + BC barrel .
4+ * BC composition barrel: resolve ArtifactStorage from configured providers .
55 *
66 * Hierarchy:
7- * @bitcode /artifact-generics Artifact primitive + storage contract
8- * @bitcode /generic-artifacts-patch PatchArtifact base
9- * @bitcode /asset-packs-synthesis AssetPackPatchArtifact product
10- * @bitcode /artifacts this package (backend implementation)
7+ * @bitcode /artifact-generics Artifact + ArtifactStorage contract
8+ * @bitcode /generic-artifacts-patch PatchArtifact (type)
9+ * @bitcode /generic-artifacts-aws S3 provider
10+ * @bitcode /generic-artifacts-supabase Supabase Storage provider
11+ * @bitcode /generic-artifacts-vercel Vercel Blob provider
12+ * @bitcode /artifacts this package (compose + BC saveArtifact)
1113 *
12- * Prefer importing contracts from artifact-generics and bases from
13- * generic-artifacts-patch for new code. This package remains the default
14- * saveArtifact / putArtifactAtKey implementation used by execution + logger.
14+ * Default order: AWS S3 → Supabase → Vercel Blob (first configured wins).
1515 */
1616
17- import { createClient as createSupabase } from '@supabase/supabase-js' ;
18- import { S3Client , PutObjectCommand } from '@aws-sdk/client-s3' ;
19- import * as crypto from 'crypto' ;
2017import type {
2118 ArtifactBytes ,
2219 ArtifactInfo ,
2320 ArtifactStorage ,
2421} from '@bitcode/artifact-generics' ;
2522import { DEFAULT_ARTIFACT_CONTENT_TYPE } from '@bitcode/artifact-generics' ;
23+ import { createAwsS3ArtifactStorage } from '@bitcode/generic-artifacts-aws' ;
24+ import { createSupabaseArtifactStorage } from '@bitcode/generic-artifacts-supabase' ;
25+ import { createVercelBlobArtifactStorage } from '@bitcode/generic-artifacts-vercel' ;
2626
2727export type { ArtifactInfo , ArtifactBytes , ArtifactStorage } from '@bitcode/artifact-generics' ;
2828export {
@@ -33,98 +33,55 @@ export {
3333 assertArtifactId ,
3434} from '@bitcode/artifact-generics' ;
3535
36- const s3Bucket = process . env . ARTIFACT_S3_BUCKET ;
37- const supabaseUrl = process . env . SUPABASE_URL ;
38- const supabaseAnonKey =
39- process . env . SUPABASE_ANON_KEY || process . env . NEXT_PUBLIC_SUPABASE_ANON_KEY ;
36+ export { createAwsS3ArtifactStorage } from '@bitcode/generic-artifacts-aws' ;
37+ export { createSupabaseArtifactStorage } from '@bitcode/generic-artifacts-supabase' ;
38+ export { createVercelBlobArtifactStorage } from '@bitcode/generic-artifacts-vercel' ;
4039
41- let s3Client : S3Client | null = null ;
42- if ( s3Bucket && process . env . AWS_REGION ) {
43- s3Client = new S3Client ( { region : process . env . AWS_REGION } ) ;
40+ export type ArtifactStorageProviderId = 'aws' | 'supabase' | 'vercel' ;
41+
42+ /**
43+ * Resolve the first configured ArtifactStorage provider.
44+ * Order: aws (S3) → supabase → vercel.
45+ */
46+ export function resolveArtifactStorage ( ) : ArtifactStorage | null {
47+ return (
48+ createAwsS3ArtifactStorage ( ) ??
49+ createSupabaseArtifactStorage ( ) ??
50+ createVercelBlobArtifactStorage ( ) ??
51+ null
52+ ) ;
4453}
4554
46- let supabaseStorage : ReturnType < typeof createSupabase > | null = null ;
47- if ( ! s3Client && supabaseUrl && supabaseAnonKey ) {
48- supabaseStorage = createSupabase ( supabaseUrl , supabaseAnonKey ) ;
55+ function requireStorage ( ) : ArtifactStorage {
56+ const storage = resolveArtifactStorage ( ) ;
57+ if ( ! storage ) {
58+ throw new Error (
59+ 'No artifact storage backend configured (AWS S3, Supabase, or Vercel Blob).' ,
60+ ) ;
61+ }
62+ return storage ;
4963}
5064
65+ /** Lazy default bound to process env (first configured provider). */
66+ export const defaultArtifactStorage : ArtifactStorage = {
67+ save : ( buffer , name , contentType ) =>
68+ requireStorage ( ) . save ( buffer , name , contentType ) ,
69+ putAtKey : ( key , buffer , contentType ) =>
70+ requireStorage ( ) . putAtKey ( key , buffer , contentType ) ,
71+ } ;
72+
5173export async function saveArtifact (
5274 buffer : ArtifactBytes ,
5375 name : string ,
5476 contentType = DEFAULT_ARTIFACT_CONTENT_TYPE ,
5577) : Promise < ArtifactInfo > {
56- const bytes = typeof buffer === 'string' ? Buffer . from ( buffer ) : Buffer . from ( buffer ) ;
57- const key = `${ Date . now ( ) } -${ crypto . randomUUID ( ) } -${ name } ` ;
58-
59- if ( s3Client ) {
60- await s3Client . send (
61- new PutObjectCommand ( {
62- Bucket : s3Bucket ,
63- Key : key ,
64- Body : bytes ,
65- ContentType : contentType ,
66- } ) ,
67- ) ;
68-
69- const url = `https://${ s3Bucket } .s3.${ process . env . AWS_REGION } .amazonaws.com/${ key } ` ;
70- return { url, size : bytes . length , name } ;
71- }
72-
73- if ( supabaseStorage ) {
74- const { data, error } = await supabaseStorage . storage . from ( 'artifacts' ) . upload ( key , bytes , {
75- contentType,
76- } ) ;
77- if ( error ) throw error ;
78- const { data : publicUrlData } = supabaseStorage . storage
79- . from ( 'artifacts' )
80- . getPublicUrl ( data ?. path || key ) ;
81- return { url : publicUrlData . publicUrl , size : bytes . length , name } ;
82- }
83-
84- throw new Error ( 'No artifact storage backend configured (S3 or Supabase).' ) ;
78+ return defaultArtifactStorage . save ( buffer , name , contentType ) ;
8579}
8680
87- /**
88- * Put an artifact at an explicit key/path in the backing store.
89- * Useful for stable locations (e.g., logs) that are updated over time.
90- */
9181export async function putArtifactAtKey (
9282 key : string ,
9383 buffer : ArtifactBytes ,
9484 contentType = DEFAULT_ARTIFACT_CONTENT_TYPE ,
9585) : Promise < ArtifactInfo > {
96- const bytes = typeof buffer === 'string' ? Buffer . from ( buffer ) : Buffer . from ( buffer ) ;
97-
98- if ( s3Client ) {
99- await s3Client . send (
100- new PutObjectCommand ( {
101- Bucket : s3Bucket ! ,
102- Key : key ,
103- Body : bytes ,
104- ContentType : contentType ,
105- } ) ,
106- ) ;
107- const url = `https://${ s3Bucket } .s3.${ process . env . AWS_REGION } .amazonaws.com/${ key } ` ;
108- return { url, size : bytes . length , name : key } ;
109- }
110-
111- if ( supabaseStorage ) {
112- const { data, error } = await supabaseStorage . storage . from ( 'artifacts' ) . upload ( key , bytes , {
113- contentType,
114- upsert : true ,
115- } as any ) ;
116- if ( error ) throw error ;
117- const { data : publicUrlData } = supabaseStorage . storage
118- . from ( 'artifacts' )
119- . getPublicUrl ( ( data as any ) ?. path || key ) ;
120- return { url : publicUrlData . publicUrl , size : bytes . length , name : key } ;
121- }
122-
123- throw new Error ( 'No artifact storage backend configured (S3 or Supabase).' ) ;
86+ return defaultArtifactStorage . putAtKey ( key , buffer , contentType ) ;
12487}
125-
126- /** Default ArtifactStorage bound to process env (S3 → Supabase). */
127- export const defaultArtifactStorage : ArtifactStorage = {
128- save : saveArtifact ,
129- putAtKey : putArtifactAtKey ,
130- } ;
0 commit comments