@quatrain/storage-supabase 1.1.18 → 1.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,15 @@
1
+ # LICENSE UPDATE NOTICE
2
+
3
+ As of 01/01/2026, Quatrain Core is licensed under the **GNU Affero General Public License v3.0 (AGPL v3)**.
4
+ Previous versions remain under the MIT License.
5
+
6
+ ## Why AGPL?
7
+
8
+ We believe in open collaboration for the development ecosystem. The AGPL ensures that any modification or deployment of this BaaS stack, including over a network, benefits the entire community.
9
+
10
+ ## Commercial Services & Enterprise Usage
11
+
12
+ We provide official deployment services, technical training, and certification for Quatrain Core.
13
+ For organizations requiring a non-copyleft license (commercial license) or custom proprietary integrations, please contact the copyright holder: **Quatrain Technologies**.
14
+
15
+ Copyright © 2024-2026 Quatrain Technologies. All Rights Reserved.
package/package.json CHANGED
@@ -1,15 +1,18 @@
1
1
  {
2
2
  "name": "@quatrain/storage-supabase",
3
- "version": "1.1.18",
3
+ "version": "1.1.20",
4
4
  "description": "Storage adapter for Supabase Storage",
5
- "main": "lib/index.js",
6
- "types": "lib/index.d.ts",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bun": "src/index.ts",
7
8
  "files": [
8
- "lib/",
9
+ "LICENSE.md",
10
+ "src/",
11
+ "dist/",
9
12
  "README.md"
10
13
  ],
11
14
  "author": "Quatrain Développement SAS <developers@quatrain.com>",
12
- "license": "MIT",
15
+ "license": "AGPL-3.0-only",
13
16
  "devDependencies": {
14
17
  "@tsconfig/recommended": "^1.0.1",
15
18
  "@types/jest": "^27.0.3",
@@ -22,8 +25,8 @@
22
25
  "typescript": "^5.1.5"
23
26
  },
24
27
  "dependencies": {
25
- "@quatrain/core": "^1.1.24",
26
- "@quatrain/storage": "^1.1.23",
28
+ "@quatrain/core": "^1.1.45",
29
+ "@quatrain/storage": "^1.1.25",
27
30
  "@supabase/storage-js": "^2.7.2",
28
31
  "@supabase/supabase-js": "^2.87.3"
29
32
  },
@@ -31,11 +34,6 @@
31
34
  "test-ci": "jest --runInBand",
32
35
  "build": "tsc",
33
36
  "wbuild": "tsc --watch",
34
- "bump-to": "yarn version",
35
- "hash": "node ../../bin/hashFolder.js",
36
- "hash:persist": "yarn hash > .hash_latest.txt",
37
- "hash:compare": "yarn hash > .hash_newest.txt && cmp -s .hash_latest.txt .hash_newest.txt",
38
- "publish": "yarn hash:compare || yarn publish:process",
39
- "publish:process": "yarn version patch && yarn build && yarn npm publish --access public && yarn hash:persist"
37
+ "bump-to": "yarn version"
40
38
  }
41
39
  }
