11import { promises as fs } from "fs" ;
22import path from "path" ;
3- import {
4- GetObjectCommand ,
5- NoSuchKey ,
6- PutObjectCommand ,
7- S3Client ,
8- } from "@aws-sdk/client-s3" ;
9- import { Storage } from "@google-cloud/storage" ;
10- import { createClient as createRedisClient } from "@redis/client" ;
113
124import type {
135 FlagAPIResponse ,
@@ -35,7 +27,9 @@ export type S3FallbackProviderOptions = {
3527 /**
3628 * Optional S3 client. A default client is created when omitted.
3729 */
38- client ?: Pick < S3Client , "send" > ;
30+ client ?: {
31+ send ( command : unknown ) : Promise < any > ;
32+ } ;
3933
4034 /**
4135 * Prefix for generated per-environment keys.
@@ -54,7 +48,18 @@ export type GCSFallbackProviderOptions = {
5448 /**
5549 * Optional GCS client. A default client is created when omitted.
5650 */
57- client ?: Pick < Storage , "bucket" > ;
51+ client ?: {
52+ bucket ( name : string ) : {
53+ file ( path : string ) : {
54+ exists ( ) : Promise < [ boolean ] > ;
55+ download ( ) : Promise < [ Uint8Array ] > ;
56+ save (
57+ body : string ,
58+ options : { contentType : string } ,
59+ ) : Promise < unknown > ;
60+ } ;
61+ } ;
62+ } ;
5863
5964 /**
6065 * Prefix for generated per-environment keys.
@@ -160,6 +165,16 @@ function parseSnapshot(raw: string) {
160165 return isFlagsFallbackSnapshot ( parsed ) ? parsed : undefined ;
161166}
162167
168+ async function createDefaultS3Client ( ) {
169+ const { S3Client } = await import ( "@aws-sdk/client-s3" ) ;
170+ return new S3Client ( { } ) ;
171+ }
172+
173+ async function createDefaultGCSClient ( ) {
174+ const { Storage } = await import ( "@google-cloud/storage" ) ;
175+ return new Storage ( ) ;
176+ }
177+
163178export function createFileFallbackProvider ( {
164179 directory,
165180} : FileFallbackProviderOptions = { } ) : FlagsFallbackProvider {
@@ -189,13 +204,27 @@ export function createFileFallbackProvider({
189204
190205export function createS3FallbackProvider ( {
191206 bucket,
192- client = new S3Client ( { } ) ,
207+ client,
193208 keyPrefix,
194209} : S3FallbackProviderOptions ) : FlagsFallbackProvider {
210+ let defaultClient :
211+ | {
212+ send ( command : unknown ) : Promise < any > ;
213+ }
214+ | undefined ;
215+
216+ const getClient = async ( ) => {
217+ defaultClient ??= client ?? ( await createDefaultS3Client ( ) ) ;
218+ return defaultClient ;
219+ } ;
220+
195221 return {
196222 async load ( context ) {
223+ const s3 = await getClient ( ) ;
224+ const { GetObjectCommand } = await import ( "@aws-sdk/client-s3" ) ;
225+
197226 try {
198- const response = await client . send (
227+ const response = await s3 . send (
199228 new GetObjectCommand ( {
200229 Bucket : bucket ,
201230 Key : snapshotObjectKey ( context , keyPrefix ) ,
@@ -208,7 +237,6 @@ export function createS3FallbackProvider({
208237 return parseSnapshot ( body ) ;
209238 } catch ( error : any ) {
210239 if (
211- error instanceof NoSuchKey ||
212240 error ?. name === "NoSuchKey" ||
213241 error ?. $metadata ?. httpStatusCode === 404
214242 ) {
@@ -219,7 +247,10 @@ export function createS3FallbackProvider({
219247 } ,
220248
221249 async save ( context , snapshot ) {
222- await client . send (
250+ const s3 = await getClient ( ) ;
251+ const { PutObjectCommand } = await import ( "@aws-sdk/client-s3" ) ;
252+
253+ await s3 . send (
223254 new PutObjectCommand ( {
224255 Bucket : bucket ,
225256 Key : snapshotObjectKey ( context , keyPrefix ) ,
@@ -233,12 +264,20 @@ export function createS3FallbackProvider({
233264
234265export function createGCSFallbackProvider ( {
235266 bucket,
236- client = new Storage ( ) ,
267+ client,
237268 keyPrefix,
238269} : GCSFallbackProviderOptions ) : FlagsFallbackProvider {
270+ let defaultClient : GCSFallbackProviderOptions [ "client" ] | undefined ;
271+
272+ const getClient = async ( ) => {
273+ defaultClient ??= client ?? ( await createDefaultGCSClient ( ) ) ;
274+ return defaultClient ;
275+ } ;
276+
239277 return {
240278 async load ( context ) {
241- const file = client
279+ const storage = await getClient ( ) ;
280+ const file = storage
242281 . bucket ( bucket )
243282 . file ( snapshotObjectKey ( context , keyPrefix ) ) ;
244283 const [ exists ] = await file . exists ( ) ;
@@ -247,11 +286,12 @@ export function createGCSFallbackProvider({
247286 }
248287
249288 const [ contents ] = await file . download ( ) ;
250- return parseSnapshot ( contents . toString ( "utf-8" ) ) ;
289+ return parseSnapshot ( Buffer . from ( contents ) . toString ( "utf-8" ) ) ;
251290 } ,
252291
253292 async save ( context , snapshot ) {
254- await client
293+ const storage = await getClient ( ) ;
294+ await storage
255295 . bucket ( bucket )
256296 . file ( snapshotObjectKey ( context , keyPrefix ) )
257297 . save ( JSON . stringify ( snapshot ) , {
@@ -280,9 +320,16 @@ export function createRedisFallbackProvider({
280320 return client ;
281321 }
282322
283- defaultClient ??= createRedisClient (
284- process . env . REDIS_URL ? { url : process . env . REDIS_URL } : undefined ,
285- ) ;
323+ if ( ! process . env . REDIS_URL ) {
324+ throw new Error (
325+ "fallbackProviders.redis() requires REDIS_URL to be set when no client is provided" ,
326+ ) ;
327+ }
328+
329+ if ( ! defaultClient ) {
330+ const { createClient } = await import ( "@redis/client" ) ;
331+ defaultClient = createClient ( { url : process . env . REDIS_URL } ) ;
332+ }
286333
287334 if ( ! defaultClient . isOpen ) {
288335 connectPromise ??= defaultClient . connect ( ) ;
0 commit comments