{"version":3,"file":"hocuspocus-s3.cjs","names":["Database","GetObjectCommand","PutObjectCommand","S3Client","HeadObjectCommand"],"sources":["../src/S3.ts"],"sourcesContent":["import type { DatabaseConfiguration } from \"@hocuspocus/extension-database\";\nimport { Database } from \"@hocuspocus/extension-database\";\nimport {\n\tS3Client,\n\tGetObjectCommand,\n\tPutObjectCommand,\n\tHeadObjectCommand,\n} from \"@aws-sdk/client-s3\";\nimport kleur from \"kleur\";\n\nexport interface S3Configuration extends DatabaseConfiguration {\n\t/**\n\t * AWS S3 region\n\t */\n\tregion?: string;\n\t/**\n\t * S3 bucket name\n\t */\n\tbucket: string;\n\t/**\n\t * S3 key prefix for documents (optional)\n\t */\n\tprefix?: string;\n\t/**\n\t * AWS credentials\n\t */\n\tcredentials?: {\n\t\taccessKeyId: string;\n\t\tsecretAccessKey: string;\n\t};\n\t/**\n\t * S3 endpoint URL (for S3-compatible services like MinIO)\n\t */\n\tendpoint?: string;\n\t/**\n\t * Force path style URLs (required for MinIO)\n\t */\n\tforcePathStyle?: boolean;\n\t/**\n\t * Custom S3 client\n\t */\n\ts3Client?: S3Client;\n}\n\nexport class S3 extends Database {\n\tprivate s3Client?: S3Client;\n\n\tconfiguration: S3Configuration = {\n\t\tregion: \"us-east-1\",\n\t\tbucket: \"\",\n\t\tprefix: \"hocuspocus-documents/\",\n\t\tforcePathStyle: false,\n\t\tfetch: async ({ documentName }) => {\n\t\t\tconst key = this.getObjectKey(documentName);\n\n\t\t\ttry {\n\t\t\t\tconst command = new GetObjectCommand({\n\t\t\t\t\tBucket: this.configuration.bucket,\n\t\t\t\t\tKey: key,\n\t\t\t\t});\n\n\t\t\t\tconst response = await this.s3Client!.send(command);\n\n\t\t\t\tif (response.Body) {\n\t\t\t\t\t// Convert stream to Uint8Array\n\t\t\t\t\tconst chunks: Uint8Array[] = [];\n\t\t\t\t\tconst reader = response.Body.transformToWebStream().getReader();\n\n\t\t\t\t\twhile (true) {\n\t\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\t\tif (done) break;\n\t\t\t\t\t\tchunks.push(value);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Combine all chunks into a single Uint8Array\n\t\t\t\t\tconst totalLength = chunks.reduce(\n\t\t\t\t\t\t(acc, chunk) => acc + chunk.length,\n\t\t\t\t\t\t0,\n\t\t\t\t\t);\n\t\t\t\t\tconst result = new Uint8Array(totalLength);\n\t\t\t\t\tlet offset = 0;\n\n\t\t\t\t\tfor (const chunk of chunks) {\n\t\t\t\t\t\tresult.set(chunk, offset);\n\t\t\t\t\t\toffset += chunk.length;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\n\t\t\t\treturn null;\n\t\t\t} catch (error: any) {\n\t\t\t\tif (\n\t\t\t\t\terror.name === \"NoSuchKey\" ||\n\t\t\t\t\terror.$metadata?.httpStatusCode === 404\n\t\t\t\t) {\n\t\t\t\t\t// Document doesn't exist yet, return null\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t\tstore: async ({ documentName, state }) => {\n\t\t\tconst key = this.getObjectKey(documentName);\n\n\t\t\tconst command = new PutObjectCommand({\n\t\t\t\tBucket: this.configuration.bucket,\n\t\t\t\tKey: key,\n\t\t\t\tBody: state,\n\t\t\t\tContentType: \"application/octet-stream\",\n\t\t\t});\n\n\t\t\tawait this.s3Client!.send(command);\n\t\t},\n\t};\n\n\tconstructor(configuration: Partial<S3Configuration>) {\n\t\tsuper({});\n\n\t\tthis.configuration = {\n\t\t\t...this.configuration,\n\t\t\t...configuration,\n\t\t};\n\n\t\t// Validate required configuration\n\t\tif (!this.configuration.bucket) {\n\t\t\tthrow new Error(\"S3 bucket name is required\");\n\t\t}\n\t}\n\n\tprivate getObjectKey(documentName: string): string {\n\t\tconst prefix = this.configuration.prefix || \"\";\n\t\treturn `${prefix}${documentName}.bin`;\n\t}\n\n\tasync onConfigure() {\n\t\t// Use custom S3 client if provided, otherwise create one\n\t\tif (this.configuration.s3Client) {\n\t\t\tthis.s3Client = this.configuration.s3Client;\n\t\t} else {\n\t\t\tconst clientConfig: any = {\n\t\t\t\tregion: this.configuration.region,\n\t\t\t};\n\n\t\t\tif (this.configuration.credentials) {\n\t\t\t\tclientConfig.credentials = this.configuration.credentials;\n\t\t\t}\n\n\t\t\tif (this.configuration.endpoint) {\n\t\t\t\tclientConfig.endpoint = this.configuration.endpoint;\n\t\t\t\tclientConfig.forcePathStyle = this.configuration.forcePathStyle;\n\t\t\t}\n\n\t\t\tthis.s3Client = new S3Client(clientConfig);\n\t\t}\n\n\t\t// Test S3 connection by checking if bucket exists\n\t\ttry {\n\t\t\tconst command = new HeadObjectCommand({\n\t\t\t\tBucket: this.configuration.bucket,\n\t\t\t\tKey: \"test-connection\", // This will likely return 404, but that's fine\n\t\t\t});\n\n\t\t\tawait this.s3Client.send(command);\n\t\t} catch (error: any) {\n\t\t\t// 404 is expected for the test key, any other error indicates connection issues\n\t\t\tif (error.$metadata?.httpStatusCode !== 404) {\n\t\t\t\t// Don't show credential errors as connection failures in development\n\t\t\t\tif (error.message?.includes(\"Could not load credentials\")) {\n\t\t\t\t\tconsole.warn(`  ${kleur.yellow(\"S3 warning:\")} ${error.message}`);\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`  ${kleur.yellow(\"Note:\")} Ensure AWS credentials are properly configured for production use`,\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`  ${kleur.red(\"S3 connection failed:\")} ${error.message}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tasync onListen() {\n\t\tconst endpoint =\n\t\t\tthis.configuration.endpoint ||\n\t\t\t`https://s3.${this.configuration.region}.amazonaws.com`;\n\t\tconsole.log(\n\t\t\t`  ${kleur.green(\"S3 extension configured:\")} bucket=${this.configuration.bucket}, endpoint=${endpoint}`,\n\t\t);\n\n\t\tif (this.configuration.prefix) {\n\t\t\tconsole.log(\n\t\t\t\t`  ${kleur.blue(\"S3 key prefix:\")} ${this.configuration.prefix}`,\n\t\t\t);\n\t\t}\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,IAAa,KAAb,cAAwBA,wCAAS;CAwEhC,YAAY,eAAyC;AACpD,QAAM,EAAE,CAAC;uBAtEuB;GAChC,QAAQ;GACR,QAAQ;GACR,QAAQ;GACR,gBAAgB;GAChB,OAAO,OAAO,EAAE,mBAAmB;IAClC,MAAM,MAAM,KAAK,aAAa,aAAa;AAE3C,QAAI;KACH,MAAM,UAAU,IAAIC,oCAAiB;MACpC,QAAQ,KAAK,cAAc;MAC3B,KAAK;MACL,CAAC;KAEF,MAAM,WAAW,MAAM,KAAK,SAAU,KAAK,QAAQ;AAEnD,SAAI,SAAS,MAAM;MAElB,MAAM,SAAuB,EAAE;MAC/B,MAAM,SAAS,SAAS,KAAK,sBAAsB,CAAC,WAAW;AAE/D,aAAO,MAAM;OACZ,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM;AAC3C,WAAI,KAAM;AACV,cAAO,KAAK,MAAM;;MAInB,MAAM,cAAc,OAAO,QACzB,KAAK,UAAU,MAAM,MAAM,QAC5B,EACA;MACD,MAAM,SAAS,IAAI,WAAW,YAAY;MAC1C,IAAI,SAAS;AAEb,WAAK,MAAM,SAAS,QAAQ;AAC3B,cAAO,IAAI,OAAO,OAAO;AACzB,iBAAU,MAAM;;AAGjB,aAAO;;AAGR,YAAO;aACC,OAAY;AACpB,SACC,MAAM,SAAS,eACf,MAAM,WAAW,mBAAmB,IAGpC,QAAO;AAER,WAAM;;;GAGR,OAAO,OAAO,EAAE,cAAc,YAAY;IACzC,MAAM,MAAM,KAAK,aAAa,aAAa;IAE3C,MAAM,UAAU,IAAIC,oCAAiB;KACpC,QAAQ,KAAK,cAAc;KAC3B,KAAK;KACL,MAAM;KACN,aAAa;KACb,CAAC;AAEF,UAAM,KAAK,SAAU,KAAK,QAAQ;;GAEnC;AAKA,OAAK,gBAAgB;GACpB,GAAG,KAAK;GACR,GAAG;GACH;AAGD,MAAI,CAAC,KAAK,cAAc,OACvB,OAAM,IAAI,MAAM,6BAA6B;;CAI/C,AAAQ,aAAa,cAA8B;AAElD,SAAO,GADQ,KAAK,cAAc,UAAU,KACzB,aAAa;;CAGjC,MAAM,cAAc;AAEnB,MAAI,KAAK,cAAc,SACtB,MAAK,WAAW,KAAK,cAAc;OAC7B;GACN,MAAM,eAAoB,EACzB,QAAQ,KAAK,cAAc,QAC3B;AAED,OAAI,KAAK,cAAc,YACtB,cAAa,cAAc,KAAK,cAAc;AAG/C,OAAI,KAAK,cAAc,UAAU;AAChC,iBAAa,WAAW,KAAK,cAAc;AAC3C,iBAAa,iBAAiB,KAAK,cAAc;;AAGlD,QAAK,WAAW,IAAIC,4BAAS,aAAa;;AAI3C,MAAI;GACH,MAAM,UAAU,IAAIC,qCAAkB;IACrC,QAAQ,KAAK,cAAc;IAC3B,KAAK;IACL,CAAC;AAEF,SAAM,KAAK,SAAS,KAAK,QAAQ;WACzB,OAAY;AAEpB,OAAI,MAAM,WAAW,mBAAmB,IAEvC,KAAI,MAAM,SAAS,SAAS,6BAA6B,EAAE;AAC1D,YAAQ,KAAK,KAAK,cAAM,OAAO,cAAc,CAAC,GAAG,MAAM,UAAU;AACjE,YAAQ,KACP,KAAK,cAAM,OAAO,QAAQ,CAAC,oEAC3B;SAED,SAAQ,MACP,KAAK,cAAM,IAAI,wBAAwB,CAAC,GAAG,MAAM,UACjD;;;CAML,MAAM,WAAW;EAChB,MAAM,WACL,KAAK,cAAc,YACnB,cAAc,KAAK,cAAc,OAAO;AACzC,UAAQ,IACP,KAAK,cAAM,MAAM,2BAA2B,CAAC,UAAU,KAAK,cAAc,OAAO,aAAa,WAC9F;AAED,MAAI,KAAK,cAAc,OACtB,SAAQ,IACP,KAAK,cAAM,KAAK,iBAAiB,CAAC,GAAG,KAAK,cAAc,SACxD"}