@@ -0,0 +1,250 @@
1
+ import {
2
+ Storage,
3
+ AbstractStorageAdapter,
4
+ FileType,
5
+ FileResponseLinkType,
6
+ StorageParameters,
7
+ DownloadFileMetaType,
8
+ } from '@quatrain/storage'
9
+ import { Readable, Stream } from 'stream'
10
+ import { StorageClient } from '@supabase/storage-js'
11
+ import { tmpdir } from 'os'
12
+ import { join } from 'path'
13
+ import fs from 'node:fs'
14
+
15
+ export class SupabaseStorageAdapter extends AbstractStorageAdapter {
16
+ protected _client: StorageClient
17
+
18
+ constructor(params: StorageParameters) {
19
+ super(params)
20
+ this._client = new StorageClient(params.config.endpoint, {
21
+ apikey: params.config.secret,
22
+ Authorization: `Bearer ${params.config.secret}`,
23
+ })
24
+
25
+ Storage.info(`[SSA] Supabase Storage Adapter initialized`)
26
+ }
27
+
28
+ getDriver() {
29
+ return this._client
30
+ }
31
+
32
+ getMetaData(file: FileType): Promise<FileType> {
33
+ return new Promise(() => file)
34
+ }
35
+
36
+ async test(): Promise<boolean> {
37
+ try {
38
+ const { data, error } = await this._client.listBuckets()
39
+
40
+ if (error !== null) {
41
+ Storage.error(`Unable to get buckets list`)
42
+ throw new Error(`Unable to get buckets list: ${error.message}`)
43
+ }
44
+
45
+ // Storage.info(`S3 Buckets: ${JSON.stringify(data)}`)
46
+
47
+ return true
48
+ } catch (err) {
49
+ Storage.error(
50
+ `Failed to connect to storage: ${(err as Error).message}`
51
+ )
52
+ return false
53
+ }
54
+ }
55
+
56
+ async streamToBuffer(stream: Stream): Promise<Buffer> {
57
+ return new Promise<Buffer>((resolve, reject) => {
58
+ const _buf: any[] = []
59
+
60
+ stream.on('data', (chunk) => _buf.push(chunk))
61
+ stream.on('end', () => resolve(Buffer.concat(_buf)))
62
+ stream.on('error', (err) => reject(err))
63
+ })
64
+ }
65
+
66
+ toArrayBuffer(buffer: Buffer): ArrayBuffer {
67
+ const arrayBuffer = new ArrayBuffer(buffer.length)
68
+ const view = new Uint8Array(arrayBuffer)
69
+ for (let i = 0; i < buffer.length; ++i) {
70
+ view[i] = buffer[i]
71
+ }
72
+ return arrayBuffer
73
+ }
74
+
75
+ async create(file: FileType, stream: Readable | string): Promise<FileType> {
76
+ Storage.info(`[SSA] Uploading ${file.ref} to ${file.bucket}`)
77
+
78
+ if (typeof stream === 'string') {
79
+ const { error } = await this._client
80
+ .from(file.bucket)
81
+ .upload(file.ref, new Blob([fs.readFileSync(stream)]), {
82
+ upsert: true,
83
+ contentType: file.contentType,
84
+ })
85
+ if (error !== null) {
86
+ Storage.error(error)
87
+ Storage.error(`Unable to upload ${file.ref} to ${file.bucket}`)
88
+ throw new Error(`Unable to upload ${file.ref} to ${file.bucket}`)
89
+ }
90
+ } else {
91
+ const content = new Blob([
92
+ this.toArrayBuffer(await this.streamToBuffer(stream)),
93
+ ])
94
+ const { error } = await this._client
95
+ .from(file.bucket)
96
+ .upload(file.ref, content, {
97
+ // cacheControl: '3600',
98
+ upsert: false,
99
+ contentType: file.contentType,
100
+ })
101
+ if (error !== null) {
102
+ console.log(error)
103
+ Storage.error(`Unable to upload ${file.ref} to ${file.bucket}`)
104
+ throw new Error(`Unable to upload ${file.ref} to ${file.bucket}`)
105
+ }
106
+ }
107
+
108
+ return file
109
+ }
110
+
111
+ async copy(file: FileType, destFile: FileType) {
112
+ try {
113
+ const response = await this._client
114
+ .from(file.bucket)
115
+ .copy(file.ref, destFile.ref, {
116
+ destinationBucket: destFile.bucket,
117
+ })
118
+ } catch (err) {
119
+ console.error(err)
120
+ }
121
+ }
122
+
123
+ async move(file: FileType, destFile: FileType) {
124
+ Storage.info(
125
+ `Moving file ${file.ref} to ${destFile.ref} in same bucket ${file.bucket}`
126
+ )
127
+ const { error } = await this._client
128
+ .from(file.bucket)
129
+ .move(file.ref, destFile.ref)
130
+
131
+ if (error !== null) {
132
+ Storage.error(
133
+ `Unable to move ${file.ref} to ${destFile.ref}: ${error.message}`
134
+ )
135
+ throw new Error(`Unable to move ${file.ref} to ${destFile.ref}`)
136
+ }
137
+
138
+ return destFile
139
+ }
140
+
141
+ async getUrl(file: FileType, expiresIn = 3600) {
142
+ Storage.debug(
143
+ `Getting signed url for file ${file.ref} in bucket ${file.bucket}`
144
+ )
145
+ const { data, error } = await this._client
146
+ .from(file.bucket)
147
+ .createSignedUrl(file.ref, expiresIn, { download: true })
148
+
149
+ if (error !== null) {
150
+ throw new Error(`Unable to get signed url: ${error}`)
151
+ }
152
+
153
+ return { url: data?.signedUrl, expiresIn }
154
+ }
155
+
156
+ async delete(file: FileType) {
157
+ Storage.info(`Deleting file ${file.ref} in bucket ${file.bucket}`)
158
+ const { error } = await this._client.from(file.bucket).remove([file.ref])
159
+
160
+ if (error !== null) {
161
+ throw new Error(`Unable to delete ${file.ref}`)
162
+ }
163
+
164
+ Storage.info(`Object ${file.ref} successfully deleted`)
165
+
166
+ return true
167
+ }
168
+
169
+ async getReadable(file: FileType): Promise<Readable> {
170
+ Storage.debug(`GET Readable : ${file.ref}`)
171
+
172
+ const path = join(tmpdir(), String(Date.now()))
173
+ const item = await this.download(file, { path, onlyContent: true })
174
+ const buffer = Buffer.from(item.toString(), 'base64')
175
+ const readable = new Readable()
176
+ readable.push(buffer)
177
+ readable.push(null)
178
+
179
+ return readable
180
+ }
181
+
182
+ async stream(file: FileType, res: any) {
183
+ Storage.debug(`GET Stream : ${file.ref}`)
184
+
185
+ const path = join(tmpdir(), String(Date.now()))
186
+ const item = await this.download(file, { path, onlyContent: true })
187
+ const buffer = Buffer.from(item.toString(), 'base64')
188
+ const readable = new Readable()
189
+ readable.push(buffer)
190
+ readable.push(null)
191
+
192
+ return readable.pipe(res)
193
+ }
194
+
195
+ async download(
196
+ file: FileType,
197
+ meta: DownloadFileMetaType
198
+ ): Promise<string | Blob> {
199
+ const { data, error } = await this._client
200
+ .from(file.bucket)
201
+ .download(file.ref)
202
+
203
+ if (error !== null) {
204
+ console.log(error)
205
+ throw new Error(`Unable to download ${file.ref}`)
206
+ }
207
+
208
+ if (meta.onlyContent) {
209
+ return data
210
+ }
211
+
212
+ // Write the downloaded data to the specified path
213
+ const buffer = await data.arrayBuffer()
214
+ fs.writeFileSync(meta.path, Buffer.from(buffer))
215
+
216
+ return meta.path
217
+ }
218
+
219
+ /**
220
+ * Get signed upload url for file
221
+ * Careful, on docker the time to live for JWT signature is defaulted to 60 sec
222
+ * Add SIGNED_UPLOAD_URL_EXPIRATION_TIME env variable to fix TTL
223
+ * @param file FileType
224
+ * @param _expiresIn actually ignored
225
+ * @returns
226
+ */
227
+ async getUploadUrl(
228
+ file: FileType,
229
+ _expiresIn = 7200
230
+ ): Promise<FileResponseLinkType> {
231
+ const { data, error } = await this._client
232
+ .from(file.bucket)
233
+ .createSignedUploadUrl(file.ref, { upsert: true })
234
+
235
+ if (error !== null) {
236
+ throw new Error(`Unable to get signed upload url: ${error}`)
237
+ }
238
+
239
+ Storage.info(
240
+ `Upload URL for ${file.ref} in bucket ${file.bucket} is ${data?.signedUrl}`
241
+ )
242
+
243
+ return {
244
+ url: data?.signedUrl,
245
+ method: 'PUT',
246
+ accept: file.contentType || 'application/octet-stream',
247
+ expiresIn: 7200, // fixed value in Supabase
248
+ }
249
+ }
250
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { SupabaseStorageAdapter } from './SupabaseStorageAdapter'
2
+
3
+ export { SupabaseStorageAdapter }
File without changes
File without changes
File without changes
File without changes